diff --git a/frappe/core/doctype/custom_field/custom_field.json b/frappe/core/doctype/custom_field/custom_field.json index 712ba856f7..e84810e693 100644 --- a/frappe/core/doctype/custom_field/custom_field.json +++ b/frappe/core/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.076820", + "modified": "2014-09-26 05:04:49.148736", "modified_by": "Administrator", "module": "Core", "name": "Custom Field", diff --git a/frappe/core/doctype/customize_form/customize_form.py b/frappe/core/doctype/customize_form/customize_form.py index 04d74eb287..5be88baffc 100644 --- a/frappe/core/doctype/customize_form/customize_form.py +++ b/frappe/core/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/core/doctype/customize_form_field/customize_form_field.json b/frappe/core/doctype/customize_form_field/customize_form_field.json index 0c540044bc..80534f2df5 100644 --- a/frappe/core/doctype/customize_form_field/customize_form_field.json +++ b/frappe/core/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.641454", + "modified": "2014-09-26 05:06:37.372434", "modified_by": "Administrator", "module": "Core", "name": "Customize Form Field", 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/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/db_schema.py b/frappe/model/db_schema.py index 7f2a013d8c..18cc9310f6 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), debug=1): 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)))