diff --git a/frappe/api.py b/frappe/api.py index 784e671036..ae17316631 100644 --- a/frappe/api.py +++ b/frappe/api.py @@ -90,7 +90,7 @@ def handle(): if frappe.local.form_dict.get('fields'): frappe.local.form_dict['fields'] = json.loads(frappe.local.form_dict['fields']) frappe.local.response.update({ - "data": frappe.call(frappe.desk.reportview.execute, + "data": frappe.call(frappe.client.get_list, doctype, **frappe.local.form_dict)}) if frappe.local.request.method=="POST": diff --git a/frappe/client.py b/frappe/client.py index dd80dc6026..2e72c47712 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -8,6 +8,12 @@ import frappe.model import frappe.utils import json, os +@frappe.whitelist() +def get_list(doctype, fields=None, filters=None, order_by=None, + limit_start=None, limit_page_length=None): + return frappe.get_list(doctype, fields=fields, filters=filters, order_by=order_by, + limit_start=limit_start, limit_page_length=limit_page_length) + @frappe.whitelist() def get(doctype, name=None, filters=None): if filters and not name: diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 1bb0d71a57..e68d387b38 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -78,6 +78,13 @@ "search_index": 0, "width": "50px" }, + { + "fieldname": "unique", + "fieldtype": "Check", + "label": "Unique", + "permlevel": 0, + "precision": "" + }, { "fieldname": "in_list_view", "fieldtype": "Check", @@ -314,7 +321,7 @@ "in_dialog": 1, "issingle": 0, "istable": 1, - "modified": "2014-09-05 07:41:05.956027", + "modified": "2014-09-26 05:03:44.822570", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index b17082c8f6..ac43c81db8 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -145,6 +145,13 @@ "permlevel": 0, "search_index": 0 }, + { + "fieldname": "unique", + "fieldtype": "Check", + "label": "Unique", + "permlevel": 0, + "precision": "" + }, { "fieldname": "read_only", "fieldtype": "Check", @@ -267,7 +274,7 @@ ], "icon": "icon-glass", "idx": 1, - "modified": "2014-09-05 07:41:13.076821", + "modified": "2014-09-26 05:04:49.148737", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 04d74eb287..5be88baffc 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -31,6 +31,7 @@ class CustomizeForm(Document): 'width': 'Data', 'print_width': 'Data', 'reqd': 'Check', + 'unique': 'Check', 'ignore_user_permissions': 'Check', 'in_filter': 'Check', 'in_list_view': 'Check', 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 87375bd7fb..719d4ab76e 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -68,6 +68,13 @@ "search_index": 0, "width": "50px" }, + { + "fieldname": "unique", + "fieldtype": "Check", + "label": "Unique", + "permlevel": 0, + "precision": "" + }, { "fieldname": "in_list_view", "fieldtype": "Check", @@ -278,7 +285,7 @@ "idx": 1, "issingle": 0, "istable": 1, - "modified": "2014-09-05 07:41:29.641455", + "modified": "2014-09-26 05:06:37.372435", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/data/Framework.sql b/frappe/data/Framework.sql index 5a2c1cd82d..1e838e0f4b 100644 --- a/frappe/data/Framework.sql +++ b/frappe/data/Framework.sql @@ -30,6 +30,7 @@ CREATE TABLE `tabDocField` ( `print_hide` int(1) DEFAULT NULL, `report_hide` int(1) DEFAULT NULL, `reqd` int(1) DEFAULT NULL, + `unique` int(1) DEFAULT NULL, `no_copy` int(1) DEFAULT NULL, `allow_on_submit` int(1) DEFAULT NULL, `trigger` varchar(255) DEFAULT NULL, diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index f6d3af095f..9044d2db2a 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -7,9 +7,13 @@ import frappe import json -no_value_fields = ['Section Break', 'Column Break', 'HTML', 'Table', 'Button', 'Image', 'Fold'] -default_fields = ['doctype','name','owner','creation','modified','modified_by','parent','parentfield','parenttype','idx','docstatus'] -integer_docfield_properties = ["reqd", "search_index", "in_list_view", "permlevel", "hidden", "read_only", "ignore_user_permissions", "allow_on_submit", "report_hide", "in_filter", "no_copy", "print_hide"] +no_value_fields = ['Section Break', 'Column Break', 'HTML', 'Table', 'Button', + 'Image', 'Fold'] +default_fields = ['doctype','name','owner','creation','modified','modified_by', + 'parent','parentfield','parenttype','idx','docstatus'] +integer_docfield_properties = ["reqd", "search_index", "in_list_view", "permlevel", + "hidden", "read_only", "ignore_user_permissions", "allow_on_submit", "report_hide", + "in_filter", "no_copy", "print_hide", "unique"] def insert(doclist): if not isinstance(doclist, list): diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index c8c264354a..7e055e5887 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -217,11 +217,20 @@ class BaseDocument(object): d = self.get_valid_dict() columns = d.keys() - frappe.db.sql("""update `tab{doctype}` - set {values} where name=%s""".format( - doctype = self.doctype, - values = ", ".join(["`"+c+"`=%s" for c in columns]) - ), d.values() + [d.get("name")]) + try: + frappe.db.sql("""update `tab{doctype}` + set {values} where name=%s""".format( + doctype = self.doctype, + values = ", ".join(["`"+c+"`=%s" for c in columns]) + ), d.values() + [d.get("name")]) + except Exception, e: + if e.args[0]==1062: + type, value, traceback = sys.exc_info() + fieldname = str(e).split("'")[-2] + frappe.msgprint(_("{0} must be unique".format(self.meta.get_label(fieldname)))) + raise frappe.ValidationError, (self.doctype, self.name, e), traceback + else: + raise def db_set(self, fieldname, value): self.set(fieldname, value) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 6658a792bf..50cd7943fc 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -223,10 +223,13 @@ class DatabaseQuery(object): if not isinstance(f, (list, tuple)): frappe.throw("Filter must be a tuple or list (in a list)") - if len(f) != 4: + if len(f) == 3: + f = (self.doctype, f[0], f[1], f[2]) + + elif len(f) != 4: frappe.throw("Filter must have 4 values (doctype, fieldname, condition, value): " + str(f)) - return f + return list(f) def build_match_conditions(self, as_condition=True): """add match conditions if applicable""" diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index 7f2a013d8c..9c5154c008 100644 --- a/frappe/model/db_schema.py +++ b/frappe/model/db_schema.py @@ -36,14 +36,27 @@ type_map = { ,'Attach': ('varchar', '255') } -default_columns = ['name', 'creation', 'modified', 'modified_by', 'owner', 'docstatus', 'parent',\ - 'parentfield', 'parenttype', 'idx'] +default_columns = ['name', 'creation', 'modified', 'modified_by', 'owner', + 'docstatus', 'parent', 'parentfield', 'parenttype', 'idx'] default_shortcuts = ['_Login', '__user', '_Full Name', 'Today', '__today'] -# ------------------------------------------------- -# Class database table -# ------------------------------------------------- +def updatedb(dt): + """ + Syncs a `DocType` to the table + * creates if required + * updates columns + * updates indices + """ + res = frappe.db.sql("select ifnull(issingle, 0) from tabDocType where name=%s", (dt,)) + if not res: + raise Exception, 'Wrong doctype "%s" in updatedb' % dt + + if not res[0][0]: + frappe.db.commit() + tab = DbTable(dt, 'tab') + tab.sync() + frappe.db.begin() class DbTable: def __init__(self, doctype, prefix = 'tab'): @@ -55,13 +68,21 @@ class DbTable: # lists for change self.add_column = [] self.change_type = [] + self.add_index = [] self.drop_index = [] + self.set_default = [] # load self.get_columns_from_docfields() + def sync(self): + if not self.name in DbManager(frappe.db).get_tables_list(frappe.db.cur_db_name): + self.create() + else: + self.alter() + def create(self): add_text = '' @@ -89,31 +110,6 @@ class DbTable: ENGINE=InnoDB CHARACTER SET=utf8""" % (self.name, add_text)) - def get_columns_from_docfields(self): - """ - get columns from docfields and custom fields - """ - fl = frappe.db.sql("SELECT * FROM tabDocField WHERE parent = %s", self.doctype, as_dict = 1) - - try: - 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 - - 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')) - - def get_columns_from_db(self): - self.show_columns = frappe.db.sql("desc `%s`" % self.name) - for c in self.show_columns: - self.current_columns[c[0]] = {'name': c[0], 'type':c[1], 'index':c[3], 'default':c[4]} - def get_column_definitions(self): column_list = [] + default_columns ret = [] @@ -133,6 +129,31 @@ class DbTable: ret.append('index `' + key + '`(`' + key + '`)') return ret + def get_columns_from_docfields(self): + """ + get columns from docfields and custom fields + """ + fl = frappe.db.sql("SELECT * FROM tabDocField WHERE parent = %s", self.doctype, as_dict = 1) + + try: + 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 + + 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')) + + def get_columns_from_db(self): + self.show_columns = frappe.db.sql("desc `%s`" % self.name) + for c in self.show_columns: + self.current_columns[c[0]] = {'name': c[0], + 'type':c[1], 'index':c[3]=="MUL", 'default':c[4], "unique":c[3]=="UNI"} # GET foreign keys def get_foreign_keys(self): @@ -165,16 +186,10 @@ class DbTable: frappe.db.sql("alter table `%s` drop foreign key `%s`" % (self.name, fk_dict[col.fieldname])) frappe.db.sql("set foreign_key_checks=1") - def sync(self): - if not self.name in DbManager(frappe.db).get_tables_list(frappe.db.cur_db_name): - self.create() - else: - self.alter() - def alter(self): self.get_columns_from_db() for col in self.columns.values(): - col.check(self.current_columns.get(col.fieldname, None)) + col.build_for_alter_table(self.current_columns.get(col.fieldname, None)) query = [] @@ -193,11 +208,12 @@ class DbTable: for col in self.drop_index: if col.fieldname != 'name': # primary key # if index key exists - if frappe.db.sql("show index from `%s` where key_name = %s" % - (self.name, '%s'), col.fieldname): + if frappe.db.sql("""show index from `{0}` + where key_name=%s + and Non_unique=%s""".format(self.name), (col.fieldname, 1 if col.unique else 0)): query.append("drop index `{}`".format(col.fieldname)) - for col in list(set(self.set_default).difference(set(self.change_type))): + for col in self.set_default: if col.fieldname=="name": continue @@ -212,7 +228,8 @@ class DbTable: frappe.db.sql("alter table `{}` {}".format(self.name, ", ".join(query))) class DbColumn: - def __init__(self, table, fieldname, fieldtype, length, default, set_index, options): + def __init__(self, table, fieldname, fieldtype, length, default, + set_index, options, unique): self.table = table self.fieldname = fieldname self.fieldtype = fieldtype @@ -220,18 +237,22 @@ class DbColumn: self.set_index = set_index self.default = default self.options = options + self.unique = unique - def get_definition(self, with_default=1): - ret = get_definition(self.fieldtype) + def get_definition(self): + column_def = get_definition(self.fieldtype) - if with_default and self.default and (self.default not in default_shortcuts) \ - and not self.default.startswith(":") and ret not in ['text', 'longtext']: - ret += ' default "' + self.default.replace('"', '\"') + '"' + if self.default and (self.default not in default_shortcuts) \ + and not self.default.startswith(":") and column_def not in ['text', 'longtext']: + column_def += ' default "' + self.default.replace('"', '\"') + '"' - return ret + if self.unique: + column_def += ' unique' - def check(self, current_def): - column_def = self.get_definition(0) + return column_def + + def build_for_alter_table(self, current_def): + column_def = get_definition(self.fieldtype) # no columns if not column_def: @@ -244,20 +265,27 @@ class DbColumn: return # type - if current_def['type'] != column_def: + if (current_def['type'] != column_def) or (self.unique and not current_def['unique']): self.table.change_type.append(self) - # index else: + # index if (current_def['index'] and not self.set_index): self.table.drop_index.append(self) + if (current_def['unique'] and not self.unique): + self.table.drop_index.append(self) + if (not current_def['index'] and self.set_index and not (column_def in ['text', 'longtext'])): self.table.add_index.append(self) - # default - if (self.default_changed(current_def) and (self.default not in default_shortcuts) and not cstr(self.default).startswith(":") and not (column_def in ['text','longtext'])): - self.table.set_default.append(self) + # default + if (self.default_changed(current_def) \ + and (self.default not in default_shortcuts) \ + and not cstr(self.default).startswith(":") \ + and not (column_def in ['text','longtext'])): + self.table.set_default.append(self) + def default_changed(self, current_def): @@ -373,22 +401,6 @@ def validate_column_name(n): return n -def updatedb(dt): - """ - Syncs a `DocType` to the table - * creates if required - * updates columns - * updates indices - """ - res = frappe.db.sql("select ifnull(issingle, 0) from tabDocType where name=%s", (dt,)) - if not res: - raise Exception, 'Wrong doctype "%s" in updatedb' % dt - - if not res[0][0]: - frappe.db.commit() - tab = DbTable(dt, 'tab') - tab.sync() - frappe.db.begin() def remove_all_foreign_keys(): frappe.db.sql("set foreign_key_checks = 0") @@ -417,8 +429,8 @@ def get_definition(fieldtype): ret += '(' + d[1] + ')' return ret - def add_column(doctype, column_name, fieldtype): + """Add a column to the database""" frappe.db.commit() frappe.db.sql("alter table `tab%s` add column %s %s" % (doctype, column_name, get_definition(fieldtype))) diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index 1676b01da7..7c96c5f0f9 100644 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -190,7 +190,12 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ me.disp_area && $(me.disp_area).toggle(false); $(me.input_area).toggle(true); $(me.input_area).find("input").prop("disabled", false); - !me.has_input && me.make_input(); + if(!me.has_input) { + me.make_input(); + if(me.df.on_make) { + me.df.on_make(me); + } + }; if(me.doctype && me.docname) me.set_input(me.value); } else { diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 050bfcbcf7..208d22e0e1 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -508,7 +508,7 @@ frappe.ui.form.GridRow = Class.extend({