From f20da41bf6c95180c7ef599543ea128562880976 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 23 Feb 2018 11:10:21 +0530 Subject: [PATCH 1/3] [fix] rename custom fields before sync if name conflicts (#5062) * [fix] rename custom fields before sync if name conflicts * [minor] re-add text_type * Update doctype.py --- frappe/core/doctype/doctype/doctype.py | 26 ++++++++++++++++++- frappe/database.py | 10 ++++--- .../desk/doctype/desktop_icon/desktop_icon.py | 5 +++- frappe/utils/help.py | 10 ++++--- 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index d2d8d8a621..ac40bce2f3 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -15,7 +15,7 @@ from frappe.model.document import Document from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.desk.notifications import delete_notification_count_for from frappe.modules import make_boilerplate -from frappe.model.db_schema import validate_column_name, validate_column_length +from frappe.model.db_schema import validate_column_name, validate_column_length, type_map import frappe.website.render # imports - third-party imports @@ -205,6 +205,8 @@ class DocType(Document): def on_update(self): """Update database schema, make controller templates if `custom` is not set and clear cache.""" from frappe.model.db_schema import updatedb + self.rename_custom_fields() + updatedb(self.name, self) self.change_modified_of_parent() @@ -236,6 +238,28 @@ class DocType(Document): if self.name in frappe.local.meta_cache: del frappe.local.meta_cache[self.name] + def rename_custom_fields(self): + if not (frappe.db.table_exists(self.name) and frappe.db.table_exists("Custom Field")): + return + fields = [d.fieldname for d in self.fields if d.fieldtype in type_map] + custom_field_conflict = frappe.db.sql('''select name, fieldname from + `tabCustom Field` + where + fieldname in ({0})'''.format(', '.join(['%s'] * len(fields))), fields, as_dict=True) + + for custom_field in custom_field_conflict: + if frappe.db.has_column(self.name, custom_field.fieldname): + frappe.db.commit() + column_type = frappe.db.get_column_type(self.name, custom_field.fieldname) + + if not frappe.db.has_column(self.name, custom_field.fieldname + '_custom'): + frappe.db.sql('alter table `tab{0}` change `{1}` `{1}_custom` {2}'.format(self.name, + custom_field.fieldname, column_type)) + + # rename in custom field + frappe.db.set_value('Custom Field', custom_field.name, 'fieldname', custom_field.fieldname + '_custom') + + def sync_global_search(self): '''If global search settings are changed, rebuild search properties for this table''' global_search_fields_before_update = [d.fieldname for d in diff --git a/frappe/database.py b/frappe/database.py index 990ddf37be..bc0697ab9e 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -776,9 +776,9 @@ class Database: """Return true of field exists.""" return self.sql("select name from tabDocField where fieldname=%s and parent=%s", (dt, fn)) - def table_exists(self, tablename): - """Returns True if table exists.""" - return ("tab" + tablename) in self.get_tables() + def table_exists(self, doctype): + """Returns True if table for given doctype exists.""" + return ("tab" + doctype) in self.get_tables() def get_tables(self): return [d[0] for d in self.sql("show tables")] @@ -842,6 +842,10 @@ class Database: """Returns True if column exists in database.""" return column in self.get_table_columns(doctype) + def get_column_type(self, doctype, column): + return frappe.db.sql('''SELECT column_type FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'tab{0}' AND COLUMN_NAME = "{1}"'''.format(doctype, column))[0][0] + def add_index(self, doctype, fields, index_name=None): """Creates an index with given fields if not already created. Index name will be `fieldname1_fieldname2_index`""" diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.py b/frappe/desk/doctype/desktop_icon/desktop_icon.py index 6d5e2d6ab5..1874e0725c 100644 --- a/frappe/desk/doctype/desktop_icon/desktop_icon.py +++ b/frappe/desk/doctype/desktop_icon/desktop_icon.py @@ -352,7 +352,10 @@ def sync_from_app(app): m['_doctype'] = m.pop('doctype') desktop_icon.update(m) - desktop_icon.save() + try: + desktop_icon.save() + except frappe.exceptions.UniqueValidationError: + pass return modules_list diff --git a/frappe/utils/help.py b/frappe/utils/help.py index 45971820c0..c76e977dd8 100644 --- a/frappe/utils/help.py +++ b/frappe/utils/help.py @@ -15,6 +15,8 @@ from bs4 import BeautifulSoup import jinja2.exceptions from six import text_type +import io + def sync(): # make table print('Syncing help database...') @@ -55,7 +57,7 @@ class HelpDatabase(object): self.global_help_setup = frappe.conf.get('global_help_setup') if self.global_help_setup: bench_name = os.path.basename(os.path.abspath(frappe.get_app_path('frappe')).split('/apps/')[0]) - self.help_db_name = hashlib.sha224(bench_name).hexdigest()[:15] + self.help_db_name = hashlib.sha224(bench_name.encode('utf-8')).hexdigest()[:15] def make_database(self): '''make database for global help setup''' @@ -135,9 +137,9 @@ class HelpDatabase(object): for fname in files: if fname.rsplit('.', 1)[-1] in ('md', 'html'): fpath = os.path.join(basepath, fname) - with open(fpath, 'r') as f: + with io.open(fpath, 'r', encoding = 'utf-8') as f: try: - content = frappe.render_template(text_type(f.read(), 'utf-8'), + content = frappe.render_template(f.read(), {'docs_base_url': '/assets/{app}_docs'.format(app=app)}) relpath = self.get_out_path(fpath) @@ -235,7 +237,7 @@ class HelpDatabase(object): # files not in index.txt for f in os.listdir(path): - if not os.path.isdir(os.path.join(path, f)): + if not os.path.isdir(os.path.join(path, f)) and len(f.rsplit('.', 1)) == 2: name, extn = f.rsplit('.', 1) if name not in files \ and name != 'index' and extn in ('md', 'html'): From f07a9cf17ffa70a16b4e734a8fccbb1739c718a1 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 23 Feb 2018 16:16:49 +0530 Subject: [PATCH 2/3] Delete duplicate custom fields (#5070) * Delete duplicate custom fields * Delete duplicate custom fields --- frappe/core/doctype/doctype/doctype.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index ac40bce2f3..b82b012e23 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -205,8 +205,7 @@ class DocType(Document): def on_update(self): """Update database schema, make controller templates if `custom` is not set and clear cache.""" from frappe.model.db_schema import updatedb - self.rename_custom_fields() - + self.delete_duplicate_custom_fields() updatedb(self.name, self) self.change_modified_of_parent() @@ -238,27 +237,16 @@ class DocType(Document): if self.name in frappe.local.meta_cache: del frappe.local.meta_cache[self.name] - def rename_custom_fields(self): + def delete_duplicate_custom_fields(self): if not (frappe.db.table_exists(self.name) and frappe.db.table_exists("Custom Field")): return fields = [d.fieldname for d in self.fields if d.fieldtype in type_map] - custom_field_conflict = frappe.db.sql('''select name, fieldname from + + frappe.db.sql('''delete from `tabCustom Field` where - fieldname in ({0})'''.format(', '.join(['%s'] * len(fields))), fields, as_dict=True) - - for custom_field in custom_field_conflict: - if frappe.db.has_column(self.name, custom_field.fieldname): - frappe.db.commit() - column_type = frappe.db.get_column_type(self.name, custom_field.fieldname) - - if not frappe.db.has_column(self.name, custom_field.fieldname + '_custom'): - frappe.db.sql('alter table `tab{0}` change `{1}` `{1}_custom` {2}'.format(self.name, - custom_field.fieldname, column_type)) - - # rename in custom field - frappe.db.set_value('Custom Field', custom_field.name, 'fieldname', custom_field.fieldname + '_custom') - + dt = {0} and fieldname in ({1}) + '''.format('%s', ', '.join(['%s'] * len(fields))), tuple([self.name] + fields), as_dict=True) def sync_global_search(self): '''If global search settings are changed, rebuild search properties for this table''' @@ -415,6 +403,8 @@ class DocType(Document): frappe.throw(_("DocType's name should start with a letter and it can only consist of letters, numbers, spaces and underscores"), frappe.NameError) def validate_fields_for_doctype(doctype): + doc = frappe.get_doc("DocType", doctype) + doc.delete_duplicate_custom_fields() validate_fields(frappe.get_meta(doctype, cached=False)) # this is separate because it is also called via custom field From 4e5d6df2dc3fda7ffdf46d0ebebf78f1f023bdb6 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 23 Feb 2018 17:26:42 +0600 Subject: [PATCH 3/3] bumped to version 10.1.1 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index bf164efcde..f18a794e7c 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json from .exceptions import * from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template -__version__ = '10.1.0' +__version__ = '10.1.1' __title__ = "Frappe Framework" local = Local()