From 4c483f0015d46feecc2836874885e92926b96906 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Tue, 26 Mar 2019 09:51:10 +0000 Subject: [PATCH 001/176] fix: Google calendar connexion + data migration tool --- .../connectors/calendar_connector.py | 34 ++++++------- .../data_migration_run/data_migration_run.py | 30 ++++++------ .../event_to_gcalendar.json | 49 ++++++++++++++++++- .../gcalendar_to_event/__init__.py | 39 ++++++++------- .../gcalendar_to_event.json | 49 ++++++++++++++++++- .../gcalendar_sync/gcalendar_sync.json | 6 +-- 6 files changed, 150 insertions(+), 57 deletions(-) diff --git a/frappe/data_migration/doctype/data_migration_connector/connectors/calendar_connector.py b/frappe/data_migration/doctype/data_migration_connector/connectors/calendar_connector.py index ec62c69e72..578ac0fc37 100644 --- a/frappe/data_migration/doctype/data_migration_connector/connectors/calendar_connector.py +++ b/frappe/data_migration/doctype/data_migration_connector/connectors/calendar_connector.py @@ -7,6 +7,7 @@ from googleapiclient.errors import HttpError import time from datetime import datetime from frappe.utils import add_days, add_years +from frappe.desk.doctype.event.event import has_permission class CalendarConnector(BaseConnection): def __init__(self, connector): @@ -64,24 +65,21 @@ class CalendarConnector(BaseConnection): def insert(self, doctype, doc): if doctype == 'Events': - from frappe.desk.doctype.event.event import has_permission d = frappe.get_doc("Event", doc["name"]) if has_permission(d, self.account.name): - if doc["start_datetime"] >= datetime.now(): - try: - doctype = "Event" - e = self.insert_events(doctype, doc) - return e - except Exception: - frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") + try: + doctype = "Event" + e = self.insert_events(doctype, doc) + return e + except Exception: + frappe.log_error(frappe.get_traceback(), "GCalendar Synchronization Error") def update(self, doctype, doc, migration_id): if doctype == 'Events': - from frappe.desk.doctype.event.event import has_permission d = frappe.get_doc("Event", doc["name"]) if has_permission(d, self.account.name): - if doc["start_datetime"] >= datetime.now() and migration_id is not None: + if migration_id is not None: try: doctype = "Event" return self.update_events(doctype, doc, migration_id) @@ -217,23 +215,23 @@ class CalendarConnector(BaseConnection): day = [] if e.repeat_on == "Every Day": - if e.monday is not None: + if e.monday == 1: day.append("MO") - if e.tuesday is not None: + if e.tuesday == 1: day.append("TU") - if e.wednesday is not None: + if e.wednesday == 1: day.append("WE") - if e.thursday is not None: + if e.thursday == 1: day.append("TH") - if e.friday is not None: + if e.friday == 1: day.append("FR") - if e.saturday is not None: + if e.saturday == 1: day.append("SA") - if e.sunday is not None: + if e.sunday == 1: day.append("SU") day = "BYDAY=" + ",".join(str(d) for d in day) - frequency = "FREQ=DAILY" + frequency = "FREQ=WEEKLY" elif e.repeat_on == "Every Week": frequency = "FREQ=WEEKLY" diff --git a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py index 55e7dbe818..fb91ac551c 100644 --- a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py +++ b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, json, math from frappe.model.document import Document from frappe import _ -from frappe.utils import get_source_value +from frappe.utils import get_source_value, cstr class DataMigrationRun(Document): def run(self): @@ -213,19 +213,19 @@ class DataMigrationRun(Document): def get_deleted_local_data(self): '''Fetch local deleted data using `frappe.get_all`. Used during Push''' mapping = self.get_mapping(self.current_mapping) - or_filters = self.get_or_filters(mapping) - filters = dict( - deleted_doctype=mapping.local_doctype - ) + filters = self.get_last_modified_condition() + filters.update({ + "deleted_doctype": mapping.local_doctype + }) - data = frappe.get_all('Deleted Document', fields=['data'], - filters=filters, or_filters=or_filters) + data = frappe.get_all('Deleted Document', fields=['name', 'data'], + filters=filters) _data = [] for d in data: doc = json.loads(d.data) if doc.get(mapping.migration_id_field): - doc['_deleted_document_name'] = d.name + doc['_deleted_document_name'] = d["name"] _data.append(doc) return _data @@ -306,8 +306,8 @@ class DataMigrationRun(Document): self.update_log('push_insert', 1) # post process after insert self.post_process_doc(local_doc=d, remote_doc=response_doc) - except Exception: - self.update_log('push_failed', d.name) + except Exception as e: + self.update_log('push_failed', {d.name: cstr(e)}) # update page_start self.db_set('current_mapping_start', @@ -338,8 +338,8 @@ class DataMigrationRun(Document): self.update_log('push_update', 1) # post process after update self.post_process_doc(local_doc=d, remote_doc=response_doc) - except Exception: - self.update_log('push_failed', d.name) + except Exception as e: + self.update_log('push_failed', {d.name: cstr(e)}) # update page_start self.db_set('current_mapping_start', @@ -370,8 +370,8 @@ class DataMigrationRun(Document): self.update_log('push_delete', 1) # post process only when action is success self.post_process_doc(local_doc=d, remote_doc=response_doc) - except Exception: - self.update_log('push_failed', d.name) + except Exception as e: + self.update_log('push_failed', {d.name: cstr(e)}) # update page_start self.db_set('current_mapping_start', @@ -414,7 +414,7 @@ class DataMigrationRun(Document): self.post_process_doc(remote_doc=d, local_doc=local_doc) except Exception: # failed, append to log - self.update_log('pull_failed', migration_id_value) + self.update_log('push_failed', {migration_id_value: cstr(e)}) if len(data) < mapping.page_length: # last page, done with pull diff --git a/frappe/integrations/data_migration_mapping/event_to_gcalendar/event_to_gcalendar.json b/frappe/integrations/data_migration_mapping/event_to_gcalendar/event_to_gcalendar.json index b940237c8d..6d2a6020a5 100644 --- a/frappe/integrations/data_migration_mapping/event_to_gcalendar/event_to_gcalendar.json +++ b/frappe/integrations/data_migration_mapping/event_to_gcalendar/event_to_gcalendar.json @@ -33,6 +33,51 @@ "local_fieldname": "repeat_this_event", "remote_fieldname": "repeat_this_event" }, + { + "is_child_table": 0, + "local_fieldname": "repeat_on", + "remote_fieldname": "repeat_on" + }, + { + "is_child_table": 0, + "local_fieldname": "repeat_till", + "remote_fieldname": "repeat_till" + }, + { + "is_child_table": 0, + "local_fieldname": "monday", + "remote_fieldname": "monday" + }, + { + "is_child_table": 0, + "local_fieldname": "tuesday", + "remote_fieldname": "tuesday" + }, + { + "is_child_table": 0, + "local_fieldname": "wednesday", + "remote_fieldname": "wednesday" + }, + { + "is_child_table": 0, + "local_fieldname": "thursday", + "remote_fieldname": "thursday" + }, + { + "is_child_table": 0, + "local_fieldname": "friday", + "remote_fieldname": "friday" + }, + { + "is_child_table": 0, + "local_fieldname": "saturday", + "remote_fieldname": "saturday" + }, + { + "is_child_table": 0, + "local_fieldname": "sunday", + "remote_fieldname": "sunday" + }, { "is_child_table": 0, "local_fieldname": "name", @@ -45,8 +90,8 @@ "mapping_name": "Event to GCalendar", "mapping_type": "Push", "migration_id_field": "gcalendar_sync_id", - "modified": "2018-05-18 14:38:43.658069", - "modified_by": "chdecultot@dokos.io", + "modified": "2019-03-26 10:16:45.400150", + "modified_by": "Administrator", "name": "Event to GCalendar", "owner": "Administrator", "page_length": 10, diff --git a/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py b/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py index 441d2c3797..5518871c97 100644 --- a/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py +++ b/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals import frappe -from datetime import datetime +from datetime import datetime, timedelta from dateutil.parser import parse from pytz import timezone from frappe.utils import add_days @@ -30,7 +30,7 @@ def pre_process(events): event_tz = events["start"]["timeZone"] else: event_tz = events["calendar_tz"] - start_dt= timezone(event_tz).localize(start_dt) + start_dt = timezone(event_tz).localize(start_dt) if end_dt.tzinfo is None or end_dt.tzinfo.utcoffset(end_dt) is None: if "timeZone" in events["end"]: @@ -79,32 +79,37 @@ def get_recurrence_event_fields_value(recur_rule, starts_on): else: repeat_on = "Every Year" elif "UNTIL" in _str: # get repeat till - date = datetime.strptime(_str.split("=")[1], "%Y%m%dT%H%M%SZ") + date = parse(_str.split("=")[1]) repeat_till = get_repeat_till_date(date) elif "COUNT" in _str: # get repeat till - date = datetime.strptime(starts_on, "%Y-%m-%d %H:%M:%S") + date = parse(starts_on) repeat_till = get_repeat_till_date(date, count=_str.split("=")[1], repeat_on=repeat_on) elif "BYDAY" in _str: days = _str.split("=")[1] - if repeat_on == "DAILY": - repeat_days.update({ - "sunday": 1 if "SU" in days else 0, - "monday": 1 if "MO" in days else 0, - "tuesday": 1 if "TU" in days else 0, - "wednesday": 1 if "WD" in days else 0, - "thursday": 1 if "TU" in days else 0, - "friday": 1 if "TU" in days else 0, - "saturday": 1 if "TU" in days else 0, - }) + repeat_days.update({ + "sunday": 1 if "SU" in days else 0, + "monday": 1 if "MO" in days else 0, + "tuesday": 1 if "TU" in days else 0, + "wednesday": 1 if "WD" in days else 0, + "thursday": 1 if "TH" in days else 0, + "friday": 1 if "FR" in days else 0, + "saturday": 1 if "SA" in days else 0, + }) + repeat_on = "Every Day" - return { + recurrence = { "repeat_on": repeat_on, "repeat_till": repeat_till, - "repeat_this_event": 1, - "repeat_days": repeat_days + "repeat_this_event": 1 } + if repeat_days: + recurrence.update(repeat_days) + + return recurrence + + def get_repeat_till_date(date, count=None, repeat_on=None): if count: if repeat_on == "Every Day": diff --git a/frappe/integrations/data_migration_mapping/gcalendar_to_event/gcalendar_to_event.json b/frappe/integrations/data_migration_mapping/gcalendar_to_event/gcalendar_to_event.json index ec5bfc5f00..a4ca740ce5 100644 --- a/frappe/integrations/data_migration_mapping/gcalendar_to_event/gcalendar_to_event.json +++ b/frappe/integrations/data_migration_mapping/gcalendar_to_event/gcalendar_to_event.json @@ -33,6 +33,51 @@ "local_fieldname": "repeat_this_event", "remote_fieldname": "repeat_this_event" }, + { + "is_child_table": 0, + "local_fieldname": "repeat_on", + "remote_fieldname": "repeat_on" + }, + { + "is_child_table": 0, + "local_fieldname": "repeat_till", + "remote_fieldname": "repeat_till" + }, + { + "is_child_table": 0, + "local_fieldname": "monday", + "remote_fieldname": "monday" + }, + { + "is_child_table": 0, + "local_fieldname": "tuesday", + "remote_fieldname": "tuesday" + }, + { + "is_child_table": 0, + "local_fieldname": "wednesday", + "remote_fieldname": "wednesday" + }, + { + "is_child_table": 0, + "local_fieldname": "thursday", + "remote_fieldname": "thursday" + }, + { + "is_child_table": 0, + "local_fieldname": "friday", + "remote_fieldname": "friday" + }, + { + "is_child_table": 0, + "local_fieldname": "saturday", + "remote_fieldname": "saturday" + }, + { + "is_child_table": 0, + "local_fieldname": "sunday", + "remote_fieldname": "sunday" + }, { "is_child_table": 0, "local_fieldname": "gcalendar_sync_id", @@ -50,8 +95,8 @@ "mapping_name": "GCalendar to Event", "mapping_type": "Pull", "migration_id_field": "gcalendar_sync_id", - "modified": "2018-05-18 14:38:43.694867", - "modified_by": "chdecultot@dokos.io", + "modified": "2019-03-26 10:16:45.426138", + "modified_by": "Administrator", "name": "GCalendar to Event", "owner": "Administrator", "page_length": 250, diff --git a/frappe/integrations/data_migration_plan/gcalendar_sync/gcalendar_sync.json b/frappe/integrations/data_migration_plan/gcalendar_sync/gcalendar_sync.json index 2fac63854f..9eef669203 100644 --- a/frappe/integrations/data_migration_plan/gcalendar_sync/gcalendar_sync.json +++ b/frappe/integrations/data_migration_plan/gcalendar_sync/gcalendar_sync.json @@ -2,7 +2,7 @@ "creation": "2018-03-23 19:10:23.715161", "docstatus": 0, "doctype": "Data Migration Plan", - "idx": 4, + "idx": 22, "mappings": [ { "enabled": 1, @@ -13,8 +13,8 @@ "mapping": "GCalendar to Event" } ], - "modified": "2018-05-18 14:38:43.559026", - "modified_by": "chdecultot@dokos.io", + "modified": "2019-03-26 10:16:45.369037", + "modified_by": "Administrator", "module": "Integrations", "name": "GCalendar Sync", "owner": "Administrator", From e8ff32edfcdc127fc38580dd277fbf3e5a2be1d6 Mon Sep 17 00:00:00 2001 From: Charles-Henri Decultot Date: Tue, 26 Mar 2019 10:57:20 +0000 Subject: [PATCH 002/176] Typo correction --- .../doctype/data_migration_run/data_migration_run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py index fb91ac551c..93c0d8254d 100644 --- a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py +++ b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py @@ -414,7 +414,7 @@ class DataMigrationRun(Document): self.post_process_doc(remote_doc=d, local_doc=local_doc) except Exception: # failed, append to log - self.update_log('push_failed', {migration_id_value: cstr(e)}) + self.update_log('pull_failed', {migration_id_value: cstr(e)}) if len(data) < mapping.page_length: # last page, done with pull From a8aba197b8d481907e3fe6db4522f1179132ef9c Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Wed, 10 Apr 2019 15:33:13 +0530 Subject: [PATCH 003/176] feat(site_sync): add scheduler event for site_sync via central agent --- frappe/hooks.py | 1 + frappe/limits.py | 10 +++++++++- frappe/utils/__init__.py | 5 ++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index 960badcbab..4b5390567a 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -156,6 +156,7 @@ scheduler_events = { "frappe.utils.error.collect_error_snapshots", "frappe.desk.page.backups.backups.delete_downloadable_backups", "frappe.limits.update_space_usage", + "frappe.limits.update_site_usage", "frappe.desk.doctype.auto_repeat.auto_repeat.make_auto_repeat_entry", ], "daily": [ diff --git a/frappe/limits.py b/frappe/limits.py index 0330db2567..7dac57e7a0 100755 --- a/frappe/limits.py +++ b/frappe/limits.py @@ -5,7 +5,8 @@ from frappe.utils import now_datetime, getdate, flt, cint, get_fullname from frappe.installer import update_site_config from frappe.utils.data import formatdate from frappe.utils.user import get_enabled_system_users, disable_users -import os, subprocess +from frappe.utils.__init__ import get_site_info +import os, subprocess, json from six.moves.urllib.parse import parse_qsl, urlsplit, urlunsplit, urlencode from six import string_types @@ -235,3 +236,10 @@ def get_database_size(): FROM information_schema.TABLES WHERE table_schema = %s GROUP BY table_schema''', db_name, as_dict=True) return flt(db_size[0].get('database_size'), 2) + +def update_site_usage(): + data = get_site_info() + # exists = os.path.isfile(frappe.get_site_path("site_data.json")) + with open(os.path.join(frappe.get_site_path(), 'site_data.json'), 'w') as outfile: + json.dump(data, outfile) + outfile.close() diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index d65819edcb..2803234fe0 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -541,6 +541,8 @@ def get_site_info(): system_settings = frappe.db.get_singles_dict('System Settings') space_usage = frappe._dict((frappe.local.conf.limits or {}).get('space_usage', {})) + kwargs = {"fields": ["user", "creation", "full_name"], "filters":{"Operation": "Login", "Status": "Success"}, "limit": "10"} + site_info = { 'installed_apps': get_installed_apps_info(), 'users': users, @@ -555,7 +557,8 @@ def get_site_info(): 'space_used': flt((space_usage.total or 0) / 1024.0, 2), 'database_size': space_usage.database_size, 'backup_size': space_usage.backup_size, - 'files_size': space_usage.files_size + 'files_size': space_usage.files_size, + 'last_logins': frappe.get_all("Activity Log", **kwargs) } # from other apps From becef09e0680786343c581d984e7de5dcb961d16 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 11 Apr 2019 14:53:48 +0530 Subject: [PATCH 004/176] fix(xlsx): Fix for handle failed html parse --- frappe/utils/xlsxutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/xlsxutils.py b/frappe/utils/xlsxutils.py index 9d9e35a023..89c541935d 100644 --- a/frappe/utils/xlsxutils.py +++ b/frappe/utils/xlsxutils.py @@ -66,7 +66,7 @@ def handle_html(data): value = obj.handle(h) except Exception: # unable to parse html, send it raw - return value + return data value = ", ".join(value.split(' \n')) value = " ".join(value.split('\n')) From 2f0201e8e15a3e2c72043713d6712fe1cd359853 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 11 Apr 2019 16:07:09 +0530 Subject: [PATCH 005/176] fix: In global search property from customize form is not working (#7261) --- frappe/custom/doctype/customize_form/customize_form.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 2423db45ec..a7b26f18c9 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -149,6 +149,7 @@ class CustomizeForm(Document): return self.flags.update_db = False + self.flags.rebuild_doctype_for_global_search = False self.set_property_setters() self.update_custom_fields() @@ -164,6 +165,10 @@ class CustomizeForm(Document): frappe.clear_cache(doctype=self.doc_type) self.fetch_to_customize() + if self.flags.rebuild_doctype_for_global_search: + frappe.enqueue('frappe.utils.global_search.rebuild_for_doctype', + now=True, doctype=self.doc_type) + def set_property_setters(self): meta = frappe.get_meta(self.doc_type) # doctype property setters @@ -224,6 +229,10 @@ class CustomizeForm(Document): frappe.msgprint(_("You can't set 'Translatable' for field {0}").format(df.label)) continue + elif (property == 'in_global_search' and + df.in_global_search != meta_df[0].get("in_global_search")): + self.flags.rebuild_doctype_for_global_search = True + self.make_property_setter(property=property, value=df.get(property), property_type=docfield_properties[property], fieldname=df.fieldname) From 7f4e300271111c44be3c8852594f6f99c850cbc5 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 11 Apr 2019 16:10:17 +0530 Subject: [PATCH 006/176] fix: set ignore mandatory true for create contact on creating user (#7255) --- frappe/core/doctype/user/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index aee12e6f54..7327c54bec 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -93,7 +93,7 @@ class User(Document): clear_notifications(user=self.name) frappe.clear_cache(user=self.name) self.send_password_notification(self.__new_password) - create_contact(self) + create_contact(self, ignore_mandatory=True) if self.name not in ('Administrator', 'Guest') and not self.user_image: frappe.enqueue('frappe.core.doctype.user.user.update_gravatar', name=self.name) From 39660d6bd1340b732f52ae79e573cd248803da5f Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Fri, 12 Apr 2019 01:43:18 +0530 Subject: [PATCH 007/176] fix: get_system_manager --- frappe/utils/user.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/frappe/utils/user.py b/frappe/utils/user.py index 9814b22de4..d449abc219 100755 --- a/frappe/utils/user.py +++ b/frappe/utils/user.py @@ -312,13 +312,15 @@ def disable_users(limits=None): return if limits.get('users'): - system_manager = get_system_managers(only_name=True)[-1] - + system_manager = get_system_managers(only_name=True) + user_list = ['Administrator', 'Guest'] + if system_manager: + user_list.append(system_manager[-1]) #exclude system manager from active user list - active_users = frappe.db.sql_list("""select name from tabUser - where name not in ('Administrator', 'Guest', %s) and user_type = 'System User' and enabled=1 - order by creation desc""", system_manager) - + # active_users = frappe.db.sql_list("""select name from tabUser + # where name not in ('Administrator', 'Guest', %s) and user_type = 'System User' and enabled=1 + # order by creation desc""", system_manager) + active_users = frappe.get_all("User", filters={"user_type":"System User", "enabled":1, "name": ["not in", user_list]}, fields=["name"]) user_limit = cint(limits.get('users')) - 1 if len(active_users) > user_limit: From b19025fec2eb33b263735cc16af40a7cb4b9562b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 12 Apr 2019 12:54:03 +0530 Subject: [PATCH 008/176] fix: Bulk assign to is not working --- frappe/public/js/frappe/form/footer/assign_to.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/footer/assign_to.js b/frappe/public/js/frappe/form/footer/assign_to.js index 15255b20de..0a5e8e91e9 100644 --- a/frappe/public/js/frappe/form/footer/assign_to.js +++ b/frappe/public/js/frappe/form/footer/assign_to.js @@ -149,7 +149,8 @@ frappe.ui.form.AssignToDialog = Class.extend({ { value: 'High', label: __('High') } ], // Pick up priority from the source document, if it exists and is available in ToDo - 'default': ["Low", "Medium", "High"].includes(opts.obj.frm.doc.priority) ? opts.obj.frm.doc.priority : 'Medium' + 'default': ["Low", "Medium", "High"].includes(opts.obj.frm && opts.obj.frm.doc.priority + ? opts.obj.frm.doc.priority : 'Medium') }, ], primary_action: function() { frappe.ui.add_assignment(opts, this) }, From 0d7da4bb3195e31a822164cc829f36782bb8cd4c Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sat, 13 Apr 2019 21:32:43 +0530 Subject: [PATCH 009/176] fix:Refresh report again if route options --- frappe/public/js/frappe/views/reports/query_report.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index de4bd03e75..6bc7b5e499 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -93,10 +93,16 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.toggle_nothing_to_show(true); return; } + if (this.report_name !== frappe.get_route()[1]) { // this.toggle_loading(true); // different report this.load_report(); + } + else if (frappe.route_options){ + // filters passed through routes + // so refresh report again + this.refresh_report(); } else { // same report // don't do anything to preserve state From 5f0f552d02002a3b1b443278bb9db215677f8a99 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 12 Apr 2019 16:17:25 +0530 Subject: [PATCH 010/176] feat: Allow user to save custom reports --- frappe/core/doctype/report/report.js | 30 +++++++ frappe/core/doctype/report/report.json | 42 +++++++-- frappe/desk/query_report.py | 90 +++++++++++++++++-- .../js/frappe/views/reports/query_report.js | 53 ++++++++++- 4 files changed, 201 insertions(+), 14 deletions(-) diff --git a/frappe/core/doctype/report/report.js b/frappe/core/doctype/report/report.js index 903a9cbac4..c464e772a1 100644 --- a/frappe/core/doctype/report/report.js +++ b/frappe/core/doctype/report/report.js @@ -58,6 +58,36 @@ frappe.ui.form.on('Report', { // make the document read-only frm.set_read_only(); } + + let doc = frm.doc; + frm.add_custom_button(__("Show Report"), function() { + switch(doc.report_type) { + case "Report Builder": + frappe.set_route('List', doc.ref_doctype, 'Report', doc.name); + break; + case "Query Report": + frappe.set_route("query-report", doc.name); + break; + case "Script Report": + frappe.set_route("query-report", doc.name); + break; + case "Custom Report": + frappe.set_route("query-report", doc.name); + break; + } + }, "fa fa-table"); + + if (doc.is_standard === "Yes") { + frm.add_custom_button(doc.disabled ? __("Enable Report") : __("Disable Report"), function() { + frm.call('toggle_disable', { + disable: doc.disabled ? 0 : 1 + }).then(() => { + frm.reload_doc(); + }); + }, doc.disabled ? "fa fa-check" : "fa fa-off"); + } + + frm.events.report_type(frm); }, ref_doctype: function(frm) { diff --git a/frappe/core/doctype/report/report.json b/frappe/core/doctype/report/report.json index a26bc3164f..8c30b8e328 100644 --- a/frappe/core/doctype/report/report.json +++ b/frappe/core/doctype/report/report.json @@ -79,6 +79,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "reference_report", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference Report", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -228,7 +261,7 @@ "label": "Report Type", "length": 0, "no_copy": 0, - "options": "Report Builder\nQuery Report\nScript Report", + "options": "Report Builder\nQuery Report\nScript Report\nCustom Report", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -479,7 +512,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:doc.report_type==\"Report Builder\"", + "depends_on": "eval:doc.report_type==\"Report Builder\" || \"Custom Report\"", "fetch_if_empty": 0, "fieldname": "json", "fieldtype": "Code", @@ -642,17 +675,15 @@ } ], "has_web_view": 0, - "hide_heading": 0, "hide_toolbar": 0, "icon": "", "idx": 1, - "image_view": 0, "in_create": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-04-07 20:32:30.943582", + "modified": "2019-04-12 15:53:14.194591", "modified_by": "Administrator", "module": "Core", "name": "Report", @@ -737,7 +768,6 @@ ], "quick_entry": 0, "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 5790c42878..9d783670a3 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -19,6 +19,15 @@ from frappe.utils.file_manager import get_file from frappe.utils import gzip_decompress def get_report_doc(report_name): + + custom_report = custom_columns = frappe.db.get_value("Report", + {'report_name':report_name, 'is_standard': 'No', 'report_type': 'Custom Report'}, + ['reference_report', 'json'] + ) + + if custom_report: + report_name, custom_columns = custom_report[0], custom_report[1] + doc = frappe.get_doc("Report", report_name) if not doc.is_permitted(): frappe.throw(_("You don't have access to Report: {0}").format(report_name), frappe.PermissionError) @@ -30,10 +39,10 @@ def get_report_doc(report_name): if doc.disabled: frappe.throw(_("Report {0} is disabled").format(report_name)) - return doc + return doc, custom_columns -def generate_report_result(report, filters=None, user=None): +def generate_report_result(report, filters=None, user=None, custom_columns=None): status = None if not user: user = frappe.session.user @@ -82,6 +91,11 @@ def generate_report_result(report, filters=None, user=None): if len(res) > 4: data_to_be_printed = res[4] + + if custom_columns: + columns = json.loads(custom_columns) + result = add_data_to_custom_columns(columns, result) + if result: result = get_filtered_data(report.ref_doctype, columns, result, user) @@ -103,7 +117,7 @@ def background_enqueue_run(report_name, filters=None, user=None): """run reports in background""" if not user: user = frappe.session.user - report = get_report_doc(report_name) + report, _ = get_report_doc(report_name) track_instance = \ frappe.get_doc({ "doctype": "Prepared Report", @@ -126,8 +140,7 @@ def background_enqueue_run(report_name, filters=None, user=None): @frappe.whitelist() def get_script(report_name): - report = get_report_doc(report_name) - + report, _ = get_report_doc(report_name) module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module") module_path = get_module_path(module) report_folder = os.path.join(module_path, "report", scrub(report.name)) @@ -162,7 +175,7 @@ def get_script(report_name): @frappe.read_only() def run(report_name, filters=None, user=None): - report = get_report_doc(report_name) + report, custom_columns = get_report_doc(report_name) if not user: user = frappe.session.user if not frappe.has_permission(report.ref_doctype, "report"): @@ -182,12 +195,37 @@ def run(report_name, filters=None, user=None): dn = "" result = get_prepared_report_result(report, filters, dn, user) else: - result = generate_report_result(report, filters, user) + result = generate_report_result(report, filters, user, custom_columns) result["add_total_row"] = report.add_total_row return result +def add_data_to_custom_columns(columns, result): + + custom_fields_data = get_data_for_custom_report(columns) + + data = [] + for row in result: + row_obj = {} + if isinstance(row, list): + for idx, column in enumerate(columns): + if column.get('link_field'): + row_obj[column['fieldname']] = None + row.insert(idx,None) + else: + row_obj[column['fieldname']] = row[idx] + data.append(row_obj) + else: + data.append(row) + + for row in data: + for column in columns: + if column.get('link_field'): + row[column['fieldname']] = \ + custom_fields_data.get((column['doctype'], column['fieldname']), {}).get(row[column['link_field']]) + + return data def get_prepared_report_result(report, filters, dn="", user=None): latest_report_data = {} @@ -362,6 +400,44 @@ def get_data_for_custom_field(doctype, field): return value_map +def get_data_for_custom_report(columns): + doc_field_value_map = {} + + for column in columns: + if column.get('link_field'): + fieldname = column.get('fieldname') + doctype = column.get('doctype') + doc_field_value_map[(doctype, fieldname)] = get_data_for_custom_field(doctype, fieldname) + + return doc_field_value_map + +@frappe.whitelist() +def save_report(reference_report, report_name, columns): + + report_doc, _ = get_report_doc(reference_report) + + docname = frappe.db.exists("Report", report_name) + if docname: + report = frappe.get_doc("Report", {'report_name':docname, 'is_standard': 'No', 'report_type': 'Custom Report'}) + report.update({"json": columns}) + report.save() + frappe.msgprint("Report updated successfully") + + return docname + else: + new_report = frappe.get_doc({ + 'doctype': 'Report', + 'report_name': report_name, + 'json': columns, + 'ref_doctype': report_doc.ref_doctype, + 'is_standard': 'No', + 'report_type': 'Custom Report', + 'reference_report': reference_report + }).insert(ignore_permissions = True) + frappe.msgprint("{0} saved successfully".format(new_report.name)) + return new_report.name + + def get_filtered_data(ref_doctype, columns, data, user): result = [] linked_doctypes = get_linked_doctypes(columns, data) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 6bc7b5e499..ce25e3848d 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -116,9 +116,21 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.page_name = frappe.get_route_str(); this.report_name = this.route[1]; this.page_title = __(this.report_name); + this.show_save = false; this.menu_items = this.get_menu_items(); this.datatable = null; this.prepared_report_action = "New"; + this.custom_report = null; + + frappe.db.get_value("Report", + {"report_name": this.report_name}, + 'reference_report', (r) => { + if (r.reference_report){ + this.custom_report = this.report_name; + this.report_name = r.reference_report; + } + } + ) frappe.run_serially([ () => this.get_report_doc(), @@ -295,7 +307,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { method: 'frappe.desk.query_report.run', type: 'GET', args: { - report_name: this.report_name, + report_name: this.custom_report || this.report_name, filters: filters, }, callback: resolve, @@ -1009,8 +1021,11 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { fieldname: df.fieldname, fieldtype: df.fieldtype, label: df.label, + link_field: this.doctype_field_map[values.doctype], + doctype: values.doctype, width: 100 }); + frappe.call({ method: 'frappe.desk.query_report.get_data_for_custom_field', args: { @@ -1024,6 +1039,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { d.hide(); } }); + this.show_save = true; + this.set_menu_items() } }) @@ -1031,6 +1048,40 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }, standard: true }, + { + label: __('Save'), + action: () => { + let d = new frappe.ui.Dialog({ + title: __('Save Reports'), + fields: [ + { + fieldtype: 'Data', + fieldname: 'report_name', + label: __("Report Name"), + default: this.report_doc.is_standard == 'No' ? this.custom_report : "", + } + ], + primary_action: (values) => { + frappe.call({ + method: "frappe.desk.query_report.save_report", + args: { + reference_report: this.report_name, + report_name: values.report_name, + columns: this.columns + }, + callback: function(r) { + this.show_save = false; + d.hide(); + frappe.set_route('query-report', r.message); + } + }); + } + }); + d.show() + }, + condition: () => this.show_save, + standard: true + }, { label: __('User Permissions'), action: () => frappe.set_route('List', 'User Permission', { From 6efb5c6ece2e30f18e459387c5632603498f2413 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 12 Apr 2019 16:49:28 +0530 Subject: [PATCH 011/176] fix: Styling and codacy fixes --- frappe/public/js/frappe/views/reports/query_report.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index ce25e3848d..78266855ae 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -130,7 +130,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.report_name = r.reference_report; } } - ) + ); frappe.run_serially([ () => this.get_report_doc(), @@ -1040,7 +1040,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } }); this.show_save = true; - this.set_menu_items() + this.set_menu_items(); } }) @@ -1077,7 +1077,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }); } }); - d.show() + d.show(); }, condition: () => this.show_save, standard: true From 9f293b5846ea3caa800844e05e66e240f3c4a552 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 15 Apr 2019 15:05:40 +0530 Subject: [PATCH 012/176] fix:Code cleanup and fixes --- frappe/desk/query_report.py | 47 ++++++++++--------- .../js/frappe/views/reports/query_report.js | 22 ++++----- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 9d783670a3..ed679a14ca 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -19,16 +19,17 @@ from frappe.utils.file_manager import get_file from frappe.utils import gzip_decompress def get_report_doc(report_name): - - custom_report = custom_columns = frappe.db.get_value("Report", - {'report_name':report_name, 'is_standard': 'No', 'report_type': 'Custom Report'}, - ['reference_report', 'json'] - ) - - if custom_report: - report_name, custom_columns = custom_report[0], custom_report[1] - doc = frappe.get_doc("Report", report_name) + doc.custom_columns = [] + + if doc.report_type == 'Custom Report': + custom_report_doc = doc + reference_report = custom_report_doc.reference_report + doc = frappe.get_doc("Report", reference_report) + doc.custom_report = report_name + doc.custom_columns = custom_report_doc.json + doc.is_custom_report = True + if not doc.is_permitted(): frappe.throw(_("You don't have access to Report: {0}").format(report_name), frappe.PermissionError) @@ -39,10 +40,10 @@ def get_report_doc(report_name): if doc.disabled: frappe.throw(_("Report {0} is disabled").format(report_name)) - return doc, custom_columns + return doc -def generate_report_result(report, filters=None, user=None, custom_columns=None): +def generate_report_result(report, filters=None, user=None): status = None if not user: user = frappe.session.user @@ -66,6 +67,7 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) else: module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module") if report.is_standard == "Yes": + # report_doc_name = method_name = get_report_module_dotted_path(module, report.name) + ".execute" threshold = 30 res = [] @@ -92,8 +94,8 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) data_to_be_printed = res[4] - if custom_columns: - columns = json.loads(custom_columns) + if report.custom_columns: + columns = json.loads(report.custom_columns) result = add_data_to_custom_columns(columns, result) if result: @@ -117,7 +119,7 @@ def background_enqueue_run(report_name, filters=None, user=None): """run reports in background""" if not user: user = frappe.session.user - report, _ = get_report_doc(report_name) + report = get_report_doc(report_name) track_instance = \ frappe.get_doc({ "doctype": "Prepared Report", @@ -140,8 +142,9 @@ def background_enqueue_run(report_name, filters=None, user=None): @frappe.whitelist() def get_script(report_name): - report, _ = get_report_doc(report_name) + report = get_report_doc(report_name) module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module") + # report_file_name = report.reference_report if report.is_custom_report else report.name module_path = get_module_path(module) report_folder = os.path.join(module_path, "report", scrub(report.name)) script_path = os.path.join(report_folder, scrub(report.name) + ".js") @@ -175,7 +178,7 @@ def get_script(report_name): @frappe.read_only() def run(report_name, filters=None, user=None): - report, custom_columns = get_report_doc(report_name) + report = get_report_doc(report_name) if not user: user = frappe.session.user if not frappe.has_permission(report.ref_doctype, "report"): @@ -195,7 +198,7 @@ def run(report_name, filters=None, user=None): dn = "" result = get_prepared_report_result(report, filters, dn, user) else: - result = generate_report_result(report, filters, user, custom_columns) + result = generate_report_result(report, filters, user) result["add_total_row"] = report.add_total_row @@ -222,8 +225,10 @@ def add_data_to_custom_columns(columns, result): for row in data: for column in columns: if column.get('link_field'): - row[column['fieldname']] = \ - custom_fields_data.get((column['doctype'], column['fieldname']), {}).get(row[column['link_field']]) + fieldname = column['fieldname'] + key = (column['doctype'], fieldname) + link_field = column['link_field'] + row[fieldname] = custom_fields_data.get(key, {}).get(row[link_field]) return data @@ -421,7 +426,7 @@ def save_report(reference_report, report_name, columns): report = frappe.get_doc("Report", {'report_name':docname, 'is_standard': 'No', 'report_type': 'Custom Report'}) report.update({"json": columns}) report.save() - frappe.msgprint("Report updated successfully") + frappe.msgprint(_("Report updated successfully")) return docname else: @@ -434,7 +439,7 @@ def save_report(reference_report, report_name, columns): 'report_type': 'Custom Report', 'reference_report': reference_report }).insert(ignore_permissions = True) - frappe.msgprint("{0} saved successfully".format(new_report.name)) + frappe.msgprint(_("{0} saved successfully".format(new_report.name))) return new_report.name diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 78266855ae..3827e5f7f7 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -122,15 +122,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.prepared_report_action = "New"; this.custom_report = null; - frappe.db.get_value("Report", - {"report_name": this.report_name}, - 'reference_report', (r) => { - if (r.reference_report){ - this.custom_report = this.report_name; - this.report_name = r.reference_report; - } - } - ); frappe.run_serially([ () => this.get_report_doc(), @@ -170,7 +161,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { get_report_settings() { if (frappe.query_reports[this.report_name]) { - this.report_settings = frappe.query_reports[this.report_name]; + this.report_settings = this.get_local_report_settings(); return this._load_script; } @@ -183,7 +174,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { return r; }).then(r => { return frappe.after_ajax(() => { - this.report_settings = frappe.query_reports[this.report_name]; + this.report_settings = this.get_local_report_settings(); this.report_settings.html_format = r.message.html_format; this.report_settings.execution_time = r.message.execution_time || 0; }); @@ -192,6 +183,13 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { return this._load_script; } + get_local_report_settings() { + let report_script_name = this.report_doc.report_type === 'Custom Report' + ? this.report_doc.reference_report + : this.report_name; + return frappe.query_reports[report_script_name]; + } + setup_progress_bar() { let seconds_elapsed = 0; const execution_time = this.report_settings.execution_time || 0; @@ -1052,7 +1050,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { label: __('Save'), action: () => { let d = new frappe.ui.Dialog({ - title: __('Save Reports'), + title: __('Save Report'), fields: [ { fieldtype: 'Data', From 64d049679c827cf4baebf464ae75f9907557ed6f Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 12 Apr 2019 23:33:20 +0530 Subject: [PATCH 013/176] fix: More cleanup --- frappe/desk/query_report.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index ed679a14ca..ce68437566 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -67,7 +67,6 @@ def generate_report_result(report, filters=None, user=None): else: module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module") if report.is_standard == "Yes": - # report_doc_name = method_name = get_report_module_dotted_path(module, report.name) + ".execute" threshold = 30 res = [] @@ -144,7 +143,6 @@ def background_enqueue_run(report_name, filters=None, user=None): def get_script(report_name): report = get_report_doc(report_name) module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module") - # report_file_name = report.reference_report if report.is_custom_report else report.name module_path = get_module_path(module) report_folder = os.path.join(module_path, "report", scrub(report.name)) script_path = os.path.join(report_folder, scrub(report.name) + ".js") @@ -178,7 +176,7 @@ def get_script(report_name): @frappe.read_only() def run(report_name, filters=None, user=None): - report = get_report_doc(report_name) + report = get_report_doc(report_name) if not user: user = frappe.session.user if not frappe.has_permission(report.ref_doctype, "report"): @@ -205,7 +203,6 @@ def run(report_name, filters=None, user=None): return result def add_data_to_custom_columns(columns, result): - custom_fields_data = get_data_for_custom_report(columns) data = [] @@ -215,7 +212,7 @@ def add_data_to_custom_columns(columns, result): for idx, column in enumerate(columns): if column.get('link_field'): row_obj[column['fieldname']] = None - row.insert(idx,None) + row.insert(idx, None) else: row_obj[column['fieldname']] = row[idx] data.append(row_obj) @@ -418,12 +415,11 @@ def get_data_for_custom_report(columns): @frappe.whitelist() def save_report(reference_report, report_name, columns): - - report_doc, _ = get_report_doc(reference_report) + report_doc = get_report_doc(reference_report) docname = frappe.db.exists("Report", report_name) if docname: - report = frappe.get_doc("Report", {'report_name':docname, 'is_standard': 'No', 'report_type': 'Custom Report'}) + report = frappe.get_doc("Report", {'report_name': docname, 'is_standard': 'No', 'report_type': 'Custom Report'}) report.update({"json": columns}) report.save() frappe.msgprint(_("Report updated successfully")) From b6e9e72dfe97cae1037e0c13b024fb3226b86003 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 15 Apr 2019 08:49:59 +0530 Subject: [PATCH 014/176] fix: Pass report name while saving custom report --- frappe/public/js/frappe/views/reports/query_report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 3827e5f7f7..e6204190ef 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1056,7 +1056,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { fieldtype: 'Data', fieldname: 'report_name', label: __("Report Name"), - default: this.report_doc.is_standard == 'No' ? this.custom_report : "", + default: this.report_doc.is_standard == 'No' ? this.report_name : "", } ], primary_action: (values) => { From 35dd2af7ae8628f2e1c65bdb3d9e2cdb2e315a92 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 15 Apr 2019 15:46:48 +0530 Subject: [PATCH 015/176] fix: Report name passing fix --- frappe/public/js/frappe/views/reports/query_report.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index e6204190ef..9b91b2a2b5 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -120,7 +120,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.menu_items = this.get_menu_items(); this.datatable = null; this.prepared_report_action = "New"; - this.custom_report = null; frappe.run_serially([ @@ -305,7 +304,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { method: 'frappe.desk.query_report.run', type: 'GET', args: { - report_name: this.custom_report || this.report_name, + report_name: this.report_name, filters: filters, }, callback: resolve, From 97173028931e4ecb769774bbad8f5491332c4f13 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 15 Apr 2019 16:24:56 +0530 Subject: [PATCH 016/176] fix: Invert check for print_format_type If print_format_type is not set, it does not show up. --- frappe/public/js/frappe/model/meta.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index 2d351137ef..e24c829dc5 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -199,7 +199,7 @@ $.extend(frappe.meta, { var print_formats = frappe.get_list("Print Format", {doc_type: doctype}) .sort(function(a, b) { return (a > b) ? 1 : -1; }); $.each(print_formats, function(i, d) { - if(!in_list(print_format_list, d.name) && in_list(['Server', 'Client'], d.print_format_type)) + if(!in_list(print_format_list, d.name) && d.print_format_type !== 'Js') print_format_list.push(d.name); }); From 7fe33574ffc615f31a4065e8bd56a5897dcd99d4 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 15 Apr 2019 16:32:23 +0530 Subject: [PATCH 017/176] fix: Show button for custom report --- frappe/core/doctype/report/report.js | 33 +++------------------------- 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/frappe/core/doctype/report/report.js b/frappe/core/doctype/report/report.js index c464e772a1..9afc247c06 100644 --- a/frappe/core/doctype/report/report.js +++ b/frappe/core/doctype/report/report.js @@ -30,6 +30,9 @@ cur_frm.cscript.refresh = function(doc) { case "Script Report": frappe.set_route("query-report", doc.name); break; + case "Custom Report": + frappe.set_route("query-report", doc.name); + break; } }, "fa fa-table"); @@ -58,36 +61,6 @@ frappe.ui.form.on('Report', { // make the document read-only frm.set_read_only(); } - - let doc = frm.doc; - frm.add_custom_button(__("Show Report"), function() { - switch(doc.report_type) { - case "Report Builder": - frappe.set_route('List', doc.ref_doctype, 'Report', doc.name); - break; - case "Query Report": - frappe.set_route("query-report", doc.name); - break; - case "Script Report": - frappe.set_route("query-report", doc.name); - break; - case "Custom Report": - frappe.set_route("query-report", doc.name); - break; - } - }, "fa fa-table"); - - if (doc.is_standard === "Yes") { - frm.add_custom_button(doc.disabled ? __("Enable Report") : __("Disable Report"), function() { - frm.call('toggle_disable', { - disable: doc.disabled ? 0 : 1 - }).then(() => { - frm.reload_doc(); - }); - }, doc.disabled ? "fa fa-check" : "fa fa-off"); - } - - frm.events.report_type(frm); }, ref_doctype: function(frm) { From e4e2a6c310f3024a1ca2b6292faa7c9ea172dd16 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 15 Apr 2019 14:38:09 +0530 Subject: [PATCH 018/176] fix: save button should not remain disabled when the system is offline --- frappe/public/js/frappe/request.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index 99b130ceb9..ce862fd25b 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -30,7 +30,8 @@ frappe.call = function(opts) { indicator: 'orange', message: __('You are not connected to Internet. Retry after sometime.') }, 3); - return; + opts.always && opts.always(); + return $.ajax(); } if (typeof arguments[0]==='string') { opts = { From c193a33d3c46f53900d22c82f5a4e255a3a2d19c Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 16 Apr 2019 09:29:29 +0530 Subject: [PATCH 019/176] fix: Do not rename or move files for custom doctype --- frappe/core/doctype/doctype/doctype.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 388e4d4ef8..499f64be53 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -329,7 +329,8 @@ class DocType(Document): if merge: frappe.throw(_("DocType can not be merged")) - if not frappe.flags.in_test and not frappe.flags.in_patch: + # Do not rename and move files and folders for custom doctype + if not self.custom and not frappe.flags.in_test and not frappe.flags.in_patch: self.rename_files_and_folders(old, new) def after_rename(self, old, new, merge=False): From 695a340d1878d7b41ca9dcd172e6777f061a3b78 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 16 Apr 2019 13:04:14 +0530 Subject: [PATCH 020/176] fix: Custom columns handling for prepared report --- frappe/core/doctype/prepared_report/prepared_report.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index 64c26f27d1..13273d50b9 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -34,6 +34,15 @@ class PreparedReport(Document): def run_background(instance): report = frappe.get_doc("Report", instance.ref_report_doctype) + + report.custom_columns = [] + + if report.report_type == 'Custom Report': + custom_report_doc = report + reference_report = custom_report_doc.reference_report + report = frappe.get_doc("Report", reference_report) + report.custom_columns = custom_report_doc.json + result = generate_report_result(report, filters=instance.filters, user=instance.owner) create_json_gz_file(result['result'], 'Prepared Report', instance.name) From 5558a42fa70e9a8c786efbf3a122407c4867f760 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 16 Apr 2019 13:32:09 +0530 Subject: [PATCH 021/176] fix: Show Hidden fields in Report Builder --- frappe/public/js/frappe/views/reports/report_view.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 9799a7e934..686483ed91 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -720,9 +720,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { let out = {}; const standard_fields_filter = df => - !in_list(frappe.model.no_value_type, df.fieldtype) && - !df.report_hide && df.fieldname !== 'naming_series' && - !df.hidden; + !in_list(frappe.model.no_value_type, df.fieldtype) && !df.report_hide; let doctype_fields = frappe.meta.get_docfields(this.doctype).filter(standard_fields_filter); From bc4e68176eba5af27c387611f5ab3472fd13affd Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 16 Apr 2019 14:02:17 +0600 Subject: [PATCH 022/176] bumped to version 11.1.22 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index fd308edf84..3ad1b5bc84 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -24,7 +24,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '11.1.21' +__version__ = '11.1.22' __title__ = "Frappe Framework" local = Local() From 93d518755dec557b17e78c4f1cf0622d4077215a Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 16 Apr 2019 13:47:35 +0530 Subject: [PATCH 023/176] fix: Allow numeric filter without using = --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 358a4920aa..c2f6aaec6f 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "cookie": "^0.3.1", "express": "^4.16.2", "fast-deep-equal": "^2.0.1", - "frappe-datatable": "^1.12.0", + "frappe-datatable": "^1.12.1", "frappe-gantt": "^0.1.0", "fuse.js": "^3.2.0", "highlight.js": "^9.12.0", diff --git a/yarn.lock b/yarn.lock index a3c928e858..b8fb29551f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1224,10 +1224,10 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -frappe-datatable@^1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.12.0.tgz#2273535ead4404e5b165b6564c622acbacfdf61e" - integrity sha512-rrsRaxP9+CwPdJiYzmgmYD5ud+0pWzon8n+DKBrnrbFheN5SFnbuRdR58G8qA4/psHIN3rrSEximiQsbTUNdzw== +frappe-datatable@^1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.12.1.tgz#6efe342346025ffeed822e188a05da6c9c9230c9" + integrity sha512-AC2sJDuJOr0nSfT+w7DPZd7zPW7PHGhg9XxdRNlaAYpy45r3owdJVF+R6lCMQRvFvvzmPMht02EOE6cFpkzLKw== dependencies: hyperlist "^1.0.0-beta" lodash "^4.17.5" From 700b4e3746b53042c49e972a6b302ee9d47b2b98 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 17 Apr 2019 18:10:50 +0500 Subject: [PATCH 024/176] feat: Use `field_order` attr in DocType JSON file for cleaner git diffs --- frappe/modules/export_file.py | 31 ++++++++++++++++++++++++++++++- frappe/modules/import_file.py | 21 +++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/frappe/modules/export_file.py b/frappe/modules/export_file.py index 66a6a9f6b7..2c6d28d7c2 100644 --- a/frappe/modules/export_file.py +++ b/frappe/modules/export_file.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe, os import frappe.model from frappe.modules import scrub, get_module_path, scrub_dt_dn +import json def export_doc(doc): export_to_files([[doc.doctype, doc.name]]) @@ -38,7 +39,35 @@ 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"), 'a+') as txtfile: + # if exporting DocType, retain order of 'fields' table and change order in 'field_order' + if doc.doctype == "DocType": + newdoc["field_order"] = [f.fieldname for f in doc.fields] + + try: + 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 doc.fields] + + for fieldname in old_field_names: + field_dict = filter(lambda d: d['fieldname'] == fieldname, newdoc['fields']) + if field_dict: + new_field_dicts.append(field_dict[0]) + remaining_field_names.remove(fieldname) + + for fieldname in remaining_field_names: + field_dict = filter(lambda d: d['fieldname'] == fieldname, newdoc['fields']) + new_field_dicts.append(field_dict[0]) + + newdoc['fields'] = new_field_dicts + except ValueError: + pass + + txtfile.seek(0) + txtfile.truncate() txtfile.write(frappe.as_json(newdoc)) def get_module_name(doc): diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index eadfcb3dd5..75bc20eedd 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -89,6 +89,27 @@ def read_doc_from_file(path): else: raise IOError('%s missing' % path) + # set order of fields from field_order + if doc.get("doctype") == "DocType": + if doc.get("field_order") and doc.get("fields"): + new_field_dicts = [] + remaining_field_names = [f['fieldname'] for f in doc['fields']] + + for fieldname in doc['field_order']: + field_dict = filter(lambda d: d['fieldname'] == fieldname, doc['fields']) + if field_dict: + new_field_dicts.append(field_dict[0]) + remaining_field_names.remove(fieldname) + + for fieldname in remaining_field_names: + field_dict = filter(lambda d: d['fieldname'] == fieldname, doc['fields']) + new_field_dicts.append(field_dict[0]) + + doc['fields'] = new_field_dicts + + if "field_order" in doc: + del doc['field_order'] + return doc ignore_doctypes = [""] From e2b199120fd7db1256917941d1ea5829e2455de8 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 17 Apr 2019 18:11:09 +0500 Subject: [PATCH 025/176] test(DocType): test_load_file_field_order --- frappe/core/doctype/doctype/test_doctype.py | 124 +++++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 41a4267c97..9b097ae1ae 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -104,4 +104,126 @@ 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)) \ No newline at end of file + self.assertFalse(re.match(pattern, condition)) + + def test_load_file_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.in_test = 0 + 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 Exception: + frappe.flags.in_test = 1 + raise + + frappe.flags.in_test = 1 From 43d6b4f81fd303b036106779faa74afff7bf518e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 18 Apr 2019 12:33:04 +0530 Subject: [PATCH 026/176] fix: On assignment, system sending an email to the assigned user even if notify to email is disabled --- frappe/desk/form/assign_to.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index a07d76ecd0..f57e0a92d7 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import cint from frappe.desk.form.load import get_docinfo import frappe.share @@ -160,7 +161,7 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE', 'notify': notify } - if arg and arg.get("notify"): + if arg and cint(arg.get("notify")): _notify(arg) def _notify(args): From aa7ff91239cd148fd44e4a16a814ffc877066659 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 18 Apr 2019 13:16:05 +0530 Subject: [PATCH 027/176] fix: User not able to make lead from the communication --- frappe/email/inbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/inbox.py b/frappe/email/inbox.py index 9df4218d71..0ff3a4ddd8 100644 --- a/frappe/email/inbox.py +++ b/frappe/email/inbox.py @@ -145,7 +145,7 @@ def make_lead_from_communication(communication, ignore_communication_links=False lead_name = None if doc.sender: lead_name = frappe.db.get_value("Lead", {"email_id": doc.sender}) - if not lead_name and doc.mobile_no: + if not lead_name and doc.phone_no: lead_name = frappe.db.get_value("Lead", {"mobile_no": doc.phone_no}) if not lead_name: lead = frappe.get_doc({ From 9db45f6fab2281fcf7cd6439fd76f680d1e739d7 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 18 Apr 2019 17:24:08 +0500 Subject: [PATCH 028/176] fix: Move field_order sync logic to DocType controller and introduce hooks before_export, before_import and prepare_docdict_for_import --- frappe/core/doctype/doctype/doctype.py | 53 +++++++++++++++++++++ frappe/core/doctype/doctype/test_doctype.py | 2 +- frappe/modules/export_file.py | 31 +----------- frappe/modules/import_file.py | 31 ++++-------- 4 files changed, 65 insertions(+), 52 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 499f64be53..61169e91e9 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -17,7 +17,9 @@ 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 import frappe.website.render +import json # imports - third-party imports import pymysql @@ -390,6 +392,57 @@ 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): + # 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 = 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 = 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_docdict_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 = 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 = 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 diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 9b097ae1ae..b6e51486f1 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -106,7 +106,7 @@ class TestDocType(unittest.TestCase): if condition: self.assertFalse(re.match(pattern, condition)) - def test_load_file_field_order(self): + def test_sync_field_order(self): from frappe.modules.import_file import get_file_path import os diff --git a/frappe/modules/export_file.py b/frappe/modules/export_file.py index 2c6d28d7c2..19e4f0c680 100644 --- a/frappe/modules/export_file.py +++ b/frappe/modules/export_file.py @@ -24,6 +24,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(): @@ -39,35 +40,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"), 'a+') as txtfile: - # if exporting DocType, retain order of 'fields' table and change order in 'field_order' - if doc.doctype == "DocType": - newdoc["field_order"] = [f.fieldname for f in doc.fields] - - try: - 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 doc.fields] - - for fieldname in old_field_names: - field_dict = filter(lambda d: d['fieldname'] == fieldname, newdoc['fields']) - if field_dict: - new_field_dicts.append(field_dict[0]) - remaining_field_names.remove(fieldname) - - for fieldname in remaining_field_names: - field_dict = filter(lambda d: d['fieldname'] == fieldname, newdoc['fields']) - new_field_dicts.append(field_dict[0]) - - newdoc['fields'] = new_field_dicts - except ValueError: - pass - - txtfile.seek(0) - txtfile.truncate() + with open(os.path.join(folder, fname + ".json"), 'w+') as txtfile: txtfile.write(frappe.as_json(newdoc)) def get_module_name(doc): diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index 75bc20eedd..1feb9fba5c 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -4,8 +4,9 @@ from __future__ import unicode_literals, print_function import frappe, os, json -from frappe.modules import get_module_path, scrub_dt_dn +from frappe.modules import get_module_path, scrub_dt_dn, load_doctype_module from frappe.utils import get_datetime_str +from frappe.model.base_document import get_controller ignore_values = { "Report": ["disabled", "prepared_report"], @@ -89,27 +90,6 @@ def read_doc_from_file(path): else: raise IOError('%s missing' % path) - # set order of fields from field_order - if doc.get("doctype") == "DocType": - if doc.get("field_order") and doc.get("fields"): - new_field_dicts = [] - remaining_field_names = [f['fieldname'] for f in doc['fields']] - - for fieldname in doc['field_order']: - field_dict = filter(lambda d: d['fieldname'] == fieldname, doc['fields']) - if field_dict: - new_field_dicts.append(field_dict[0]) - remaining_field_names.remove(fieldname) - - for fieldname in remaining_field_names: - field_dict = filter(lambda d: d['fieldname'] == fieldname, doc['fields']) - new_field_dicts.append(field_dict[0]) - - doc['fields'] = new_field_dicts - - if "field_order" in doc: - del doc['field_order'] - return doc ignore_doctypes = [""] @@ -118,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_docdict_for_import') and callable(getattr(controller, 'prepare_docdict_for_import')): + controller.prepare_docdict_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) From 330072c687efd34b28362c746b8ec33114a70953 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 18 Apr 2019 17:52:47 +0500 Subject: [PATCH 029/176] feat: Remove null and empty attributes from DocType JSON file on export --- frappe/core/doctype/doctype/doctype.py | 16 ++++++++++++++++ frappe/modules/import_file.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 61169e91e9..012834c696 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -18,6 +18,7 @@ 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 @@ -393,6 +394,21 @@ class DocType(Document): 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] diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index 1feb9fba5c..d3e00f5342 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals, print_function import frappe, os, json -from frappe.modules import get_module_path, scrub_dt_dn, load_doctype_module +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 From b41cd4924a36fd9e91dc7126f53d103c35ab9dd1 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 18 Apr 2019 18:39:58 +0500 Subject: [PATCH 030/176] fix: test --- frappe/core/doctype/doctype/doctype.py | 3 ++- frappe/core/doctype/doctype/test_doctype.py | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 012834c696..5a7dd34939 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -243,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() diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index b6e51486f1..4a0782340d 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -152,7 +152,7 @@ class TestDocType(unittest.TestCase): os.remove(path) try: - frappe.flags.in_test = 0 + frappe.flags.allow_doctype_export = 1 test_doctype.save() # assert that field_order list is being created with the default order @@ -222,8 +222,7 @@ class TestDocType(unittest.TestCase): 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 Exception: - frappe.flags.in_test = 1 + except: raise - - frappe.flags.in_test = 1 + finally: + frappe.flags.allow_doctype_export = 0 From 0e5e261d9e173a1c96686f905638d16ba2d7c5ce Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 18 Apr 2019 18:40:24 +0500 Subject: [PATCH 031/176] fix: Remove trailing spaces from exported JSON --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 3ad1b5bc84..efbc617f24 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -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 From a0dc4870602657654d7e87a0aea2b678d0990b3f Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 18 Apr 2019 18:55:46 +0500 Subject: [PATCH 032/176] fix: test --- frappe/core/doctype/doctype/doctype.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 5a7dd34939..0a74555b6d 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -425,13 +425,13 @@ class DocType(Document): remaining_field_names = [f.fieldname for f in self.fields] for fieldname in old_field_names: - field_dict = filter(lambda d: d['fieldname'] == fieldname, docdict['fields']) + 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 = filter(lambda d: d['fieldname'] == fieldname, docdict['fields']) + field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict['fields'])) new_field_dicts.append(field_dict[0]) docdict['fields'] = new_field_dicts From b7acbfc6f26d75c0f013e4eb9dd823c6ff7b5e58 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 18 Apr 2019 19:07:35 +0500 Subject: [PATCH 033/176] fix: test --- frappe/core/doctype/doctype/doctype.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 0a74555b6d..6b2a28869a 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -446,13 +446,13 @@ class DocType(Document): remaining_field_names = [f['fieldname'] for f in docdict.get('fields', [])] for fieldname in docdict.get('field_order'): - field_dict = filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', [])) + 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 = filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', [])) + field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', []))) new_field_dicts.append(field_dict[0]) docdict['fields'] = new_field_dicts From 32a327fe8e599e605e1b811603af5db7bf16ffdb Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Thu, 18 Apr 2019 19:25:17 +0500 Subject: [PATCH 034/176] fix: codacy --- frappe/modules/export_file.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/modules/export_file.py b/frappe/modules/export_file.py index 19e4f0c680..b904132530 100644 --- a/frappe/modules/export_file.py +++ b/frappe/modules/export_file.py @@ -6,7 +6,6 @@ from __future__ import unicode_literals import frappe, os import frappe.model from frappe.modules import scrub, get_module_path, scrub_dt_dn -import json def export_doc(doc): export_to_files([[doc.doctype, doc.name]]) From 1fd14e13ce33efd5b369eb4098a94ae37ad77c29 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 20 Apr 2019 11:48:47 +0530 Subject: [PATCH 035/176] fix: Prepared report shows status as Queued even if job is failed (#7317) --- .../prepared_report/prepared_report.json | 114 +++++++++++++++++- .../prepared_report/prepared_report.py | 37 +++--- .../prepared_report/prepared_report_list.js | 12 ++ 3 files changed, 146 insertions(+), 17 deletions(-) create mode 100644 frappe/core/doctype/prepared_report/prepared_report_list.js diff --git a/frappe/core/doctype/prepared_report/prepared_report.json b/frappe/core/doctype/prepared_report/prepared_report.json index e1b122e68a..ec89c6327a 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.json +++ b/frappe/core/doctype/prepared_report/prepared_report.json @@ -21,6 +21,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "report_name", "fieldtype": "Data", "hidden": 1, @@ -53,6 +54,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "ref_report_doctype", "fieldtype": "Link", "hidden": 1, @@ -86,6 +88,8 @@ "bold": 0, "collapsible": 0, "columns": 0, + "default": "Queued", + "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", "hidden": 1, @@ -93,12 +97,12 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Status", "length": 0, "no_copy": 0, - "options": "Queued\nCompleted", + "options": "Error\nQueued\nCompleted", "permlevel": 0, "precision": "", "print_hide": 0, @@ -119,6 +123,39 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "report_start_time", "fieldtype": "Datetime", "hidden": 0, @@ -151,6 +188,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "report_end_time", "fieldtype": "Datetime", "hidden": 0, @@ -183,6 +221,73 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "eval:doc.status == 'Error'", + "fetch_if_empty": 0, + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "error_message", + "fieldtype": "Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Error Message", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "filters_sb", "fieldtype": "Section Break", "hidden": 0, @@ -215,6 +320,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "filters", "fieldtype": "Small Text", "hidden": 1, @@ -247,6 +353,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "filter_values", "fieldtype": "HTML", "hidden": 0, @@ -279,6 +386,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "columns", "fieldtype": "Code", "hidden": 1, @@ -315,7 +423,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-10-23 16:58:14.879417", + "modified": "2019-04-19 12:39:47.211516", "modified_by": "Administrator", "module": "Core", "name": "Prepared Report", diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index 13273d50b9..bc7050d3c4 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -25,31 +25,40 @@ class PreparedReport(Document): def after_insert(self): enqueue( run_background, - instance=self, timeout=6000 + prepared_report=self.name, timeout=6000 ) def on_trash(self): remove_all("PreparedReport", self.name, from_delete=True) -def run_background(instance): +def run_background(prepared_report): + instance = frappe.get_doc("Prepared Report", prepared_report) report = frappe.get_doc("Report", instance.ref_report_doctype) - report.custom_columns = [] + try: + report.custom_columns = [] - if report.report_type == 'Custom Report': - custom_report_doc = report - reference_report = custom_report_doc.reference_report - report = frappe.get_doc("Report", reference_report) - report.custom_columns = custom_report_doc.json + if report.report_type == 'Custom Report': + custom_report_doc = report + reference_report = custom_report_doc.reference_report + report = frappe.get_doc("Report", reference_report) + report.custom_columns = custom_report_doc.json - result = generate_report_result(report, filters=instance.filters, user=instance.owner) - create_json_gz_file(result['result'], 'Prepared Report', instance.name) + result = generate_report_result(report, filters=instance.filters, user=instance.owner) + create_json_gz_file(result['result'], 'Prepared Report', instance.name) - instance.status = "Completed" - instance.columns = json.dumps(result["columns"]) - instance.report_end_time = frappe.utils.now() - instance.save() + instance.status = "Completed" + instance.columns = json.dumps(result["columns"]) + instance.report_end_time = frappe.utils.now() + instance.save() + + except Exception: + frappe.log_error(frappe.get_traceback()) + instance = frappe.get_doc("Prepared Report", prepared_report) + instance.status = "Error" + instance.error_message = frappe.get_traceback() + instance.save() frappe.publish_realtime( 'report_generated', diff --git a/frappe/core/doctype/prepared_report/prepared_report_list.js b/frappe/core/doctype/prepared_report/prepared_report_list.js new file mode 100644 index 0000000000..8acb3bc75a --- /dev/null +++ b/frappe/core/doctype/prepared_report/prepared_report_list.js @@ -0,0 +1,12 @@ +frappe.listview_settings['Prepared Report'] = { + add_fields: ["status"], + get_indicator: function(doc) { + if(doc.status==="Completed"){ + return [__("Completed"), "green", "status,=,Completed"]; + } else if(doc.status ==="Error"){ + return [__("Error"), "red", "status,=,Error"]; + } else if(doc.status ==="Queued"){ + return [__("Queued"), "orange", "status,=,Queued"]; + } + } +}; \ No newline at end of file From 0c5fe71b69bcef488dd4529af5c6310d961be8d1 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 20 Apr 2019 11:50:36 +0530 Subject: [PATCH 036/176] fix: Move erpnext related methods from frappe to erpnext (#7315) Dependent PR https://github.com/frappe/erpnext/pull/17293 --- frappe/email/inbox.py | 65 +------------------------------------------ 1 file changed, 1 insertion(+), 64 deletions(-) diff --git a/frappe/email/inbox.py b/frappe/email/inbox.py index 0ff3a4ddd8..9d15b387e3 100644 --- a/frappe/email/inbox.py +++ b/frappe/email/inbox.py @@ -118,67 +118,4 @@ def link_communication_to_document(doc, reference_doctype, reference_name, ignor doc.reference_doctype = reference_doctype doc.reference_name = reference_name doc.status = "Linked" - doc.save(ignore_permissions=True) - -@frappe.whitelist() -def make_issue_from_communication(communication, ignore_communication_links=False): - """ raise a issue from email """ - - doc = frappe.get_doc("Communication", communication) - issue = frappe.get_doc({ - "doctype": "Issue", - "subject": doc.subject, - "communication_medium": doc.communication_medium, - "raised_by": doc.sender or "", - "raised_by_phone": doc.phone_no or "" - }).insert(ignore_permissions=True) - - link_communication_to_document(doc, "Issue", issue.name, ignore_communication_links) - - return issue.name - -@frappe.whitelist() -def make_lead_from_communication(communication, ignore_communication_links=False): - """ raise a issue from email """ - - doc = frappe.get_doc("Communication", communication) - lead_name = None - if doc.sender: - lead_name = frappe.db.get_value("Lead", {"email_id": doc.sender}) - if not lead_name and doc.phone_no: - lead_name = frappe.db.get_value("Lead", {"mobile_no": doc.phone_no}) - if not lead_name: - lead = frappe.get_doc({ - "doctype": "Lead", - "lead_name": doc.sender_full_name, - "email_id": doc.sender, - "mobile_no": doc.phone_no - }) - lead.flags.ignore_mandatory = True - lead.flags.ignore_permissions = True - lead.insert() - - lead_name = lead.name - - link_communication_to_document(doc, "Lead", lead_name, ignore_communication_links) - return lead_name - -@frappe.whitelist() -def make_opportunity_from_communication(communication, ignore_communication_links=False): - doc = frappe.get_doc("Communication", communication) - - lead = doc.reference_name if doc.reference_doctype == "Lead" else None - if not lead: - lead = make_lead_from_communication(communication, ignore_communication_links=True) - - enquiry_from = "Lead" - - opportunity = frappe.get_doc({ - "doctype": "Opportunity", - "enquiry_from": enquiry_from, - "lead": lead - }).insert(ignore_permissions=True) - - link_communication_to_document(doc, "Opportunity", opportunity.name, ignore_communication_links) - - return opportunity.name + doc.save(ignore_permissions=True) \ No newline at end of file From fd2f1b5e362835df1410033077cf0c96a8b5f70b Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 21 Apr 2019 22:03:19 +0530 Subject: [PATCH 037/176] fix: Link field fixes for custom reports --- frappe/desk/query_report.py | 2 +- frappe/public/js/frappe/views/reports/query_report.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index ce68437566..9da78e365a 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -225,7 +225,7 @@ def add_data_to_custom_columns(columns, result): fieldname = column['fieldname'] key = (column['doctype'], fieldname) link_field = column['link_field'] - row[fieldname] = custom_fields_data.get(key, {}).get(row[link_field]) + row[fieldname] = custom_fields_data.get(key, {}).get(row.get(link_field)) return data diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 9b91b2a2b5..7d689e1421 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1020,6 +1020,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { label: df.label, link_field: this.doctype_field_map[values.doctype], doctype: values.doctype, + options: df.fieldtype === "Link" ? values.doctype : undefined, width: 100 }); From dcf8ff417f45c970b6674cdc5d3fcdce5d1d84c3 Mon Sep 17 00:00:00 2001 From: Prasad Ramesh Date: Mon, 22 Apr 2019 13:10:34 +0530 Subject: [PATCH 038/176] fix broken YouTube link for user help --- frappe/core/doctype/user/user_list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/user/user_list.js b/frappe/core/doctype/user/user_list.js index 116e8e6d86..5632edf0cc 100644 --- a/frappe/core/doctype/user/user_list.js +++ b/frappe/core/doctype/user/user_list.js @@ -16,4 +16,4 @@ frappe.listview_settings['User'] = { } }; -frappe.help.youtube_id["User"] = "fnBoRhBrwR4"; +frappe.help.youtube_id["User"] = "8Slw1hsTmUI"; From 2454a85b929402d6aa9ed0a7850e129bed64a3ae Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Mon, 22 Apr 2019 14:24:09 +0550 Subject: [PATCH 039/176] bumped to version 11.1.23 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 3ad1b5bc84..d2443e93f1 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -24,7 +24,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '11.1.22' +__version__ = '11.1.23' __title__ = "Frappe Framework" local = Local() From d60ed1f0a9aed55c765c360cd6187cfee92b56e4 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 23 Apr 2019 10:05:33 +0530 Subject: [PATCH 040/176] fix: fixed minor bug on query_report.js --- frappe/public/js/frappe/views/reports/query_report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 9b91b2a2b5..c8f664d681 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -497,7 +497,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { get_possible_chart_options() { const columns = this.raw_data.columns; const rows = this.raw_data.result; - const first_row = rows[0]; + const first_row = Array.isArray(rows[0]) ? rows[0] : Object.values(rows[0]); const has_total_row = this.raw_data.add_total_row; const indices = first_row.reduce((accumulator, current_value, current_index) => { From 11fca9fb5c7ace00ef7b05ad9e6cbe7435dfb31e Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Mon, 22 Apr 2019 12:55:13 +0530 Subject: [PATCH 041/176] feat: Maintain list of tables touched during migrate This can be used to selectively restore changed tables from backup after migrate failure --- frappe/database.py | 22 +++++++++++++ frappe/migrate.py | 70 ++++++++++++++++++++++++----------------- frappe/tests/test_db.py | 29 +++++++++++++++++ 3 files changed, 93 insertions(+), 28 deletions(-) diff --git a/frappe/database.py b/frappe/database.py index 10a51af24e..9323aa9518 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -197,6 +197,10 @@ class Database: frappe.log(values) frappe.log(">>>>") self._cursor.execute(query, values) + + if frappe.flags.in_migrate: + self.log_touched_tables(query, values) + else: if debug: if explain: @@ -209,6 +213,9 @@ class Database: self._cursor.execute(query) + if frappe.flags.in_migrate: + self.log_touched_tables(query) + if debug: time_end = time() frappe.errprint(("Execution time: {0} sec").format(round(time_end - time_start, 2))) @@ -976,6 +983,21 @@ class Database: # when document does not exist return [] + def log_touched_tables(self, query, values=None): + if values: + query = self._cursor.mogrify(query, values) + if query.strip().lower().split()[0] in ('insert', 'delete', 'update', 'alter'): + # ([`\"']?) Captures ', " or ` at the begining of the table name (if provided) + # (tab([A-Z]\w+)( [A-Z]\w+)*) Captures table names that start with "tab" + # and are continued with multiple words that start with a captital letter + # e.g. 'tabXxx' or 'tabXxx Xxx' or 'tabXxx Xxx Xxx' and so on + # \1 matches the first captured group (quote character) at the end of the table name + tables = [groups[1] for groups in re.findall(r'([`"\']?)(tab([A-Z]\w+)( [A-Z]\w+)*)\1', query)] + if frappe.flags.touched_tables is None: + frappe.flags.touched_tables = set() + frappe.flags.touched_tables.update(tables) + + def enqueue_jobs_after_commit(): if frappe.flags.enqueue_after_commit and len(frappe.flags.enqueue_after_commit) > 0: for job in frappe.flags.enqueue_after_commit: diff --git a/frappe/migrate.py b/frappe/migrate.py index 956b4a3c93..1952c3cd6f 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals +import json +import os import frappe import frappe.translate import frappe.modules.patch_handler @@ -26,40 +28,52 @@ def migrate(verbose=True, rebuild_website=False): - sync web pages (from /www) - run after migrate hooks ''' - frappe.flags.in_migrate = True - clear_global_cache() - #run before_migrate hooks - for app in frappe.get_installed_apps(): - for fn in frappe.get_hooks('before_migrate', app_name=app): - frappe.get_attr(fn)() + touched_tables_file = frappe.get_site_path('touched_tables.json') + if os.path.exists(touched_tables_file): + os.remove(touched_tables_file) - # run patches - frappe.modules.patch_handler.run_all() - # sync - frappe.model.sync.sync_all(verbose=verbose) - frappe.translate.clear_cache() - sync_fixtures() - sync_customizations() - sync_desktop_icons() - sync_languages() + try: + frappe.flags.touched_tables = set() + frappe.flags.in_migrate = True + clear_global_cache() - frappe.get_doc('Portal Settings', 'Portal Settings').sync_menu() + #run before_migrate hooks + for app in frappe.get_installed_apps(): + for fn in frappe.get_hooks('before_migrate', app_name=app): + frappe.get_attr(fn)() - # syncs statics - render.clear_cache() + # run patches + frappe.modules.patch_handler.run_all() + # sync + frappe.model.sync.sync_all(verbose=verbose) + frappe.translate.clear_cache() + sync_fixtures() + sync_customizations() + sync_desktop_icons() + sync_languages() - # add static pages to global search - router.sync_global_search() + frappe.get_doc('Portal Settings', 'Portal Settings').sync_menu() - #run after_migrate hooks - for app in frappe.get_installed_apps(): - for fn in frappe.get_hooks('after_migrate', app_name=app): - frappe.get_attr(fn)() + # syncs statics + render.clear_cache() - frappe.db.commit() + # add static pages to global search + router.sync_global_search() - clear_notifications() + #run after_migrate hooks + for app in frappe.get_installed_apps(): + for fn in frappe.get_hooks('after_migrate', app_name=app): + frappe.get_attr(fn)() + + frappe.db.commit() + + clear_notifications() + + frappe.publish_realtime("version-update") + frappe.flags.in_migrate = False + finally: + with open(touched_tables_file, 'w') as f: + json.dump(list(frappe.flags.touched_tables), f, sort_keys=True, indent=4) + frappe.flags.touched_tables.clear() - frappe.publish_realtime("version-update") - frappe.flags.in_migrate = False diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index ea6c07ae76..e3c1a62a78 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import unittest import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_field class TestDB(unittest.TestCase): def test_get_value(self): @@ -27,3 +28,31 @@ class TestDB(unittest.TestCase): # def test_multiple_queries(self): # # implicit commit # self.assertRaises(frappe.SQLError, frappe.db.sql, """select name from `tabUser`; truncate `tabEmail Queue`""") + + def test_log_touched_tables(self): + frappe.flags.in_migrate = True + frappe.flags.touched_tables = set() + frappe.db.set_value('System Settings', 'System Settings', 'backup_limit', 5) + self.assertIn('tabSingles', frappe.flags.touched_tables) + + frappe.flags.touched_tables = set() + todo = frappe.get_doc({'doctype': 'ToDo', 'description': 'Random Description'}) + todo.save() + self.assertIn('tabToDo', frappe.flags.touched_tables) + + frappe.flags.touched_tables = set() + todo.description = "Another Description" + todo.save() + self.assertIn('tabToDo', frappe.flags.touched_tables) + + frappe.flags.touched_tables = set() + todo.delete() + self.assertIn('tabToDo', frappe.flags.touched_tables) + + frappe.flags.touched_tables = set() + create_custom_field('ToDo', {'label': 'ToDo Custom Field'}) + + self.assertIn('tabToDo', frappe.flags.touched_tables) + self.assertIn('tabCustom Field', frappe.flags.touched_tables) + frappe.flags.in_migrate = False + frappe.flags.touched_tables.clear() From 8e836218b2f07def4c2c42e1ff55a1f3c2fdb903 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 23 Apr 2019 17:26:24 +0530 Subject: [PATCH 042/176] fix: Changed options for Make Chart dialog --- frappe/public/js/frappe/views/reports/query_report.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index c8f664d681..04f5664070 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -562,16 +562,16 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { fieldname: 'y_field', label: 'Y Field', fieldtype: 'Select', - options: numeric_fields, - default: numeric_fields[0], + options: numeric_fields.map((opt) => opt.options), + default: numeric_fields.map((opt) => opt.options)[0], onchange: preview_chart }, { fieldname: 'x_field', label: 'X Field', fieldtype: 'Select', - options: non_numeric_fields, - default: non_numeric_fields[0], + options: non_numeric_fields.map((opt) => opt.options), + default: non_numeric_fields.map((opt) => opt.options)[0], onchange: preview_chart }, { From a08d2525760a2f28e0f90bdfb60b5000212c3d94 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 23 Apr 2019 19:03:33 +0530 Subject: [PATCH 043/176] fix: Fixed major bugs in `query_report.js` --- .../js/frappe/views/reports/query_report.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 04f5664070..67dc424189 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -496,7 +496,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { get_possible_chart_options() { const columns = this.raw_data.columns; - const rows = this.raw_data.result; + const rows = this.raw_data.result.filter(value => Object.keys(value).length !== 0); const first_row = Array.isArray(rows[0]) ? rows[0] : Object.values(rows[0]); const has_total_row = this.raw_data.add_total_row; @@ -508,11 +508,10 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }, []); function get_column_values(column_name) { - const column_index = columns.indexOf(column_name); - return rows.map(row => row[column_index]); + return rows.map(row => row[column_name]); } - function get_chart_options({ y_field, x_field, chart_type, color }) { + function make_chart_options({ y_field, x_field, chart_type, color }) { const type = chart_type.toLowerCase(); const colors = color ? [color] : undefined; @@ -540,7 +539,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { function preview_chart() { const wrapper = $(dialog.fields_dict["chart_preview"].wrapper); const values = dialog.get_values(true); - let options = get_chart_options(values); + let options = make_chart_options(values); + console.log("Chart Options", options) Object.assign(options, { height: 150 @@ -553,6 +553,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } const numeric_fields = columns.filter((col, i) => indices.includes(i)); + window.flds = numeric_fields const non_numeric_fields = columns.filter((col, i) => !indices.includes(i)) const dialog = new frappe.ui.Dialog({ @@ -562,16 +563,16 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { fieldname: 'y_field', label: 'Y Field', fieldtype: 'Select', - options: numeric_fields.map((opt) => opt.options), - default: numeric_fields.map((opt) => opt.options)[0], + options: numeric_fields.map((field) => { return {label: field.label, value: field.fieldname}}), + default: numeric_fields.map((field) => { return {label: field.label, value: field.fieldname}})[0], onchange: preview_chart }, { fieldname: 'x_field', label: 'X Field', fieldtype: 'Select', - options: non_numeric_fields.map((opt) => opt.options), - default: non_numeric_fields.map((opt) => opt.options)[0], + options: non_numeric_fields.map((field) => { return {label: field.label, value: field.fieldname}}), + default: non_numeric_fields.map((field) => { return {label: field.label, value: field.fieldname}})[0], onchange: preview_chart }, { @@ -606,9 +607,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { ], primary_action_label: __('Make'), primary_action: (values) => { - let options = get_chart_options(values); + let options = make_chart_options(values); - options.title = __(`${this.report_name}: ${values.y_field} vs ${values.x_field}`); + options.title = __(`${this.report_name}: ${numeric_fields.filter((field) => field.fieldname == values.y_field)[0].label} vs ${non_numeric_fields.filter((field) => field.fieldname == values.x_field)[0].label}`); this.render_chart(options); From 6b57f423ac334ddd7d1380c4ecf7456659fb3bc0 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 23 Apr 2019 19:16:46 +0530 Subject: [PATCH 044/176] fix: removed stray console.log --- frappe/public/js/frappe/views/reports/query_report.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 67dc424189..2d15581f29 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -540,7 +540,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { const wrapper = $(dialog.fields_dict["chart_preview"].wrapper); const values = dialog.get_values(true); let options = make_chart_options(values); - console.log("Chart Options", options) Object.assign(options, { height: 150 From 8fd8665b887fb0b8774dc0d0fcc39c09c37e066a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 23 Apr 2019 19:26:13 +0530 Subject: [PATCH 045/176] Update query_report.js removed stray code --- frappe/public/js/frappe/views/reports/query_report.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 2d15581f29..d17474419f 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -552,7 +552,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } const numeric_fields = columns.filter((col, i) => indices.includes(i)); - window.flds = numeric_fields const non_numeric_fields = columns.filter((col, i) => !indices.includes(i)) const dialog = new frappe.ui.Dialog({ From 755ae401433203c58d0e6011afca49b7227690b3 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Tue, 23 Apr 2019 20:53:38 +0530 Subject: [PATCH 046/176] fix (linting) --- .../js/frappe/views/reports/query_report.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 2d15581f29..2ba228d088 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -562,16 +562,24 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { fieldname: 'y_field', label: 'Y Field', fieldtype: 'Select', - options: numeric_fields.map((field) => { return {label: field.label, value: field.fieldname}}), - default: numeric_fields.map((field) => { return {label: field.label, value: field.fieldname}})[0], + options: numeric_fields.map((field) => { + return {label: field.label, value: field.fieldname}; + }), + default: numeric_fields.map((field) => { + return {label: field.label, value: field.fieldname}; + })[0], onchange: preview_chart }, { fieldname: 'x_field', label: 'X Field', fieldtype: 'Select', - options: non_numeric_fields.map((field) => { return {label: field.label, value: field.fieldname}}), - default: non_numeric_fields.map((field) => { return {label: field.label, value: field.fieldname}})[0], + options: non_numeric_fields.map((field) => { + return {label: field.label, value: field.fieldname}; + }), + default: non_numeric_fields.map((field) => { + return {label: field.label, value: field.fieldname}; + })[0], onchange: preview_chart }, { From 95f2f7d16934f11dfa78c816694d8303222755d5 Mon Sep 17 00:00:00 2001 From: scmmishra Date: Wed, 24 Apr 2019 09:30:09 +0530 Subject: [PATCH 047/176] chore: Minor code improvements --- .../js/frappe/views/reports/query_report.js | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index e6a0f23b1d..62f4c80bf7 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -496,7 +496,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { get_possible_chart_options() { const columns = this.raw_data.columns; - const rows = this.raw_data.result.filter(value => Object.keys(value).length !== 0); + const rows = this.raw_data.result.filter(value => Object.keys(value).length); const first_row = Array.isArray(rows[0]) ? rows[0] : Object.values(rows[0]); const has_total_row = this.raw_data.add_total_row; @@ -551,6 +551,12 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { wrapper.show(); } + function get_options(fields) { + return fields.map((field) => { + return {label: field.label, value: field.fieldname}; + } + } + const numeric_fields = columns.filter((col, i) => indices.includes(i)); const non_numeric_fields = columns.filter((col, i) => !indices.includes(i)) @@ -561,24 +567,14 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { fieldname: 'y_field', label: 'Y Field', fieldtype: 'Select', - options: numeric_fields.map((field) => { - return {label: field.label, value: field.fieldname}; - }), - default: numeric_fields.map((field) => { - return {label: field.label, value: field.fieldname}; - })[0], + options: get_options(numeric_fields), onchange: preview_chart }, { fieldname: 'x_field', label: 'X Field', fieldtype: 'Select', - options: non_numeric_fields.map((field) => { - return {label: field.label, value: field.fieldname}; - }), - default: non_numeric_fields.map((field) => { - return {label: field.label, value: field.fieldname}; - })[0], + options: get_options(non_numeric_fields), onchange: preview_chart }, { From d09b26b56349368a8b5d2acbb573ded987a3d0ba Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 24 Apr 2019 12:38:13 +0530 Subject: [PATCH 048/176] fix(ReportView): Remember column widths --- .../public/js/frappe/views/reports/query_report.js | 4 ++-- .../public/js/frappe/views/reports/report_view.js | 13 +++++++++++++ package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index fa6bf95192..6ac95d8f46 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -554,8 +554,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { function get_options(fields) { return fields.map((field) => { return {label: field.label, value: field.fieldname}; - } - } + }); + } const numeric_fields = columns.filter((col, i) => indices.includes(i)); const non_numeric_fields = columns.filter((col, i) => !indices.includes(i)) diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 686483ed91..5b3ed8fab8 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -796,7 +796,20 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { setup_columns() { const hide_columns = ['docstatus']; const fields = this.fields.filter(f => !hide_columns.includes(f[0])); + + // apply previous column width + let column_widths = null; + if (this.columns) { + column_widths = this.get_column_widths(); + } this.columns = fields.map(f => this.build_column(f)).filter(Boolean); + + if (column_widths) { + this.columns = this.columns.map(column => { + column.width = column_widths[column.id] || column.width; + return column; + }); + } } build_column(c) { diff --git a/package.json b/package.json index c2f6aaec6f..2fcaf5c307 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "cookie": "^0.3.1", "express": "^4.16.2", "fast-deep-equal": "^2.0.1", - "frappe-datatable": "^1.12.1", + "frappe-datatable": "^1.12.2", "frappe-gantt": "^0.1.0", "fuse.js": "^3.2.0", "highlight.js": "^9.12.0", diff --git a/yarn.lock b/yarn.lock index b8fb29551f..d9e03b175a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1224,10 +1224,10 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -frappe-datatable@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.12.1.tgz#6efe342346025ffeed822e188a05da6c9c9230c9" - integrity sha512-AC2sJDuJOr0nSfT+w7DPZd7zPW7PHGhg9XxdRNlaAYpy45r3owdJVF+R6lCMQRvFvvzmPMht02EOE6cFpkzLKw== +frappe-datatable@^1.12.2: + version "1.12.2" + resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.12.2.tgz#cb7e32defd38426c63278249843baa91dcba79aa" + integrity sha512-e4prKv4klBW9I5iVIesoeJgt4LofzZDfDxP+eqvQN2GZRVs8KYEXgyPhcaH89FfuhIkzVK6kDbMHw+KbGfOJIA== dependencies: hyperlist "^1.0.0-beta" lodash "^4.17.5" From d7f90a9ebcb9d6c0c9188f6a168dbc64c316a8f5 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 24 Apr 2019 13:07:15 +0500 Subject: [PATCH 049/176] style: Rename prepare_docdict_for_import to prepare_for_import --- frappe/core/doctype/doctype/doctype.py | 2 +- frappe/modules/import_file.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 6b2a28869a..673212c68a 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -439,7 +439,7 @@ class DocType(Document): pass @staticmethod - def prepare_docdict_for_import(docdict): + def prepare_for_import(docdict): # set order of fields from field_order if docdict.get("field_order"): new_field_dicts = [] diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index d3e00f5342..c4135915f8 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -100,8 +100,8 @@ def import_doc(docdict, force=False, data_import=False, pre_process=None, docdict["__islocal"] = 1 controller = get_controller(docdict['doctype']) - if controller and hasattr(controller, 'prepare_docdict_for_import') and callable(getattr(controller, 'prepare_docdict_for_import')): - controller.prepare_docdict_for_import(docdict) + 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) From dd639777102c09992133b539cf086429e9cea203 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 24 Apr 2019 17:04:29 +0530 Subject: [PATCH 050/176] Revert "feat: DocType JSON changes for cleaner Git Diffs" --- frappe/__init__.py | 2 +- frappe/core/doctype/doctype/doctype.py | 72 +----------- frappe/core/doctype/doctype/test_doctype.py | 123 +------------------- frappe/modules/export_file.py | 3 +- frappe/modules/import_file.py | 8 -- 5 files changed, 4 insertions(+), 204 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 459e8049c1..d2443e93f1 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -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, separators=(',', ': ')) + return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler) def are_emails_muted(): from frappe.utils import cint diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 673212c68a..499f64be53 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -17,10 +17,7 @@ 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 @@ -243,8 +240,7 @@ class DocType(Document): self.update_fields_to_fetch() from frappe import conf - 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: + if not self.custom and not (frappe.flags.in_import or frappe.flags.in_test) and conf.get('developer_mode'): self.export_doc() self.make_controller_template() @@ -394,72 +390,6 @@ 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 diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 4a0782340d..41a4267c97 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -104,125 +104,4 @@ 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)) - - 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 + self.assertFalse(re.match(pattern, condition)) \ No newline at end of file diff --git a/frappe/modules/export_file.py b/frappe/modules/export_file.py index b904132530..66a6a9f6b7 100644 --- a/frappe/modules/export_file.py +++ b/frappe/modules/export_file.py @@ -23,7 +23,6 @@ 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(): @@ -39,7 +38,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): diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index c4135915f8..eadfcb3dd5 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -6,7 +6,6 @@ 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"], @@ -98,15 +97,8 @@ 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) From b9fef7db2a5586722a8faa595b8b2baced1a6233 Mon Sep 17 00:00:00 2001 From: NahuelOperto Date: Wed, 24 Apr 2019 16:09:03 -0300 Subject: [PATCH 051/176] fix childtable error in editable report builder --- .../public/js/frappe/views/reports/report_view.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 686483ed91..6653865acf 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -509,7 +509,20 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { .then((updated_doc) => { const _data = this.data.find(d => d.name === updated_doc.name); for (let field in _data) { - _data[field] = updated_doc[field]; + if (field.includes(':')) { + // child table field + const [cdt, _field] = field.split(':'); + const cdt_row = Object.keys(updated_doc) + .filter(key => Array.isArray(updated_doc[key]) && updated_doc[key][0].doctype === cdt) + .map(key => updated_doc[key]) + .map(a => a[0]) + .filter(cdoc => cdoc.name === _data[cdt + ':name'])[0]; + if (cdt_row) { + _data[field] = cdt_row[_field]; + } + } else { + _data[field] = updated_doc[field]; + } } }) .then(() => this.refresh_charts()); From d26b5e5e088e86f7665f38b1e907e82e5e697269 Mon Sep 17 00:00:00 2001 From: Saif Date: Thu, 25 Apr 2019 12:08:49 +0500 Subject: [PATCH 052/176] feat: Set initial depth of tree report (#7348) --- frappe/public/js/frappe/views/reports/query_report.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 6ac95d8f46..ba18866c24 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -467,6 +467,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.datatable = new DataTable(this.$report[0], datatable_options); } + if (typeof this.report_settings.initial_depth == "number") { + this.datatable.rowmanager.setTreeDepth(this.report_settings.initial_depth); + } if (this.report_settings.after_datatable_render) { this.report_settings.after_datatable_render(this.datatable); } From fba37163bf7dfc76e7ac504bbb2c3fa73daafbdc Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 26 Apr 2019 12:03:06 +0530 Subject: [PATCH 053/176] fix: fixed Make new button for empty state in report view --- frappe/public/js/frappe/views/reports/report_view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 5b3ed8fab8..63ed524f72 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -41,6 +41,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { setup_view() { this.setup_columns(); this.bind_charts_button(); + super.setup_new_doc_event(); } setup_result_area() { From 38047b909fd956b17247884efdeaaa6bf21c31a8 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Fri, 26 Apr 2019 12:43:37 +0530 Subject: [PATCH 054/176] perf: Remove MANIFEST.in (#7361) Faster pip install -e frappe https://stackoverflow.com/questions/24727709/do-python-projects-need-a-manifest-in-and-what-should-be-in-it --- MANIFEST.in | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 30ca6761a2..0000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,29 +0,0 @@ -include MANIFEST.in -include requirements.txt -include *.json -include *.md -include *.py -recursive-include frappe *.css -recursive-include frappe *.dat -recursive-include frappe *.eot -recursive-include frappe *.gif -recursive-include frappe *.html -recursive-include frappe *.jpg -recursive-include frappe *.js -recursive-include frappe *.json -recursive-include frappe *.md -recursive-include frappe *.otf -recursive-include frappe *.png -recursive-include frappe *.py -recursive-include frappe *.sql -recursive-include frappe *.svg -recursive-include frappe *.swf -recursive-include frappe *.ttf -recursive-include frappe *.woff -recursive-include frappe *.xml -recursive-include frappe *.csv -recursive-include frappe *.ico -recursive-include frappe *.less -recursive-include frappe *.txt -recursive-include frappe/public * -recursive-exclude * *.pyc From 194159d97b11ec9f2dcd88dde8e6f429c8189c8c Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 26 Apr 2019 13:57:23 +0530 Subject: [PATCH 055/176] fix: Upgrade datatable to support RTL --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2fcaf5c307..8e938ac137 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "cookie": "^0.3.1", "express": "^4.16.2", "fast-deep-equal": "^2.0.1", - "frappe-datatable": "^1.12.2", + "frappe-datatable": "^1.13.0", "frappe-gantt": "^0.1.0", "fuse.js": "^3.2.0", "highlight.js": "^9.12.0", diff --git a/yarn.lock b/yarn.lock index d9e03b175a..1ac0efb420 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1224,10 +1224,10 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -frappe-datatable@^1.12.2: - version "1.12.2" - resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.12.2.tgz#cb7e32defd38426c63278249843baa91dcba79aa" - integrity sha512-e4prKv4klBW9I5iVIesoeJgt4LofzZDfDxP+eqvQN2GZRVs8KYEXgyPhcaH89FfuhIkzVK6kDbMHw+KbGfOJIA== +frappe-datatable@^1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.13.0.tgz#b0b283664e946aeedf1ec6a4e678bac98b274a34" + integrity sha512-Vmtkzgtk7fQ4RTuNNHtAE3doeIKg8m1YUVP5K8srS2eYepPQLmR1HIzBWcMlzkXPNOyYIEEmWK+hZ0l///Ka8w== dependencies: hyperlist "^1.0.0-beta" lodash "^4.17.5" From e39f477fdb971cdd6575e0b3210664c65a5b3a28 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 26 Apr 2019 15:30:13 +0530 Subject: [PATCH 056/176] fix: RTL support for reports --- frappe/public/js/frappe/desk.js | 2 +- frappe/public/js/frappe/misc/utils.js | 4 ++++ frappe/public/js/frappe/views/reports/query_report.js | 1 + frappe/public/js/frappe/views/reports/report_view.js | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 84f4912db0..73c37ce934 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -466,7 +466,7 @@ frappe.Application = Class.extend({ }, set_rtl: function() { - if (["ar", "he", "fa"].indexOf(frappe.boot.lang) >= 0) { + if (frappe.utils.is_rtl()) { var ls = document.createElement('link'); ls.rel="stylesheet"; ls.href= "assets/css/frappe-rtl.css"; diff --git a/frappe/public/js/frappe/misc/utils.js b/frappe/public/js/frappe/misc/utils.js index 7eef74509b..76cc71c73b 100644 --- a/frappe/public/js/frappe/misc/utils.js +++ b/frappe/public/js/frappe/misc/utils.js @@ -673,6 +673,10 @@ Object.assign(frappe.utils, { deep_equal(a, b) { return deep_equal(a, b); + }, + + is_rtl() { + return ["ar", "he", "fa"].includes(frappe.boot.lang); } }); diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index ba18866c24..2982ef4796 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -456,6 +456,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { layout: 'fixed', cellHeight: 33, showTotalRow: this.raw_data.add_total_row, + direction: frappe.utils.is_rtl() ? 'rtl' : 'ltr', hooks: { columnTotal: frappe.utils.report_column_total } diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 63ed524f72..f22746ecfb 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -229,6 +229,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { checkboxColumn: true, inlineFilters: true, cellHeight: 35, + direction: frappe.utils.is_rtl() ? 'rtl' : 'ltr', events: { onRemoveColumn: (column) => { this.remove_column_from_datatable(column); From e290d8fa233c55419a415b8a968b1f14d70a25c7 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 29 Apr 2019 11:34:14 +0530 Subject: [PATCH 057/176] fix: "Border disappears on zoom out" issue (#7365) * fix: "Border disappears on zoom out" issue * fix: Use before pseudo element to avoid element block --- frappe/public/less/page.less | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frappe/public/less/page.less b/frappe/public/less/page.less index d051a63b00..c0609f8395 100644 --- a/frappe/public/less/page.less +++ b/frappe/public/less/page.less @@ -109,6 +109,17 @@ .layout-main-section { border: 1px solid @border-color; border-top: 0px; + // hack + // https://stackoverflow.com/a/49038292/5955589 + &::before { + content: ''; + position: absolute; + top: 0; + right: 15px; + bottom: 0; + left: 15px; + border: 1px solid @border-color; + } } .layout-main-section-wrapper { From a88b0a4ac24210b66ce467d563e0ccb8033457b0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 29 Apr 2019 13:48:38 +0530 Subject: [PATCH 058/176] fix: GSTR-1 report not working if show totals is enabled --- frappe/desk/query_report.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 9da78e365a..1f3cd752bc 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -360,6 +360,8 @@ def add_total_row(result, columns, meta = None): options = col.get("options") for row in result: + if i >= len(row): continue + cell = row.get(fieldname) if isinstance(row, dict) else row[i] if fieldtype in ["Currency", "Int", "Float", "Percent"] and flt(cell): total_row[i] = flt(total_row[i]) + flt(cell) From b0db49a273e83f52f08a46964965dc849d171ee6 Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Mon, 29 Apr 2019 16:53:37 +0550 Subject: [PATCH 059/176] bumped to version 11.1.24 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index d2443e93f1..8a4fe507c5 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -24,7 +24,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '11.1.23' +__version__ = '11.1.24' __title__ = "Frappe Framework" local = Local() From 9f5e32cee0ec52de1aa29475903964118ab70306 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 29 Apr 2019 19:10:12 +0530 Subject: [PATCH 060/176] fix: while saving employee user getting user permissions error --- frappe/permissions.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/permissions.py b/frappe/permissions.py index 3097919723..753019cc7a 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -25,16 +25,18 @@ def print_has_permission_check_logs(func): frappe.flags['has_permission_check_logs'] = [] result = func(*args, **kwargs) self_perm_check = True if not kwargs.get('user') else kwargs.get('user') == frappe.session.user + raise_exception = False if kwargs.get('raise_exception') == False else True + # print only if access denied # and if user is checking his own permission - if not result and self_perm_check: + if not result and self_perm_check and raise_exception: msgprint(('
').join(frappe.flags.get('has_permission_check_logs'))) frappe.flags.pop('has_permission_check_logs', None) return result return inner @print_has_permission_check_logs -def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): +def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None, raise_exception=True): """Returns True if user has permission `ptype` for given `doctype`. If `doc` is passed, it also checks user, share and owner permissions. From d78ace4dc575a331a5afe4f893c1df67454e36c8 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 29 Apr 2019 19:35:51 +0530 Subject: [PATCH 061/176] Revert "fix: "Border disappears on zoom out" issue" (#7377) This reverts commit e290d8fa233c55419a415b8a968b1f14d70a25c7. --- frappe/public/less/page.less | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/frappe/public/less/page.less b/frappe/public/less/page.less index c0609f8395..d051a63b00 100644 --- a/frappe/public/less/page.less +++ b/frappe/public/less/page.less @@ -109,17 +109,6 @@ .layout-main-section { border: 1px solid @border-color; border-top: 0px; - // hack - // https://stackoverflow.com/a/49038292/5955589 - &::before { - content: ''; - position: absolute; - top: 0; - right: 15px; - bottom: 0; - left: 15px; - border: 1px solid @border-color; - } } .layout-main-section-wrapper { From 362d31c231deba68c59edc1535339a0db2d5fe6d Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Mon, 29 Apr 2019 19:57:03 +0550 Subject: [PATCH 062/176] bumped to version 11.1.25 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 8a4fe507c5..e88e0d3328 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -24,7 +24,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '11.1.24' +__version__ = '11.1.25' __title__ = "Frappe Framework" local = Local() From 0ef3130ebd58313862c84edc685954a3fc294636 Mon Sep 17 00:00:00 2001 From: cameron Date: Mon, 29 Apr 2019 22:59:52 +0800 Subject: [PATCH 063/176] Initial update to ldap3 --- .../doctype/ldap_settings/ldap_settings.json | 844 +++++++++++------- .../doctype/ldap_settings/ldap_settings.py | 224 ++--- frappe/templates/includes/login/login.js | 2 +- frappe/www/login.py | 5 +- 4 files changed, 620 insertions(+), 455 deletions(-) diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.json b/frappe/integrations/doctype/ldap_settings/ldap_settings.json index 6eb44a2db8..aa43b2e9d0 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.json +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.json @@ -1,317 +1,363 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-09-22 04:16:48.829658", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "System", - "editable_grid": 1, + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-09-22 04:16:48.829658", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "System", + "editable_grid": 1, "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "enabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Enabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ldap_server_url", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "LDAP Server Url", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "organizational_unit", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Organizational Unit", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_dn", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Base Distinguished Name (DN)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "password", - "fieldtype": "Password", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Password for Base DN", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ldap_search_string", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "LDAP Search String", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ldap_first_name_field", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "LDAP First Name Field", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ldap_email_field", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "LDAP Email Field", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ldap_username_field", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "LDAP Username Field", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, + "fieldname": "enabled", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Enabled", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "ldap_server_url", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "LDAP Server Url", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "organizational_unit", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Organizational Unit", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "base_dn", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Base Distinguished Name (DN)", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "password", + "fieldtype": "Password", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Password for Base DN", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "ldap_search_string", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "LDAP Search String", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "ldap_first_name_field", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "LDAP First Name Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "ldap_email_field", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "LDAP Email Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "ldap_username_field", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "LDAP Username Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "ldap_security", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "LDAP Security", "length": 0, "no_copy": 0, @@ -325,22 +371,28 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, "default": "Off", "description": "", + "fetch_if_empty": 0, "fieldname": "ssl_tls_mode", "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "SSL/TLS Mode", "length": 0, "no_copy": 0, @@ -355,21 +407,27 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, "default": "No", + "fetch_if_empty": 0, "fieldname": "require_trusted_certificate", "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Require Trusted Certificate", "length": 0, "no_copy": 0, @@ -384,53 +442,153 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "local_private_key_file", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Path to private Key File", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "local_server_certificate_file", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Path to Server Certificate", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "local_ca_certs_file", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Path to CA Certs File", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 1, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2019-01-30 11:02:41.011412", - "modified_by": "Administrator", - "module": "Integrations", - "name": "LDAP Settings", - "name_case": "", - "owner": "Administrator", + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 1, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2019-04-29 10:56:42.322696", + "modified_by": "Administrator", + "module": "Integrations", + "name": "LDAP Settings", + "name_case": "", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 } - ], - "quick_entry": 0, - "read_only": 1, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "quick_entry": 0, + "read_only": 1, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index e12a6fce05..aa5b945131 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -5,136 +5,144 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import cstr from frappe.model.document import Document + class LDAPSettings(Document): - def validate(self): - if not self.flags.ignore_mandatory: - self.validate_ldap_credentails() + def validate(self): + if not self.flags.ignore_mandatory: + if self.ldap_search_string.endswith("={0}"): + if self.enabled: + connect_to_ldap(server_url=self.ldap_server_url, + base_dn=self.base_dn, + password=self.get_password(raise_exception=False), + ssl_tls_mode=self.ssl_tls_mode, + trusted_cert=self.require_trusted_certificate) + else: + frappe.throw(_("LDAP Search String needs to end with a placeholder, eg sAMAccountName={0}")) - def validate_ldap_credentails(self): - try: - import ldap - conn = ldap.initialize(self.ldap_server_url) - try: - if self.ssl_tls_mode == 'StartTLS': - conn.set_option(ldap.OPT_X_TLS_DEMAND, True) - if self.require_trusted_certificate == 'Yes': - conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND) - conn.start_tls_s() - except: - frappe.throw(_("StartTLS is not supported")) - conn.simple_bind_s(self.base_dn, self.get_password(raise_exception=False)) - except ImportError: - msg = """ -
- {{_("Seems ldap is not installed on system.
Guidelines to install ldap dependancies and python package")}}, - {{_("Click here")}}, -
- """ - frappe.throw(msg, title=_("LDAP Not Installed")) +def get_ldap_client_settings(): + # return the settings to be used on the client side. + result = { + "enabled": False + } + settings = frappe.get_doc("LDAP Settings") - except ldap.LDAPError: - conn.unbind_s() - frappe.throw(_("Incorrect UserId or Password")) + if settings and settings.enabled: + result["enabled"] = True + result["method"] = "frappe.integrations.doctype.ldap_settings.ldap_settings.login" + return result -def get_ldap_settings(): - try: - settings = frappe.get_doc("LDAP Settings") - settings.update({ - "method": "frappe.integrations.doctype.ldap_settings.ldap_settings.login" - }) - return settings - except Exception: - # this will return blank settings - return frappe._dict() +def connect_to_ldap(server_url, + base_dn, + password, + ssl_tls_mode, + trusted_cert): + try: + import ldap3 + import ssl + + if trusted_cert == 'Yes': + tls_configuration = ldap3.Tls(validate=ssl.CERT_REQUIRED, + version=ssl.PROTOCOL_TLSv1) + else: + tls_configuration = ldap3.Tls(validate=ssl.CERT_NONE, + version=ssl.PROTOCOL_TLSv1) + + server = ldap3.Server(host=server_url, + tls=tls_configuration) + bind_type = ldap3.AUTO_BIND_TLS_BEFORE_BIND if ssl_tls_mode == "StartTLS" else True + + conn = ldap3.Connection(server=server, + user=base_dn, + password=password, + auto_bind=bind_type, + read_only=True, + raise_exceptions=True) + + return conn + + except ImportError: + msg = _("Please Install the ldap3 library via pip to use ldap functionality.") + frappe.throw(msg, title=_("LDAP Not Installed")) + except ldap3.core.exceptions.LDAPInvalidCredentialsResult: + frappe.throw(_("Invalid Credentials")) + except Exception as ex: + frappe.throw(_(str(ex))) + @frappe.whitelist(allow_guest=True) def login(): - #### LDAP LOGIN LOGIC ##### - args = frappe.form_dict - user = authenticate_ldap_user(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd)) + # LDAP LOGIN LOGIC + args = frappe.form_dict + user = authenticate_ldap_user(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd)) - frappe.local.login_manager.user = user.name - frappe.local.login_manager.post_login() + frappe.local.login_manager.user = user.name + frappe.local.login_manager.post_login() - # because of a GET request! - frappe.db.commit() + # because of a GET request! + frappe.db.commit() -def authenticate_ldap_user(user=None, password=None): - dn = None - params = {} - settings = get_ldap_settings() - try: - import ldap - except: - msg = """ -
- {{_("Seems ldap is not installed on system.")}}
- {{_("Click here")}}, - {{_("Guidelines to install ldap dependancies and python")}} -
- """ - frappe.throw(msg, title=_("LDAP Not Installed")) +def authenticate_ldap_user(user=None, + password=None): - conn = ldap.initialize(settings.ldap_server_url) + params = {} + settings = frappe.get_doc("LDAP Settings") + if settings and settings.enabled: + import ldap3 - try: - try: - # set TLS settings for secure connection - if settings.ssl_tls_mode == 'StartTLS': - conn.set_option(ldap.OPT_X_TLS_DEMAND, True) - if settings.require_trusted_certificate == 'Yes': - conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND) - conn.start_tls_s() - except: - frappe.throw(_("StartTLS is not supported")) + conn = connect_to_ldap(server_url=settings.ldap_server_url, + base_dn=settings.base_dn, + password=settings.get_password(raise_exception=False), + ssl_tls_mode=settings.ssl_tls_mode, + trusted_cert=settings.require_trusted_certificate) - # simple_bind_s is synchronous binding to server, it takes two param DN and password - conn.simple_bind_s(settings.base_dn, settings.get_password(raise_exception=False)) + filter = settings.ldap_search_string.format(user) + conn.search(search_base=settings.organizational_unit, + search_filter="({0})".format(filter), + attributes=[settings.ldap_email_field, + settings.ldap_username_field, + settings.ldap_first_name_field]) - #search for surnames beginning with a - #available options for how deep a search you want. - #LDAP_SCOPE_BASE, LDAP_SCOPE_ONELEVEL,LDAP_SCOPE_SUBTREE, - result = conn.search_s(settings.organizational_unit, ldap.SCOPE_SUBTREE, - settings.ldap_search_string.format(user)) + if len(conn.entries) > 0 and conn.entries[0]: + user = conn.entries[0] + params["email"] = str(user[settings.ldap_email_field]) + params["username"] = str(user[settings.ldap_username_field]) + params["first_name"] = str(user[settings.ldap_first_name_field]) + connect_to_ldap(server_url=settings.ldap_server_url, + base_dn=user.entry_dn, + password=frappe.as_unicode(password), + ssl_tls_mode=settings.ssl_tls_mode, + trusted_cert=settings.require_trusted_certificate) + return create_user(params) + else: + frappe.throw(_("Not a valid LDAP user")) + else: + frappe.throw(_("LDAP is not enabled.")) - for dn, r in result: - dn = cstr(dn) - params["email"] = cstr(r[settings.ldap_email_field][0]) - params["username"] = cstr(r[settings.ldap_username_field][0]) - params["first_name"] = cstr(r[settings.ldap_first_name_field][0]) - - if dn: - conn.simple_bind_s(dn, frappe.as_unicode(password)) - return create_user(params) - else: - frappe.throw(_("Not a valid LDAP user")) - - except ldap.LDAPError: - conn.unbind_s() - frappe.throw(_("Incorrect UserId or Password")) def create_user(params): - if frappe.db.exists("User", params["email"]): - return frappe.get_doc("User", params["email"]) + if frappe.db.exists("User", params["email"]): + user = frappe.get_doc("User", params["email"]) + user.first_name = params["first_name"] + user.username = params["username"] + user.save(ignore_permissions=True) + return user - else: - params.update({ - "doctype": "User", - "send_welcome_email": 0, - "language": "", - "user_type": "System User", - "roles": [{ - "role": _("Blogger") - }] - }) + else: + params.update({ + "doctype": "User", + "send_welcome_email": 0, + "language": "", + "user_type": "System User", + "roles": [{ + "role": _("Blogger") + }] + }) - user = frappe.get_doc(params).insert(ignore_permissions=True) - frappe.db.commit() + user = frappe.get_doc(params).insert(ignore_permissions=True) - return user + return user diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js index dd0f57eb4c..992051bc45 100644 --- a/frappe/templates/includes/login/login.js +++ b/frappe/templates/includes/login/login.js @@ -66,7 +66,7 @@ login.bind_events = function() { } }); - {% if ldap_settings %} + {% if ldap_settings.enabled %} $(".btn-ldap-login").on("click", function(){ var args = {}; args.cmd = "{{ ldap_settings.method }}"; diff --git a/frappe/www/login.py b/frappe/www/login.py index c2f83e45c3..793b57d28a 100644 --- a/frappe/www/login.py +++ b/frappe/www/login.py @@ -8,7 +8,7 @@ from frappe.utils.oauth import get_oauth2_authorize_url, get_oauth_keys, login_v import json from frappe import _ from frappe.auth import LoginManager -from frappe.integrations.doctype.ldap_settings.ldap_settings import get_ldap_settings +from frappe.integrations.doctype.ldap_settings.ldap_settings import get_ldap_client_settings from frappe.utils.password import get_decrypted_password from frappe.utils.html_utils import get_icon_html @@ -38,8 +38,7 @@ def get_context(context): "icon": icon }) context["social_login"] = True - - ldap_settings = get_ldap_settings() + ldap_settings = get_ldap_client_settings() context["ldap_settings"] = ldap_settings login_name_placeholder = [_("Email address")] From eb9d2e6182c196844dee98c1033bef98205fa758 Mon Sep 17 00:00:00 2001 From: cameron Date: Mon, 29 Apr 2019 23:21:50 +0800 Subject: [PATCH 064/176] add tests for cert files. --- .../doctype/ldap_settings/ldap_settings.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index aa5b945131..1d258d4bd5 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -17,7 +17,10 @@ class LDAPSettings(Document): base_dn=self.base_dn, password=self.get_password(raise_exception=False), ssl_tls_mode=self.ssl_tls_mode, - trusted_cert=self.require_trusted_certificate) + trusted_cert=self.require_trusted_certificate, + private_key_file=self.local_private_key_file, + server_cert_file=self.local_server_certificate_file, + ca_certs_file=self.local_ca_certs_file) else: frappe.throw(_("LDAP Search String needs to end with a placeholder, eg sAMAccountName={0}")) @@ -39,7 +42,10 @@ def connect_to_ldap(server_url, base_dn, password, ssl_tls_mode, - trusted_cert): + trusted_cert, + private_key_file, + server_cert_file, + ca_certs_file): try: import ldap3 import ssl @@ -51,6 +57,13 @@ def connect_to_ldap(server_url, tls_configuration = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1) + if private_key_file: + tls_configuration.private_key_file = private_key_file + if server_cert_file: + tls_configuration.certificate_file = server_cert_file + if ca_certs_file: + tls_configuration.ca_certs_file = ca_certs_file + server = ldap3.Server(host=server_url, tls=tls_configuration) bind_type = ldap3.AUTO_BIND_TLS_BEFORE_BIND if ssl_tls_mode == "StartTLS" else True @@ -98,7 +111,11 @@ def authenticate_ldap_user(user=None, base_dn=settings.base_dn, password=settings.get_password(raise_exception=False), ssl_tls_mode=settings.ssl_tls_mode, - trusted_cert=settings.require_trusted_certificate) + trusted_cert=settings.require_trusted_certificate, + private_key_file=settings.local_private_key_file, + server_cert_file=settings.local_server_certificate_file, + ca_certs_file=settings.local_ca_certs_file + ) filter = settings.ldap_search_string.format(user) conn.search(search_base=settings.organizational_unit, @@ -116,7 +133,11 @@ def authenticate_ldap_user(user=None, base_dn=user.entry_dn, password=frappe.as_unicode(password), ssl_tls_mode=settings.ssl_tls_mode, - trusted_cert=settings.require_trusted_certificate) + trusted_cert=settings.require_trusted_certificate, + private_key_file=settings.local_private_key_file, + server_cert_file=settings.local_server_certificate_file, + ca_certs_file=settings.local_ca_certs_file + ) return create_user(params) else: frappe.throw(_("Not a valid LDAP user")) From c4ea153d4fed218725e49ae4bb537dfa1e52b1ff Mon Sep 17 00:00:00 2001 From: cameron Date: Tue, 30 Apr 2019 09:53:44 +0800 Subject: [PATCH 065/176] remove ldap3 import, cleanup syntax --- .../integrations/doctype/ldap_settings/ldap_settings.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index 1d258d4bd5..c9799c9867 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -105,8 +105,6 @@ def authenticate_ldap_user(user=None, params = {} settings = frappe.get_doc("LDAP Settings") if settings and settings.enabled: - import ldap3 - conn = connect_to_ldap(server_url=settings.ldap_server_url, base_dn=settings.base_dn, password=settings.get_password(raise_exception=False), @@ -114,12 +112,11 @@ def authenticate_ldap_user(user=None, trusted_cert=settings.require_trusted_certificate, private_key_file=settings.local_private_key_file, server_cert_file=settings.local_server_certificate_file, - ca_certs_file=settings.local_ca_certs_file - ) + ca_certs_file=settings.local_ca_certs_file) - filter = settings.ldap_search_string.format(user) + user_filter = settings.ldap_search_string.format(user) conn.search(search_base=settings.organizational_unit, - search_filter="({0})".format(filter), + search_filter="({0})".format(user_filter), attributes=[settings.ldap_email_field, settings.ldap_username_field, settings.ldap_first_name_field]) From 7ed00ab60d458a5e0769649416c6491b6e6d27c1 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 1 May 2019 14:40:11 +0530 Subject: [PATCH 066/176] fix(UX): Don't set root_label as page title in tree (#7386) Root label is already shown in the Tree and in some cases the filter --- frappe/public/js/frappe/views/treeview.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/views/treeview.js b/frappe/public/js/frappe/views/treeview.js index 2b76e135cf..707e7bd8c8 100644 --- a/frappe/public/js/frappe/views/treeview.js +++ b/frappe/public/js/frappe/views/treeview.js @@ -107,11 +107,10 @@ frappe.views.TreeView = Class.extend({ me.args[filter.fieldname] = val; if (val) { me.root_label = val; - me.page.set_title(val); } else { me.root_label = me.opts.root_label; - me.set_title(); } + me.set_title(); me.make_tree(); } } From b63a80cc890da2813073a8d62d9e2adb085f14c0 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 1 May 2019 14:41:06 +0530 Subject: [PATCH 067/176] fix: Set user language in print preview (#7384) The User language was set in the select control but the value didn't pass correctly in the request, because `this.lang_code` wasn't set. We also abort duplicate requests. --- frappe/public/js/frappe/form/print.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/form/print.js b/frappe/public/js/frappe/form/print.js index f0a4226444..64ff603d8b 100644 --- a/frappe/public/js/frappe/form/print.js +++ b/frappe/public/js/frappe/form/print.js @@ -114,13 +114,8 @@ frappe.ui.form.PrintPreview = Class.extend({ }, set_default_print_language: function () { var print_format = this.get_print_format(); - - if (print_format.default_print_language) { - this.lang_code = print_format.default_print_language; - this.language_sel.val(this.lang_code); - } else { - this.language_sel.val(frappe.boot.lang); - } + this.lang_code = print_format.default_print_format || frappe.boot.lang; + this.language_sel.val(this.lang_code); }, multilingual_preview: function () { var me = this; @@ -210,7 +205,10 @@ frappe.ui.form.PrintPreview = Class.extend({ } }, get_print_html: function (callback) { - frappe.call({ + if (this._req) { + this._req.abort(); + } + this._req = frappe.call({ method: "frappe.www.printview.get_html_and_style", args: { doc: this.frm.doc, From 3522593e5efb3ea54b348cb9bda5d0e77615cff4 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 1 May 2019 14:44:34 +0530 Subject: [PATCH 068/176] patch: remove user perm for page and report doctype (#7382) * fix: status options for integration request * patch: remove user perm for page and report doctype --- .../integration_request.json | 54 ++++++++++++++++--- frappe/patches.txt | 2 +- ...pe_user_permissions_for_page_and_report.py | 3 +- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/frappe/integrations/doctype/integration_request/integration_request.json b/frappe/integrations/doctype/integration_request/integration_request.json index f69668973e..c3123fb574 100644 --- a/frappe/integrations/doctype/integration_request/integration_request.json +++ b/frappe/integrations/doctype/integration_request/integration_request.json @@ -1,5 +1,7 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, "beta": 0, @@ -12,16 +14,20 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "integration_type", "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Integration Type", @@ -38,19 +44,24 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "integration_request_service", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Integration Request Service", @@ -67,26 +78,31 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, "default": "Queued", + "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Status", "length": 0, "no_copy": 0, - "options": "\nQueued\nAuthorized\nCompleted\nCancelled\nFailed\n", + "options": "\nQueued\nAuthorized\nCompleted\nCancelled\nFailed", "permlevel": 0, "precision": "", "print_hide": 0, @@ -97,19 +113,24 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "data", "fieldtype": "Code", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Data", @@ -125,19 +146,24 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "output", "fieldtype": "Code", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Output", @@ -153,19 +179,24 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "error", "fieldtype": "Code", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Error", @@ -181,19 +212,24 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "reference_doctype", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Reference Doctype", @@ -210,19 +246,24 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "reference_docname", "fieldtype": "Dynamic Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Reference Docname", @@ -239,20 +280,21 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 0, "image_view": 0, "in_create": 1, - "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-10-09 14:40:00.783063", + "modified": "2019-04-25 16:38:21.084580", "modified_by": "Administrator", "module": "Integrations", "name": "Integration Request", @@ -261,7 +303,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -269,7 +310,6 @@ "export": 1, "if_owner": 0, "import": 0, - "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -284,9 +324,11 @@ "quick_entry": 0, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", "title_field": "integration_request_service", "track_changes": 1, - "track_seen": 0 + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/frappe/patches.txt b/frappe/patches.txt index fdd032131d..b085379e09 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -235,4 +235,4 @@ frappe.patches.v11_0.delete_all_prepared_reports frappe.patches.v11_0.fix_order_by_in_reports_json execute:frappe.delete_doc('Page', 'applications', ignore_missing=True) frappe.patches.v11_0.set_missing_creation_and_modified_value_for_user_permissions -frappe.patches.v11_0.remove_doctype_user_permissions_for_page_and_report \ No newline at end of file +frappe.patches.v11_0.remove_doctype_user_permissions_for_page_and_report #2019-05-01 \ No newline at end of file diff --git a/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py b/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py index c1dc1b79be..e2c2ef5f0e 100644 --- a/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py +++ b/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py @@ -5,5 +5,4 @@ from __future__ import unicode_literals import frappe def execute(): - if frappe.db.table_exists('User Permission for Page and Report'): - frappe.delete_doc("DocType", "User Permission for Page and Report") \ No newline at end of file + frappe.delete_doc_if_exists("DocType", "User Permission for Page and Report") \ No newline at end of file From 49d2d838ad5e453492d8c87bf809e16b438c9c5f Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Wed, 1 May 2019 15:32:07 +0550 Subject: [PATCH 069/176] bumped to version 11.1.26 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index e88e0d3328..544e1cf1b0 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -24,7 +24,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '11.1.25' +__version__ = '11.1.26' __title__ = "Frappe Framework" local = Local() From e5fbf7d438786372354faf852ee2e116541064f6 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 2 May 2019 15:05:06 +0530 Subject: [PATCH 070/176] fix: frappe.utils.add_to_date added parameter seconds to add and subtract seconds (#7393) --- frappe/utils/data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 8e5b89f1ef..ad7cf3c94a 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -70,7 +70,7 @@ def to_timedelta(time_str): else: return time_str -def add_to_date(date, years=0, months=0, days=0, hours=0, as_string=False, as_datetime=False): +def add_to_date(date, years=0, months=0, days=0, hours=0, seconds=0, as_string=False, as_datetime=False): """Adds `days` to the given date""" from dateutil.relativedelta import relativedelta @@ -86,7 +86,7 @@ def add_to_date(date, years=0, months=0, days=0, hours=0, as_string=False, as_da as_datetime = True date = parser.parse(date) - date = date + relativedelta(years=years, months=months, days=days, hours=hours) + date = date + relativedelta(years=years, months=months, days=days, hours=hours, seconds=seconds) if as_string: if as_datetime: From b14d6e76b1f421d36d04b74d74848e988a6cf73c Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 2 May 2019 19:35:07 +0530 Subject: [PATCH 071/176] fix: Do not show locals to user (#7396) --- frappe/__init__.py | 3 +-- frappe/public/js/frappe/request.js | 6 ------ frappe/utils/error.py | 12 ------------ frappe/utils/response.py | 2 -- 4 files changed, 1 insertion(+), 22 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 544e1cf1b0..8fab534286 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -16,7 +16,6 @@ from faker import Faker # public from .exceptions import * from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader) -from .utils.error import get_frame_locals # Hamless for Python 3 # For Python 2 set default encoding to utf-8 @@ -274,7 +273,7 @@ def errprint(msg): if not request or (not "cmd" in local.form_dict) or conf.developer_mode: print(msg.encode('utf-8')) - error_log.append({"exc": msg, "locals": get_frame_locals()}) + error_log.append({"exc": msg}) def log(msg): """Add to `debug_log`. diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index ce862fd25b..10667bea33 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -375,12 +375,9 @@ frappe.request.report_error = function(xhr, request_opts) { var data = JSON.parse(xhr.responseText); if (data.exc) { var exc = (JSON.parse(data.exc) || []).join("\n"); - var locals = (JSON.parse(data.locals) || []).join("\n"); delete data.exc; - delete data.locals; } else { var exc = ""; - locals = ""; } if (exc) { @@ -412,9 +409,6 @@ frappe.request.report_error = function(xhr, request_opts) { '
Error Report
', '
' + exc + '
', '
', - '
Locals
', - '
' + locals + '
', - '
', '
Request Data
', '
' + JSON.stringify(request_opts, null, "\t") + '
', '
', diff --git a/frappe/utils/error.py b/frappe/utils/error.py index 4f826ce126..f249355ee5 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -199,15 +199,3 @@ def clear_old_snapshots(): def get_error_snapshot_path(): return frappe.get_site_path('error-snapshots') - -def get_frame_locals(): - traceback = sys.exc_info()[2] - frames = [] - if traceback: - frames = inspect.getinnerframes(traceback, context=0) - _locals = ['Locals (most recent call last):'] - for frame, filename, lineno, function, __, __ in frames: - if '/apps/' in filename: - _locals.append('File "{}", line {}, in {}\n{}'.format(filename, lineno, function, json.dumps(frame.f_locals, default=str, indent=4))) - - return '\n'.join(_locals) diff --git a/frappe/utils/response.py b/frappe/utils/response.py index ca563e5a3b..fa105b3a01 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -106,8 +106,6 @@ def make_logs(response = None): if frappe.error_log: response['exc'] = json.dumps([frappe.utils.cstr(d["exc"]) for d in frappe.local.error_log]) - if frappe.conf.developer_mode: - response['locals'] = json.dumps([frappe.utils.cstr(d["locals"]) for d in frappe.local.error_log]) if frappe.local.message_log: response['_server_messages'] = json.dumps([frappe.utils.cstr(d) for From 42045552ac6ab5fd14edc95594f826c10a51c754 Mon Sep 17 00:00:00 2001 From: cameron Date: Fri, 3 May 2019 23:28:23 +0800 Subject: [PATCH 072/176] convert spaces to tabs --- .../doctype/ldap_settings/ldap_settings.py | 232 +++++++++--------- 1 file changed, 116 insertions(+), 116 deletions(-) diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index c9799c9867..0a4d871be8 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -9,33 +9,33 @@ from frappe.model.document import Document class LDAPSettings(Document): - def validate(self): - if not self.flags.ignore_mandatory: - if self.ldap_search_string.endswith("={0}"): - if self.enabled: - connect_to_ldap(server_url=self.ldap_server_url, - base_dn=self.base_dn, - password=self.get_password(raise_exception=False), - ssl_tls_mode=self.ssl_tls_mode, - trusted_cert=self.require_trusted_certificate, - private_key_file=self.local_private_key_file, - server_cert_file=self.local_server_certificate_file, - ca_certs_file=self.local_ca_certs_file) - else: - frappe.throw(_("LDAP Search String needs to end with a placeholder, eg sAMAccountName={0}")) + def validate(self): + if not self.flags.ignore_mandatory: + if self.ldap_search_string.endswith("={0}"): + if self.enabled: + connect_to_ldap(server_url=self.ldap_server_url, + base_dn=self.base_dn, + password=self.get_password(raise_exception=False), + ssl_tls_mode=self.ssl_tls_mode, + trusted_cert=self.require_trusted_certificate, + private_key_file=self.local_private_key_file, + server_cert_file=self.local_server_certificate_file, + ca_certs_file=self.local_ca_certs_file) + else: + frappe.throw(_("LDAP Search String needs to end with a placeholder, eg sAMAccountName={0}")) def get_ldap_client_settings(): - # return the settings to be used on the client side. - result = { - "enabled": False - } - settings = frappe.get_doc("LDAP Settings") + #return the settings to be used on the client side. + result = { + "enabled": False + } + settings = frappe.get_doc("LDAP Settings") - if settings and settings.enabled: - result["enabled"] = True - result["method"] = "frappe.integrations.doctype.ldap_settings.ldap_settings.login" - return result + if settings and settings.enabled: + result["enabled"] = True + result["method"] = "frappe.integrations.doctype.ldap_settings.ldap_settings.login" + return result def connect_to_ldap(server_url, @@ -46,121 +46,121 @@ def connect_to_ldap(server_url, private_key_file, server_cert_file, ca_certs_file): - try: - import ldap3 - import ssl + try: + import ldap3 + import ssl - if trusted_cert == 'Yes': - tls_configuration = ldap3.Tls(validate=ssl.CERT_REQUIRED, - version=ssl.PROTOCOL_TLSv1) - else: - tls_configuration = ldap3.Tls(validate=ssl.CERT_NONE, - version=ssl.PROTOCOL_TLSv1) + if trusted_cert == 'Yes': + tls_configuration = ldap3.Tls(validate=ssl.CERT_REQUIRED, + version=ssl.PROTOCOL_TLSv1) + else: + tls_configuration = ldap3.Tls(validate=ssl.CERT_NONE, + version=ssl.PROTOCOL_TLSv1) - if private_key_file: - tls_configuration.private_key_file = private_key_file - if server_cert_file: - tls_configuration.certificate_file = server_cert_file - if ca_certs_file: - tls_configuration.ca_certs_file = ca_certs_file + if private_key_file: + tls_configuration.private_key_file = private_key_file + if server_cert_file: + tls_configuration.certificate_file = server_cert_file + if ca_certs_file: + tls_configuration.ca_certs_file = ca_certs_file - server = ldap3.Server(host=server_url, - tls=tls_configuration) - bind_type = ldap3.AUTO_BIND_TLS_BEFORE_BIND if ssl_tls_mode == "StartTLS" else True + server = ldap3.Server(host=server_url, + tls=tls_configuration) + bind_type = ldap3.AUTO_BIND_TLS_BEFORE_BIND if ssl_tls_mode == "StartTLS" else True - conn = ldap3.Connection(server=server, - user=base_dn, - password=password, - auto_bind=bind_type, - read_only=True, - raise_exceptions=True) + conn = ldap3.Connection(server=server, + user=base_dn, + password=password, + auto_bind=bind_type, + read_only=True, + raise_exceptions=True) - return conn + return conn - except ImportError: - msg = _("Please Install the ldap3 library via pip to use ldap functionality.") - frappe.throw(msg, title=_("LDAP Not Installed")) - except ldap3.core.exceptions.LDAPInvalidCredentialsResult: - frappe.throw(_("Invalid Credentials")) - except Exception as ex: - frappe.throw(_(str(ex))) + except ImportError: + msg = _("Please Install the ldap3 library via pip to use ldap functionality.") + frappe.throw(msg, title=_("LDAP Not Installed")) + except ldap3.core.exceptions.LDAPInvalidCredentialsResult: + frappe.throw(_("Invalid Credentials")) + except Exception as ex: + frappe.throw(_(str(ex))) @frappe.whitelist(allow_guest=True) def login(): - # LDAP LOGIN LOGIC - args = frappe.form_dict - user = authenticate_ldap_user(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd)) + # LDAP LOGIN LOGIC + args = frappe.form_dict + user = authenticate_ldap_user(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd)) - frappe.local.login_manager.user = user.name - frappe.local.login_manager.post_login() + frappe.local.login_manager.user = user.name + frappe.local.login_manager.post_login() - # because of a GET request! - frappe.db.commit() + # because of a GET request! + frappe.db.commit() def authenticate_ldap_user(user=None, password=None): - params = {} - settings = frappe.get_doc("LDAP Settings") - if settings and settings.enabled: - conn = connect_to_ldap(server_url=settings.ldap_server_url, - base_dn=settings.base_dn, - password=settings.get_password(raise_exception=False), - ssl_tls_mode=settings.ssl_tls_mode, - trusted_cert=settings.require_trusted_certificate, - private_key_file=settings.local_private_key_file, - server_cert_file=settings.local_server_certificate_file, - ca_certs_file=settings.local_ca_certs_file) + params = {} + settings = frappe.get_doc("LDAP Settings") + if settings and settings.enabled: + conn = connect_to_ldap(server_url=settings.ldap_server_url, + base_dn=settings.base_dn, + password=settings.get_password(raise_exception=False), + ssl_tls_mode=settings.ssl_tls_mode, + trusted_cert=settings.require_trusted_certificate, + private_key_file=settings.local_private_key_file, + server_cert_file=settings.local_server_certificate_file, + ca_certs_file=settings.local_ca_certs_file) - user_filter = settings.ldap_search_string.format(user) - conn.search(search_base=settings.organizational_unit, - search_filter="({0})".format(user_filter), - attributes=[settings.ldap_email_field, - settings.ldap_username_field, - settings.ldap_first_name_field]) + user_filter = settings.ldap_search_string.format(user) + conn.search(search_base=settings.organizational_unit, + search_filter="({0})".format(user_filter), + attributes=[settings.ldap_email_field, + settings.ldap_username_field, + settings.ldap_first_name_field]) - if len(conn.entries) > 0 and conn.entries[0]: - user = conn.entries[0] - params["email"] = str(user[settings.ldap_email_field]) - params["username"] = str(user[settings.ldap_username_field]) - params["first_name"] = str(user[settings.ldap_first_name_field]) - connect_to_ldap(server_url=settings.ldap_server_url, - base_dn=user.entry_dn, - password=frappe.as_unicode(password), - ssl_tls_mode=settings.ssl_tls_mode, - trusted_cert=settings.require_trusted_certificate, - private_key_file=settings.local_private_key_file, - server_cert_file=settings.local_server_certificate_file, - ca_certs_file=settings.local_ca_certs_file - ) - return create_user(params) - else: - frappe.throw(_("Not a valid LDAP user")) - else: - frappe.throw(_("LDAP is not enabled.")) + if len(conn.entries) > 0 and conn.entries[0]: + user = conn.entries[0] + params["email"] = str(user[settings.ldap_email_field]) + params["username"] = str(user[settings.ldap_username_field]) + params["first_name"] = str(user[settings.ldap_first_name_field]) + connect_to_ldap(server_url=settings.ldap_server_url, + base_dn=user.entry_dn, + password=frappe.as_unicode(password), + ssl_tls_mode=settings.ssl_tls_mode, + trusted_cert=settings.require_trusted_certificate, + private_key_file=settings.local_private_key_file, + server_cert_file=settings.local_server_certificate_file, + ca_certs_file=settings.local_ca_certs_file + ) + return create_user(params) + else: + frappe.throw(_("Not a valid LDAP user")) + else: + frappe.throw(_("LDAP is not enabled.")) def create_user(params): - if frappe.db.exists("User", params["email"]): - user = frappe.get_doc("User", params["email"]) - user.first_name = params["first_name"] - user.username = params["username"] - user.save(ignore_permissions=True) - return user + if frappe.db.exists("User", params["email"]): + user = frappe.get_doc("User", params["email"]) + user.first_name = params["first_name"] + user.username = params["username"] + user.save(ignore_permissions=True) + return user - else: - params.update({ - "doctype": "User", - "send_welcome_email": 0, - "language": "", - "user_type": "System User", - "roles": [{ - "role": _("Blogger") - }] - }) + else: + params.update({ + "doctype": "User", + "send_welcome_email": 0, + "language": "", + "user_type": "System User", + "roles": [{ + "role": _("Blogger") + }] + }) - user = frappe.get_doc(params).insert(ignore_permissions=True) + user = frappe.get_doc(params).insert(ignore_permissions=True) - return user + return user From 8aa0b5103d376938eee7924082307711ad9e17f3 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 May 2019 20:00:11 +0530 Subject: [PATCH 073/176] fix: link_title not getting set in address and contact --- frappe/contacts/doctype/address/address.py | 13 +++++++++++++ frappe/contacts/doctype/contact/contact.py | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index bfd63d2f80..c5de5706a8 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -39,6 +39,7 @@ class Address(Document): def validate(self): self.link_address() self.validate_reference() + self.set_link_title() deduplicate_dynamic_links(self) def link_address(self): @@ -53,6 +54,18 @@ class Address(Document): return False + def set_link_title(self): + if not self.links: + return + else: + for address in self.links: + if not address.link_title: + linked_doc = frappe.get_doc(address.link_doctype, address.link_name) + try: + address.link_title = linked_doc.title_field + except AttributeError: + address.link_title = linked_doc.name + def validate_reference(self): if self.is_your_company_address: if not [row for row in self.links if row.link_doctype == "Company"]: diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index e1117be4d7..82e2258bd5 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -31,6 +31,7 @@ class Contact(Document): if self.email_id: self.email_id = self.email_id.strip() self.set_user() + self.set_link_title() if self.email_id and not self.image: self.image = has_gravatar(self.email_id) @@ -40,6 +41,18 @@ class Contact(Document): if not self.user and self.email_id: self.user = frappe.db.get_value("User", {"email": self.email_id}) + def set_link_title(self): + if not self.links: + return + else: + for contact in self.links: + if not contact.link_title: + linked_doc = frappe.get_doc(contact.link_doctype, contact.link_name) + try: + contact.link_title = linked_doc.title_field + except AttributeError: + contact.link_title = linked_doc.name + def get_link_for(self, link_doctype): '''Return the link name, if exists for the given link DocType''' for link in self.links: From 22771008f9bacb4addd4bdaf2efae50bf765048f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 May 2019 20:05:39 +0530 Subject: [PATCH 074/176] feat: disable customize option for single doctypes --- frappe/custom/doctype/customize_form/customize_form.py | 3 +++ frappe/public/js/frappe/form/toolbar.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index a7b26f18c9..6c8096de96 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -89,6 +89,9 @@ class CustomizeForm(Document): if self.doc_type in core_doctypes_list: return frappe.msgprint(_("Core DocTypes cannot be customized.")) + if meta.issingle: + return frappe.msgprint(_("Single DocTypes cannot be customized.")) + if meta.custom: return frappe.msgprint(_("Only standard DocTypes are allowed to be customized from Customize Form.")) diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index 99d913f1cd..b4c7667afc 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -167,7 +167,7 @@ frappe.ui.form.Toolbar = Class.extend({ me.frm.savetrash();}, true); } - if(frappe.user_roles.includes("System Manager")) { + if(frappe.user_roles.includes("System Manager") && me.frm.meta.issingle === 0) { this.page.add_menu_item(__("Customize"), function() { frappe.set_route("Form", "Customize Form", { doc_type: me.frm.doctype From f1463ccc24322e42ea31c985b22b9ac2faf4ff15 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 7 May 2019 10:35:34 +0530 Subject: [PATCH 075/176] fix: create new from dashboard will not set the value in the dynamic link field --- frappe/public/js/frappe/model/model.js | 2 +- frappe/public/js/legacy/client_script_helpers.js | 7 ++++++- frappe/public/js/legacy/form.js | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index e687624577..8411e7d12a 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -382,7 +382,7 @@ $.extend(frappe.model, { tasks.push(() => frappe.model.trigger(key, value, doc)); } else { // execute link triggers (want to reselect to execute triggers) - if(fieldtype=="Link" && doc) { + if(in_list(["Link", "Dynamic Link"], fieldtype) && doc) { tasks.push(() => frappe.model.trigger(key, value, doc)); } } diff --git a/frappe/public/js/legacy/client_script_helpers.js b/frappe/public/js/legacy/client_script_helpers.js index 60baeb16bd..d9a2d59740 100644 --- a/frappe/public/js/legacy/client_script_helpers.js +++ b/frappe/public/js/legacy/client_script_helpers.js @@ -509,7 +509,12 @@ _f.Frm.prototype.make_new = function(doctype) { // set link fields (if found) frappe.get_meta(doctype).fields.forEach(function(df) { - if(df.fieldtype==='Link' && df.options===me.doctype) { + if(df.fieldtype==='Link' && df.options==="DocType") { + new_doc[df.fieldname] = me.doctype; + } + + if((df.fieldtype==='Link' && df.options===me.doctype) + || (df.fieldtype==='Dynamic Link' && new_doc[df.options] === me.doctype)) { new_doc[df.fieldname] = me.doc.name; } }); diff --git a/frappe/public/js/legacy/form.js b/frappe/public/js/legacy/form.js index 8fe4a6e347..aa2933b2b0 100644 --- a/frappe/public/js/legacy/form.js +++ b/frappe/public/js/legacy/form.js @@ -615,7 +615,7 @@ _f.Frm.prototype.trigger_link_fields = function() { // trigger link fields which have default values set if (this.is_new() && this.doc.__run_link_triggers) { $.each(this.fields_dict, function(fieldname, field) { - if (field.df.fieldtype=="Link" && this.doc[fieldname]) { + if (in_list(['Link', 'Dynamic Link'], field.df.fieldtype) && this.doc[fieldname]) { // triggers add fetch, sets value in model and runs triggers field.set_value(this.doc[fieldname]); } From 9b3ae050d0f96c65477bdf14c31ad903a7e78620 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 7 May 2019 14:21:45 +0530 Subject: [PATCH 076/176] fix: error reporting (#7404) * fix(minor): fix error reporting exception JSON handling * fix: dont show double messages --- frappe/desk/form/save.py | 2 -- frappe/public/js/frappe/request.js | 9 +++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/frappe/desk/form/save.py b/frappe/desk/form/save.py index 1e07f10ba7..d8da07a293 100644 --- a/frappe/desk/form/save.py +++ b/frappe/desk/form/save.py @@ -30,8 +30,6 @@ def savedocs(doc, action): frappe.get_user().update_recent(doc.doctype, doc.name) send_updated_docs(doc) except Exception: - if not frappe.local.message_log: - frappe.msgprint(frappe._('Did not save')) frappe.errprint(frappe.utils.get_traceback()) raise diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index 10667bea33..2b18684046 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -373,11 +373,16 @@ frappe.after_ajax = function(fn) { frappe.request.report_error = function(xhr, request_opts) { var data = JSON.parse(xhr.responseText); + var exc; if (data.exc) { - var exc = (JSON.parse(data.exc) || []).join("\n"); + try { + exc = (JSON.parse(data.exc) || []).join("\n"); + } catch (e) { + exc = data.exc; + } delete data.exc; } else { - var exc = ""; + exc = ""; } if (exc) { From 9bbb5b2b5db4381b3b5b6cc3b14f10efa563177d Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Tue, 7 May 2019 14:59:51 +0550 Subject: [PATCH 077/176] bumped to version 11.1.27 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 8fab534286..1e84785bc0 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '11.1.26' +__version__ = '11.1.27' __title__ = "Frappe Framework" local = Local() From a9bf40b7dbd8e6ce606c5e37f947c693b8a6d6ef Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 May 2019 15:24:59 +0530 Subject: [PATCH 078/176] chore: code improvements --- frappe/contacts/doctype/address/address.py | 12 ++++-------- frappe/contacts/doctype/contact/contact.py | 12 ++++-------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index c5de5706a8..c09f6a0c59 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -57,14 +57,10 @@ class Address(Document): def set_link_title(self): if not self.links: return - else: - for address in self.links: - if not address.link_title: - linked_doc = frappe.get_doc(address.link_doctype, address.link_name) - try: - address.link_title = linked_doc.title_field - except AttributeError: - address.link_title = linked_doc.name + for address in self.links: + if not address.link_title: + linked_doc = frappe.get_doc(address.link_doctype, address.link_name) + address.link_title = linked_doc.get("title_field") or linked_doc.get("name") def validate_reference(self): if self.is_your_company_address: diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index 82e2258bd5..3cd98c57e5 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -44,14 +44,10 @@ class Contact(Document): def set_link_title(self): if not self.links: return - else: - for contact in self.links: - if not contact.link_title: - linked_doc = frappe.get_doc(contact.link_doctype, contact.link_name) - try: - contact.link_title = linked_doc.title_field - except AttributeError: - contact.link_title = linked_doc.name + for contact in self.links: + if not contact.link_title: + linked_doc = frappe.get_doc(contact.link_doctype, contact.link_name) + contact.link_title = linked_doc.get("title_field") or linked_doc.get("name") def get_link_for(self, link_doctype): '''Return the link name, if exists for the given link DocType''' From 255dd6a89a7aba0929a039e38847a3449b172efd Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 May 2019 16:41:03 +0530 Subject: [PATCH 079/176] style: indendation fix --- frappe/contacts/doctype/address/address.py | 2 +- frappe/contacts/doctype/contact/contact.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index c09f6a0c59..fb2b9909fc 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -60,7 +60,7 @@ class Address(Document): for address in self.links: if not address.link_title: linked_doc = frappe.get_doc(address.link_doctype, address.link_name) - address.link_title = linked_doc.get("title_field") or linked_doc.get("name") + address.link_title = linked_doc.get("title_field") or linked_doc.get("name") def validate_reference(self): if self.is_your_company_address: diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index 3cd98c57e5..5c5581b294 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -47,7 +47,7 @@ class Contact(Document): for contact in self.links: if not contact.link_title: linked_doc = frappe.get_doc(contact.link_doctype, contact.link_name) - contact.link_title = linked_doc.get("title_field") or linked_doc.get("name") + contact.link_title = linked_doc.get("title_field") or linked_doc.get("name") def get_link_for(self, link_doctype): '''Return the link name, if exists for the given link DocType''' From 1b0dbb789eee2b8838e5521014614b49d873e8bf Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 8 May 2019 15:58:35 +0530 Subject: [PATCH 080/176] fix: enqueue prepared report after commiting prepared report record --- frappe/core/doctype/prepared_report/prepared_report.py | 2 +- frappe/desk/query_report.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index bc7050d3c4..cc087ae784 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -22,7 +22,7 @@ class PreparedReport(Document): self.status = "Queued" self.report_start_time = frappe.utils.now() - def after_insert(self): + def enqueue_report(self): enqueue( run_background, prepared_report=self.name, timeout=6000 diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 1f3cd752bc..0bd408524a 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -133,6 +133,8 @@ def background_enqueue_run(report_name, filters=None, user=None): }) track_instance.insert(ignore_permissions=True) frappe.db.commit() + track_instance.enqueue_report() + return { "name": track_instance.name, "redirect_url": get_url_to_form("Prepared Report", track_instance.name) From a42e18fcf1de007030f4c00a3ca73b47e0232932 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Wed, 8 May 2019 17:30:24 +0530 Subject: [PATCH 081/176] fix: add parameter minutes --- frappe/utils/data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index ad7cf3c94a..de790c6fff 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -70,7 +70,7 @@ def to_timedelta(time_str): else: return time_str -def add_to_date(date, years=0, months=0, days=0, hours=0, seconds=0, as_string=False, as_datetime=False): +def add_to_date(date, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, as_string=False, as_datetime=False): """Adds `days` to the given date""" from dateutil.relativedelta import relativedelta @@ -86,7 +86,7 @@ def add_to_date(date, years=0, months=0, days=0, hours=0, seconds=0, as_string=F as_datetime = True date = parser.parse(date) - date = date + relativedelta(years=years, months=months, days=days, hours=hours, seconds=seconds) + date = date + relativedelta(years=years, months=months, days=days, hours=hours, minutes=minutes, seconds=seconds) if as_string: if as_datetime: From cee3d77e8bab46eb2ef83eb47e8ecf3d73c165b3 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 9 May 2019 15:18:48 +0530 Subject: [PATCH 082/176] fix: Disallow editing standard print formats --- .../print_format_builder.js | 30 +++++-------- .../print_format_builder.py | 12 ++++++ frappe/public/js/frappe/form/print.js | 42 +++++++++++++------ 3 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 frappe/printing/page/print_format_builder/print_format_builder.py diff --git a/frappe/printing/page/print_format_builder/print_format_builder.js b/frappe/printing/page/print_format_builder/print_format_builder.js index 8e21ee4f50..e853035620 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder.js +++ b/frappe/printing/page/print_format_builder/print_format_builder.js @@ -12,10 +12,9 @@ frappe.pages['print-format-builder'].on_page_show = function(wrapper) { }); } else if(frappe.route_options) { if(frappe.route_options.make_new) { - var doctype = frappe.route_options.doctype; - var name = frappe.route_options.name; + let { doctype, name, based_on } = frappe.route_options; frappe.route_options = null; - frappe.print_format_builder.setup_new_print_format(doctype, name); + frappe.print_format_builder.setup_new_print_format(doctype, name, based_on); } else { frappe.print_format_builder.print_format = frappe.route_options.doc; frappe.route_options = null; @@ -130,23 +129,14 @@ frappe.PrintFormatBuilder = Class.extend({ }); }, - setup_new_print_format: function(doctype, name) { - var me = this; - frappe.call({ - method: "frappe.client.insert", - args: { - doc: { - doctype: "Print Format", - name: name, - standard: "No", - doc_type: doctype, - print_format_builder: 1 - } - }, - callback: function(r) { - me.print_format = r.message; - me.refresh(); - } + setup_new_print_format: function(doctype, name, based_on) { + frappe.call('frappe.printing.page.print_format_builder.print_format_builder.create_custom_format', { + doctype, + name, + based_on + }).then((r) => { + this.print_format = r.message; + this.refresh(); }); }, setup_print_format: function() { diff --git a/frappe/printing/page/print_format_builder/print_format_builder.py b/frappe/printing/page/print_format_builder/print_format_builder.py new file mode 100644 index 0000000000..17baa9314e --- /dev/null +++ b/frappe/printing/page/print_format_builder/print_format_builder.py @@ -0,0 +1,12 @@ +import frappe + +@frappe.whitelist() +def create_custom_format(doctype, name, based_on): + doc = frappe.new_doc('Print Format') + doc.doc_type = doctype + doc.name = name + doc.print_format_builder = 1 + doc.format_data = frappe.db.get_value('Print Format', based_on, 'format_data') \ + if based_on != 'Standard' else None + doc.insert() + return doc diff --git a/frappe/public/js/frappe/form/print.js b/frappe/public/js/frappe/form/print.js index f0a4226444..82a2788b0a 100644 --- a/frappe/public/js/frappe/form/print.js +++ b/frappe/public/js/frappe/form/print.js @@ -81,26 +81,42 @@ frappe.ui.form.PrintPreview = Class.extend({ this.wrapper.find(".btn-print-edit").on("click", function () { let print_format = me.get_print_format(); - if (print_format && print_format.name) { - if (print_format.print_format_builder) { - frappe.set_route("print-format-builder", print_format.name); - } else { - frappe.set_route("Form", "Print Format", print_format.name); + let is_custom_format = print_format.name + && print_format.print_format_builder + && print_format.standard === 'No'; + let is_standard_but_editable = print_format.name && print_format.custom_format; + + if (is_standard_but_editable) { + frappe.set_route("Form", "Print Format", print_format.name); + return; + } + if (is_custom_format) { + frappe.set_route("print-format-builder", print_format.name); + return; + } + // start a new print format + frappe.prompt([ + { + label: __("New Print Format Name"), + fieldname: "print_format_name", + fieldtype: "Data", + reqd: 1, + }, + { + label: __('Based On'), + fieldname: 'based_on', + fieldtype: 'Read Only', + default: print_format.name || 'Standard' } - } else { - // start a new print format - frappe.prompt({ - fieldname: "print_format_name", fieldtype: "Data", reqd: 1, - label: "New Print Format Name" - }, function (data) { + ], function (data) { frappe.route_options = { make_new: true, doctype: me.frm.doctype, - name: data.print_format_name + name: data.print_format_name, + based_on: data.based_on }; frappe.set_route("print-format-builder"); }, __("New Custom Print Format"), __("Start")); - } }); }, set_user_lang: function () { From 4f0f3b233f8b37e4697ec583cefd6cb358d4cb66 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 9 May 2019 15:46:30 +0530 Subject: [PATCH 083/176] fix: Add snyk file --- .snyk | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .snyk diff --git a/.snyk b/.snyk new file mode 100644 index 0000000000..f056697eb7 --- /dev/null +++ b/.snyk @@ -0,0 +1,8 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.13.3 +ignore: {} +# patches apply the minimum changes required to fix a vulnerability +patch: + 'npm:extend:20180424': + - superagent > extend: + patched: '2019-05-09T10:14:19.246Z' From 740812c0b49df8e35f83a39dc14a36ac3d988263 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 9 May 2019 15:49:22 +0530 Subject: [PATCH 084/176] fix: Text editor styles when readonly --- .../js/frappe/form/controls/text_editor.js | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js index 3e98528bfa..ef6afde262 100644 --- a/frappe/public/js/frappe/form/controls/text_editor.js +++ b/frappe/public/js/frappe/form/controls/text_editor.js @@ -38,24 +38,6 @@ class MyLink extends Link { Quill.register(MyLink); - -// hidden blot -class HiddenBlock extends Block { - static create(value) { - const node = super.create(value); - node.setAttribute('data-comment', value); - node.classList.add('hidden'); - return node; - } - - static formats(node) { - return node.getAttribute('data-comment'); - } -} -HiddenBlock.blotName = 'hiddenblot'; -HiddenBlock.tagName = 'SPAN'; -Quill.register(HiddenBlock, true); - // image uploader const Uploader = Quill.import('modules/uploader'); Uploader.DEFAULTS.mimetypes.push('image/gif'); @@ -73,6 +55,11 @@ Quill.register(AlignStyle, true); Quill.register(DirectionStyle, true); frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ + make_wrapper() { + this._super(); + this.$wrapper.find(".like-disabled-input").addClass("ql-editor"); + }, + make_input() { this.has_input = true; this.make_quill_editor(); From d07d3605b76b9d737f9ec211535f4f6751605998 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 3 May 2019 18:17:04 +0530 Subject: [PATCH 085/176] fix: naming and provision to use same db credentails for secondary --- frappe/__init__.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 1e84785bc0..3960351016 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -187,14 +187,19 @@ def connect(site=None, db_name=None): local.db = Database(user=db_name or local.conf.db_name) set_user("Administrator") -def connect_read_only(): - from frappe.database import Database +def connect_secondary(): + from frappe.database import get_db + user = local.conf.db_name + password = local.conf.db_password - local.read_only_db = Database(local.conf.slave_host, local.conf.slave_db_name, - local.conf.slave_db_password) + if local.conf.different_credentials_for_secondary: + user = local.conf.secondary_db_name + password = local.conf.secondary_db_password + + local.read_only_db = get_db(host=local.conf.secondary_host, user=user, password=password) # swap db connections - local.master_db = local.db + local.primary_db = local.db local.db = local.read_only_db def get_site_config(sites_path=None, site_path=None): @@ -495,16 +500,17 @@ def whitelist(allow_guest=False, xss_safe=False): def read_only(): def innfn(fn): def wrapper_fn(*args, **kwargs): - if conf.use_slave_for_read_only: - connect_read_only() + if conf.read_from_secondary: + connect_secondary() + try: retval = fn(*args, **get_newargs(fn, kwargs)) except: raise finally: - if local and hasattr(local, 'master_db'): + if local and hasattr(local, 'primary_db'): local.db.close() - local.db = local.master_db + local.db = local.primary_db return retval return wrapper_fn From 8586b885152d91d8d04117763fe68fce54acf93c Mon Sep 17 00:00:00 2001 From: Saurabh Date: Thu, 9 May 2019 20:31:27 +0530 Subject: [PATCH 086/176] fix: v11 compatible changes --- frappe/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 3960351016..e0f4e6ded8 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -188,7 +188,7 @@ def connect(site=None, db_name=None): set_user("Administrator") def connect_secondary(): - from frappe.database import get_db + from frappe.database import Database user = local.conf.db_name password = local.conf.db_password @@ -196,7 +196,7 @@ def connect_secondary(): user = local.conf.secondary_db_name password = local.conf.secondary_db_password - local.read_only_db = get_db(host=local.conf.secondary_host, user=user, password=password) + local.read_only_db = Database(host=local.conf.secondary_host, user=user, password=password) # swap db connections local.primary_db = local.db From 636efb70466a60ffc84d83e2f6bc7056f7843e9e Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 10 May 2019 10:48:45 +0530 Subject: [PATCH 087/176] fix: naming --- frappe/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index e0f4e6ded8..7e96653f64 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -187,20 +187,20 @@ def connect(site=None, db_name=None): local.db = Database(user=db_name or local.conf.db_name) set_user("Administrator") -def connect_secondary(): +def connect_replica(): from frappe.database import Database user = local.conf.db_name password = local.conf.db_password - if local.conf.different_credentials_for_secondary: - user = local.conf.secondary_db_name - password = local.conf.secondary_db_password + if local.conf.different_credentials_for_replica: + user = local.conf.replica_db_name + password = local.conf.replica_db_password - local.read_only_db = Database(host=local.conf.secondary_host, user=user, password=password) + local.replica_db = Database(host=local.conf.replica_host, user=user, password=password) # swap db connections local.primary_db = local.db - local.db = local.read_only_db + local.db = local.replica_db def get_site_config(sites_path=None, site_path=None): """Returns `site_config.json` combined with `sites/common_site_config.json`. @@ -500,8 +500,8 @@ def whitelist(allow_guest=False, xss_safe=False): def read_only(): def innfn(fn): def wrapper_fn(*args, **kwargs): - if conf.read_from_secondary: - connect_secondary() + if conf.read_from_replica: + connect_replica() try: retval = fn(*args, **get_newargs(fn, kwargs)) From efd5f52fdef27331624b169fedd48f279257d9d2 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 10 May 2019 15:59:59 +0530 Subject: [PATCH 088/176] fix(report): Allow report export only if user has export permission on ref doctype (#7433) * fix: Allow export only if user has export permission on reference doctype * fix: Show only custom "no permission" error --- frappe/desk/query_report.py | 4 ++++ frappe/model/rename_doc.py | 2 +- frappe/public/js/frappe/views/reports/query_report.js | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 0bd408524a..33fbe2fcf1 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -282,6 +282,10 @@ def export_query(): filters = json.loads(data["filters"]) if isinstance(data.get("report_name"), string_types): report_name = data["report_name"] + frappe.permissions.can_export( + frappe.get_cached_value('Report', report_name, 'ref_doctype'), + raise_exception=True + ) if isinstance(data.get("file_format_type"), string_types): file_format_type = data["file_format_type"] diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index ee5b589527..f17512248c 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -162,7 +162,7 @@ def validate_rename(doctype, new, meta, merge, force, ignore_permissions): if (not merge) and exists: frappe.msgprint(_("Another {0} with name {1} exists, select another name").format(doctype, new), raise_exception=1) - if not (ignore_permissions or frappe.has_permission(doctype, "write")): + if not (ignore_permissions or frappe.permissions.has_permission(doctype, "write", raise_exception=False)): frappe.msgprint(_("You need write permission to rename"), raise_exception=1) if not (force or ignore_permissions) and not meta.allow_rename: diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 2982ef4796..bc5e37427b 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -977,6 +977,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { { label: __('Export'), action: () => this.export_report(), + condition: () => frappe.model.can_export(this.report_doc.ref_doctype), standard: true }, { From 6792b5935311eee093ed06b039ba24104e6a010b Mon Sep 17 00:00:00 2001 From: Roland Date: Fri, 10 May 2019 13:55:14 +0200 Subject: [PATCH 089/176] Apply Customer role instead of Blogger --- frappe/integrations/doctype/ldap_settings/ldap_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index e12a6fce05..ae3a925196 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -130,7 +130,7 @@ def create_user(params): "language": "", "user_type": "System User", "roles": [{ - "role": _("Blogger") + "role": _("Customer") }] }) From eb4e2bce397b6fe625a8a1c5ca2af7d25585f285 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 10 May 2019 18:17:47 +0530 Subject: [PATCH 090/176] fix: Auto email support for Custom Reports (#7441) * fix: Added reference_report field * fix: Auto email support for custom reports * fix: Styling fixes * fix: Use let instead of var --- frappe/core/doctype/report/report.py | 2 +- .../auto_email_report/auto_email_report.js | 16 +++++- .../auto_email_report/auto_email_report.json | 57 ++++++++++++++++++- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 789c989439..cbec5ecbbc 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -100,7 +100,7 @@ class Report(Document): columns = [] out = [] - if self.report_type in ('Query Report', 'Script Report'): + if self.report_type in ('Query Report', 'Script Report', 'Custom Report'): # query and script reports data = frappe.desk.query_report.run(self.name, filters=filters, user=user) for d in data.get('columns'): diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.js b/frappe/email/doctype/auto_email_report/auto_email_report.js index ba8ab37a99..d19ddb3f67 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.js +++ b/frappe/email/doctype/auto_email_report/auto_email_report.js @@ -54,9 +54,9 @@ frappe.ui.form.on('Auto Email Report', { show_filters: function(frm) { var wrapper = $(frm.get_field('filters_display').wrapper); wrapper.empty(); - if(frm.doc.report_type !== 'Report Builder' + if(frm.doc.report_type === 'Custom Report' || (frm.doc.report_type !== 'Report Builder' && frappe.query_reports[frm.doc.report] - && frappe.query_reports[frm.doc.report].filters) { + && frappe.query_reports[frm.doc.report].filters)) { // make a table to show filters var table = $('\ @@ -65,7 +65,17 @@ frappe.ui.form.on('Auto Email Report', { $('

' + __("Click table to edit") + '

').appendTo(wrapper); var filters = JSON.parse(frm.doc.filters || '{}'); - var report_filters = frappe.query_reports[frm.doc.report].filters; + + let report_filters; + + if (frm.doc.report_type === 'Custom Report' + && frappe.query_reports[frm.doc.reference_report] + && frappe.query_reports[frm.doc.reference_report].filters) { + report_filters = frappe.query_reports[frm.doc.reference_report].filters; + } else { + report_filters = frappe.query_reports[frm.doc.report].filters; + } + if(report_filters && report_filters.length > 0) { frm.set_value('filter_meta', JSON.stringify(report_filters)); } diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.json b/frappe/email/doctype/auto_email_report/auto_email_report.json index f04f34cdba..b6de0a16fc 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.json +++ b/frappe/email/doctype/auto_email_report/auto_email_report.json @@ -21,6 +21,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "report", "fieldtype": "Link", "hidden": 0, @@ -55,6 +56,7 @@ "collapsible": 0, "columns": 0, "default": "User", + "fetch_if_empty": 0, "fieldname": "user", "fieldtype": "Link", "hidden": 0, @@ -89,6 +91,7 @@ "collapsible": 0, "columns": 0, "default": "1", + "fetch_if_empty": 0, "fieldname": "enabled", "fieldtype": "Check", "hidden": 0, @@ -121,6 +124,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_4", "fieldtype": "Column Break", "hidden": 0, @@ -153,6 +157,7 @@ "collapsible": 0, "columns": 0, "fetch_from": "report.report_type", + "fetch_if_empty": 0, "fieldname": "report_type", "fieldtype": "Read Only", "hidden": 0, @@ -186,6 +191,41 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_from": "report.reference_report", + "fetch_if_empty": 0, + "fieldname": "reference_report", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference Report", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "filter_data", "fieldtype": "Section Break", "hidden": 0, @@ -219,6 +259,7 @@ "collapsible": 0, "columns": 0, "default": "1", + "fetch_if_empty": 0, "fieldname": "send_if_data", "fieldtype": "Check", "hidden": 0, @@ -254,6 +295,7 @@ "default": "", "depends_on": "eval:doc.report_type=='Report Builder'", "description": "Zero means send records updated at anytime", + "fetch_if_empty": 0, "fieldname": "data_modified_till", "fieldtype": "Int", "hidden": 0, @@ -288,6 +330,7 @@ "columns": 0, "default": "100", "description": "", + "fetch_if_empty": 0, "fieldname": "no_of_rows", "fieldtype": "Int", "hidden": 0, @@ -321,6 +364,7 @@ "collapsible": 1, "columns": 0, "depends_on": "eval:doc.report_type !== 'Report Builder'", + "fetch_if_empty": 0, "fieldname": "report_filters", "fieldtype": "Section Break", "hidden": 0, @@ -353,6 +397,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "filters_display", "fieldtype": "HTML", "hidden": 0, @@ -385,6 +430,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "filters", "fieldtype": "Text", "hidden": 1, @@ -417,6 +463,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "filter_meta", "fieldtype": "Text", "hidden": 1, @@ -449,6 +496,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "email_settings", "fieldtype": "Section Break", "hidden": 0, @@ -481,6 +529,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "email_to", "fieldtype": "Small Text", "hidden": 0, @@ -515,6 +564,7 @@ "columns": 0, "default": "Monday", "depends_on": "eval:doc.frequency=='Weekly'", + "fetch_if_empty": 0, "fieldname": "day_of_week", "fieldtype": "Select", "hidden": 0, @@ -548,6 +598,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_13", "fieldtype": "Column Break", "hidden": 0, @@ -579,6 +630,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "frequency", "fieldtype": "Select", "hidden": 0, @@ -612,6 +664,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "format", "fieldtype": "Select", "hidden": 0, @@ -645,6 +698,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_15", "fieldtype": "Section Break", "hidden": 0, @@ -677,6 +731,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Text Editor", "hidden": 0, @@ -713,7 +768,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-11-13 01:59:17.816718", + "modified": "2019-05-09 21:45:36.944865", "modified_by": "Administrator", "module": "Email", "name": "Auto Email Report", From fb2b395bc6aac11cddc64973993395019ebd06ef Mon Sep 17 00:00:00 2001 From: ci2014 Date: Fri, 10 May 2019 17:55:39 +0200 Subject: [PATCH 091/176] Fix: Wrong variable used for this.lang_code Fix: Wrong variable used for this.lang_code --- frappe/public/js/frappe/form/print.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/print.js b/frappe/public/js/frappe/form/print.js index 64ff603d8b..2bee052568 100644 --- a/frappe/public/js/frappe/form/print.js +++ b/frappe/public/js/frappe/form/print.js @@ -114,7 +114,7 @@ frappe.ui.form.PrintPreview = Class.extend({ }, set_default_print_language: function () { var print_format = this.get_print_format(); - this.lang_code = print_format.default_print_format || frappe.boot.lang; + this.lang_code = print_format.default_print_language || frappe.boot.lang; this.language_sel.val(this.lang_code); }, multilingual_preview: function () { From 7054b9a7a8dcaca59771353697a5a88ecb774405 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 10 May 2019 22:39:22 +0530 Subject: [PATCH 092/176] fix: Option fixes in custom_columns --- frappe/public/js/frappe/views/reports/query_report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index bc5e37427b..edebb11a43 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1028,7 +1028,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { label: df.label, link_field: this.doctype_field_map[values.doctype], doctype: values.doctype, - options: df.fieldtype === "Link" ? values.doctype : undefined, + options: df.fieldtype === "Link" ? frappe.model.unscrub(df.fieldname) : undefined, width: 100 }); From acae84e3c607210c33c9c0188dafe987433590b7 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sun, 12 May 2019 19:40:46 +0530 Subject: [PATCH 093/176] style: Indent --- frappe/public/js/frappe/form/print.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/form/print.js b/frappe/public/js/frappe/form/print.js index 82a2788b0a..f0e0d62053 100644 --- a/frappe/public/js/frappe/form/print.js +++ b/frappe/public/js/frappe/form/print.js @@ -109,14 +109,14 @@ frappe.ui.form.PrintPreview = Class.extend({ default: print_format.name || 'Standard' } ], function (data) { - frappe.route_options = { - make_new: true, - doctype: me.frm.doctype, - name: data.print_format_name, - based_on: data.based_on - }; - frappe.set_route("print-format-builder"); - }, __("New Custom Print Format"), __("Start")); + frappe.route_options = { + make_new: true, + doctype: me.frm.doctype, + name: data.print_format_name, + based_on: data.based_on + }; + frappe.set_route("print-format-builder"); + }, __("New Custom Print Format"), __("Start")); }); }, set_user_lang: function () { From 328db8429bdc1c9b2e4decd95aa63d9b778d19ed Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sun, 12 May 2019 19:42:25 +0530 Subject: [PATCH 094/176] fix: Update snyk file --- .snyk | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.snyk b/.snyk index f056697eb7..09063530c7 100644 --- a/.snyk +++ b/.snyk @@ -1,6 +1,15 @@ # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. version: v1.13.3 -ignore: {} +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + SNYK-JS-AWESOMPLETE-174474: + - awesomplete: + reason: No patch available + expires: '2019-06-11T14:12:04.995Z' + 'npm:mem:20180117': + - showdown > yargs > os-locale > mem: + reason: No patch available + expires: '2019-06-11T14:12:04.995Z' # patches apply the minimum changes required to fix a vulnerability patch: 'npm:extend:20180424': From e6498715940824a80ce2a315cb190fd78aed73a3 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 13 May 2019 13:03:26 +0530 Subject: [PATCH 095/176] fix: default_print_language instead of default_print_format (#7461) --- frappe/public/js/frappe/form/print.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/print.js b/frappe/public/js/frappe/form/print.js index 49070c4c78..e92fb9f173 100644 --- a/frappe/public/js/frappe/form/print.js +++ b/frappe/public/js/frappe/form/print.js @@ -130,7 +130,7 @@ frappe.ui.form.PrintPreview = Class.extend({ }, set_default_print_language: function () { var print_format = this.get_print_format(); - this.lang_code = print_format.default_print_format || frappe.boot.lang; + this.lang_code = print_format.default_print_language || frappe.boot.lang; this.language_sel.val(this.lang_code); }, multilingual_preview: function () { From 3bd1a0774ca409e387758b8ed91d127395b11999 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 13 May 2019 13:36:22 +0530 Subject: [PATCH 096/176] fix: showing doctype name where required in error message. (#7256) * fix: showing doctype name where required in error message * fix: requested changes * fix: changes requested --- frappe/core/doctype/doctype/doctype.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 499f64be53..6a2c2272c9 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -484,35 +484,35 @@ def validate_fields(meta): def check_unique_fieldname(fieldname): duplicates = list(filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields))) if len(duplicates) > 1: - frappe.throw(_("Fieldname {0} appears multiple times in rows {1}").format(fieldname, ", ".join(duplicates))) + frappe.throw(_("{0}: Fieldname {1} appears multiple times in rows {2}").format(self.name, fieldname, ", ".join(duplicates))) def check_fieldname_length(fieldname): validate_column_length(fieldname) def check_illegal_mandatory(d): if (d.fieldtype in no_value_fields) and d.fieldtype!="Table" and d.reqd: - frappe.throw(_("Field {0} of type {1} cannot be mandatory").format(d.label, d.fieldtype)) + frappe.throw(_("{0}: Field {1} of type {2} cannot be mandatory").format(self.name, d.label, d.fieldtype)) def check_link_table_options(d): if d.fieldtype in ("Link", "Table"): if not d.options: - frappe.throw(_("Options required for Link or Table type field {0} in row {1}").format(d.label, d.idx)) + frappe.throw(_("{0}: Options required for Link or Table type field {1} in row {2}").format(self.name, d.label, d.idx)) if d.options=="[Select]" or d.options==d.parent: return if d.options != d.parent: options = frappe.db.get_value("DocType", d.options, "name") if not options: - frappe.throw(_("Options must be a valid DocType for field {0} in row {1}").format(d.label, d.idx)) + frappe.throw(_("{0}: Options must be a valid DocType for field {1} in row {2}").format(self.name, d.label, d.idx)) elif not (options == d.options): - frappe.throw(_("Options {0} must be the same as doctype name {1} for the field {2}") - .format(d.options, options, d.label)) + frappe.throw(_("{0}: Options {1} must be the same as doctype name {2} for the field {3}") + .format(self.name, d.options, options, d.label)) else: # fix case d.options = options def check_hidden_and_mandatory(d): if d.hidden and d.reqd and not d.default: - frappe.throw(_("Field {0} in row {1} cannot be hidden and mandatory without default").format(d.label, d.idx)) + frappe.throw(_("{0}: Field {1} in row {2} cannot be hidden and mandatory without default").format(self.name, d.label, d.idx)) def check_width(d): if d.fieldtype == "Currency" and cint(d.width) < 100: @@ -551,7 +551,7 @@ def validate_fields(meta): if getattr(d, "unique", False): if d.fieldtype not in ("Data", "Link", "Read Only"): - frappe.throw(_("Fieldtype {0} for {1} cannot be unique").format(d.fieldtype, d.label)) + frappe.throw(_("{0}: Fieldtype {1} for {2} cannot be unique").format(self.name, d.fieldtype, d.label)) if not d.get("__islocal"): try: @@ -571,7 +571,7 @@ def validate_fields(meta): else: # else of try block if has_non_unique_values and has_non_unique_values[0][0]: - frappe.throw(_("Field '{0}' cannot be set as Unique as it has non-unique values").format(d.label)) + frappe.throw(_("{0}: Field '{1}' cannot be set as Unique as it has non-unique values").format(self.name, d.label)) if d.search_index and d.fieldtype in ("Text", "Long Text", "Small Text", "Code", "Text Editor"): frappe.throw(_("Fieldtype {0} for {1} cannot be indexed").format(d.fieldtype, d.label)) From c747364ca8138eeede5dc6afcdd860bdfd7d0a9a Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 14 May 2019 13:40:02 +0530 Subject: [PATCH 097/176] fix: Change frappe.call to POST --- frappe/www/update-password.html | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/www/update-password.html b/frappe/www/update-password.html index 9689a1d3b9..6a69df9af7 100644 --- a/frappe/www/update-password.html +++ b/frappe/www/update-password.html @@ -118,7 +118,6 @@ frappe.ready(function() { } return frappe.call({ - type: 'GET', method: 'frappe.core.doctype.user.user.test_password_strength', args: args, callback: function(r) { From de3560f1b21f3b56c490c8862627ce9886282f27 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 14 May 2019 14:00:06 +0530 Subject: [PATCH 098/176] fix: auto repeat showing next schedule date wrong for backdated entries --- frappe/desk/doctype/auto_repeat/auto_repeat.py | 10 ++++++++-- .../desk/doctype/auto_repeat/auto_repeat_schedule.html | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/auto_repeat/auto_repeat.py b/frappe/desk/doctype/auto_repeat/auto_repeat.py index c2d04e4fce..a3b3c014b2 100644 --- a/frappe/desk/doctype/auto_repeat/auto_repeat.py +++ b/frappe/desk/doctype/auto_repeat/auto_repeat.py @@ -34,9 +34,14 @@ class AutoRepeat(Document): validate_template(self.message or "") def before_submit(self): + start_date_copy = self.start_date + + if start_date_copy < today(): + start_date_copy = today() + if not self.next_schedule_date: self.next_schedule_date = get_next_schedule_date( - self.start_date, self.frequency, self.repeat_on_day) + start_date_copy, self.frequency, self.repeat_on_day) def on_submit(self): self.update_auto_repeat_id() @@ -116,14 +121,15 @@ class AutoRepeat(Document): days = 60 if self.frequency in ['Daily', 'Weekly'] else 365 end_date_copy = add_days(today_copy, days) + start_date_copy = get_next_schedule_date(start_date_copy, self.frequency, self.repeat_on_day) while (getdate(start_date_copy) < getdate(end_date_copy)): - start_date_copy = get_next_schedule_date(start_date_copy, self.frequency, self.repeat_on_day) row = { "reference_document" : self.reference_document, "frequency" : self.frequency, "next_scheduled_date" : start_date_copy } schedule_details.append(row) + start_date_copy = get_next_schedule_date(start_date_copy, self.frequency, self.repeat_on_day) return schedule_details diff --git a/frappe/desk/doctype/auto_repeat/auto_repeat_schedule.html b/frappe/desk/doctype/auto_repeat/auto_repeat_schedule.html index 7e579821c5..562a527797 100644 --- a/frappe/desk/doctype/auto_repeat/auto_repeat_schedule.html +++ b/frappe/desk/doctype/auto_repeat/auto_repeat_schedule.html @@ -12,7 +12,7 @@ - + {% } %} From b2f897746a3c6d60b356e1bb91ceab0c7822e475 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 14 May 2019 15:04:48 +0530 Subject: [PATCH 099/176] fix: get month diff between 2 dates (#7474) --- frappe/utils/data.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index de790c6fff..0faeaaa41d 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -108,6 +108,11 @@ def add_years(date, years): def date_diff(string_ed_date, string_st_date): return (getdate(string_ed_date) - getdate(string_st_date)).days +def month_diff(string_ed_date, string_st_date): + ed_date = getdate(string_ed_date) + st_date = getdate(string_st_date) + return (ed_date.year - st_date.year) * 12 + ed_date.month - st_date.month + 1 + def time_diff(string_ed_date, string_st_date): return get_datetime(string_ed_date) - get_datetime(string_st_date) From 65b04ea494b9c8b63d2592db8dd69b5b6669ccb4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 14 May 2019 15:06:43 +0530 Subject: [PATCH 100/176] fix: Added dynamic links in dashboard (#7475) --- frappe/public/js/frappe/form/dashboard.js | 6 ++++++ frappe/public/js/frappe/list/base_list.js | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index 8b15715361..0ef25dfae3 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -310,6 +310,12 @@ frappe.ui.form.Dashboard = Class.extend({ var fieldname = this.data.non_standard_fieldnames ? (this.data.non_standard_fieldnames[doctype] || this.data.fieldname) : this.data.fieldname; + + if (this.data.dynamic_links && this.data.dynamic_links[fieldname]) { + let dynamic_fieldname = this.data.dynamic_links[fieldname][1]; + filter[dynamic_fieldname] = this.data.dynamic_links[fieldname][0]; + } + filter[fieldname] = this.frm.doc.name; return filter; }, diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index d702020c52..48f60a0829 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -619,7 +619,8 @@ class FilterArea { options: options, fieldname: df.fieldname, condition: condition, - onchange: () => this.refresh_list_view() + onchange: () => this.refresh_list_view(), + ignore_link_validation: fieldtype === 'Dynamic Link' }; })); From 7aa258e6b8d387ec5a4dc7bc88208cf1e4051d46 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 14 May 2019 16:13:22 +0530 Subject: [PATCH 101/176] style: moved set_link_title to address_and_contact --- frappe/contacts/address_and_contact.py | 8 ++++++++ frappe/contacts/doctype/address/address.py | 11 ++--------- frappe/contacts/doctype/contact/contact.py | 11 ++--------- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/frappe/contacts/address_and_contact.py b/frappe/contacts/address_and_contact.py index 38d292e9b4..6523b0b1e5 100644 --- a/frappe/contacts/address_and_contact.py +++ b/frappe/contacts/address_and_contact.py @@ -152,3 +152,11 @@ def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, fil valid_doctypes = [[doctype] for doctype in valid_doctypes] return valid_doctypes + +def set_link_title(doc): + if not doc.links: + return + for link in doc.links: + if not link.link_title: + linked_doc = frappe.get_doc(link.link_doctype, link.link_name) + link.link_title = linked_doc.get("title_field") or linked_doc.get("name") diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index fb2b9909fc..0d8eb661f3 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -15,6 +15,7 @@ from frappe.model.naming import make_autoname from frappe.core.doctype.dynamic_link.dynamic_link import deduplicate_dynamic_links from six import iteritems, string_types from past.builtins import cmp +from frappe.contacts.address_and_contact import set_link_title import functools @@ -39,7 +40,7 @@ class Address(Document): def validate(self): self.link_address() self.validate_reference() - self.set_link_title() + set_link_title(self) deduplicate_dynamic_links(self) def link_address(self): @@ -54,14 +55,6 @@ class Address(Document): return False - def set_link_title(self): - if not self.links: - return - for address in self.links: - if not address.link_title: - linked_doc = frappe.get_doc(address.link_doctype, address.link_name) - address.link_title = linked_doc.get("title_field") or linked_doc.get("name") - def validate_reference(self): if self.is_your_company_address: if not [row for row in self.links if row.link_doctype == "Company"]: diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index 5c5581b294..8c1d0b4ac4 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -10,6 +10,7 @@ from frappe.core.doctype.dynamic_link.dynamic_link import deduplicate_dynamic_li from six import iteritems from past.builtins import cmp from frappe.model.naming import append_number_if_name_exists +from frappe.contacts.address_and_contact import set_link_title import functools @@ -31,7 +32,7 @@ class Contact(Document): if self.email_id: self.email_id = self.email_id.strip() self.set_user() - self.set_link_title() + set_link_title(self) if self.email_id and not self.image: self.image = has_gravatar(self.email_id) @@ -41,14 +42,6 @@ class Contact(Document): if not self.user and self.email_id: self.user = frappe.db.get_value("User", {"email": self.email_id}) - def set_link_title(self): - if not self.links: - return - for contact in self.links: - if not contact.link_title: - linked_doc = frappe.get_doc(contact.link_doctype, contact.link_name) - contact.link_title = linked_doc.get("title_field") or linked_doc.get("name") - def get_link_for(self, link_doctype): '''Return the link name, if exists for the given link DocType''' for link in self.links: From b7161378d581a0fcd912d74d1a6b2ab557b03fe2 Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Tue, 14 May 2019 17:06:03 +0550 Subject: [PATCH 102/176] bumped to version 11.1.28 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 1e84785bc0..573076f391 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '11.1.27' +__version__ = '11.1.28' __title__ = "Frappe Framework" local = Local() From e2a945efaf68e2b55d9cd9b38fc3c2f56995c46d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 14 May 2019 17:32:57 +0530 Subject: [PATCH 103/176] fix: if more than 20 records are available the system removing last row --- frappe/public/js/frappe/form/multi_select_dialog.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index 609cd8be80..1c04ab0dd1 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -208,7 +208,7 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ txt: me.dialog.fields_dict["search_term"].get_value(), filters: filters, filter_fields: Object.keys(me.setters).concat([me.date_field]), - page_length: this.page_length + 1, + page_length: this.page_length, query: this.get_query ? this.get_query().query : '', as_dict: 1 } @@ -220,10 +220,6 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ callback: function(r) { let results = [], more = 0; if(r.values.length) { - if(r.values.length > me.page_length){ - r.values.pop(); - more = 1; - } r.values.forEach(function(result) { if(me.date_field in result) { result["Date"] = result[me.date_field] From 84b926801972eac4ba1c597d17ce8815989dd21c Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Tue, 14 May 2019 18:50:51 +0530 Subject: [PATCH 104/176] fix(patch): reload dynamic link doctype --- frappe/patches/v11_0/create_contact_for_user.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/patches/v11_0/create_contact_for_user.py b/frappe/patches/v11_0/create_contact_for_user.py index d5e9c87e8b..c91caf9189 100644 --- a/frappe/patches/v11_0/create_contact_for_user.py +++ b/frappe/patches/v11_0/create_contact_for_user.py @@ -6,6 +6,7 @@ import re def execute(): """ Create Contact for each User if not present """ frappe.reload_doc('contacts', 'doctype', 'contact') + frappe.reload_doc('core', 'doctype', 'dynamic_link') users = frappe.get_all('User', filters={"name": ('not in', 'Administrator, Guest')}, fields=["*"]) for user in users: From 65fca28d87d700b48c4ae464cb7c97c031ed2d2b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 15 May 2019 11:39:31 +0530 Subject: [PATCH 105/176] fix: Add currency code for Surinamese dollar --- frappe/geo/country_info.json | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/geo/country_info.json b/frappe/geo/country_info.json index a3f039ca17..9656d62b5d 100644 --- a/frappe/geo/country_info.json +++ b/frappe/geo/country_info.json @@ -2329,6 +2329,7 @@ }, "Suriname": { "code": "sr", + "currency": "SRD", "currency_fraction": "Cent", "currency_fraction_units": 100, "currency_symbol": "$", From e51025cc58082e45d24b09e48dd56deec5eb57ca Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 14 May 2019 21:02:26 +0530 Subject: [PATCH 106/176] feat: added more button in the multi select modal --- .../js/frappe/form/multi_select_dialog.js | 81 ++++++++++--------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index 1c04ab0dd1..3438c59a1f 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -19,6 +19,7 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ let me = this; this.page_length = 20; + this.start = 0; let fields = [ { @@ -55,7 +56,12 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ }, { fieldtype: "Section Break" }, { fieldtype: "HTML", fieldname: "results_area" }, - { fieldtype: "Button", fieldname: "make_new", label: __("Make a new " + me.doctype) } + { fieldtype: "Button", fieldname: "more_btn", label: __("More"), + click: function(){ + me.start += 20; + me.get_results(); + } + } ]); let doctype_plural = !this.doctype.endsWith('y') ? this.doctype + 's' @@ -65,25 +71,30 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ title: __("Select {0}", [(this.doctype=='[Select]') ? __("value") : __(doctype_plural)]), fields: fields, primary_action_label: __("Get Items"), + secondary_action_label: __("Make {0}", [me.doctype]), primary_action: function() { me.action(me.get_checked_values(), me.args); + }, + secondary_action: function(e) { + // If user wants to close the modal + if (e) { + frappe.route_options = {}; + + Object.keys(me.setters).forEach(function(setter) { + frappe.route_options[setter] = me.dialog.fields_dict[setter].get_value() || undefined; + }); + + frappe.new_doc(me.doctype, true); + } } }); this.$parent = $(this.dialog.body); this.$wrapper = this.dialog.fields_dict.results_area.$wrapper.append(`
`); - this.$results = this.$wrapper.find('.results'); - this.$make_new_btn = this.dialog.fields_dict.make_new.$wrapper; - this.$placeholder = $(`
- - -

No ${this.doctype} found

- -
-
`); + this.$results = this.$wrapper.find('.results'); + this.$results.append(this.make_list_row()); this.args = {}; @@ -94,6 +105,7 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ bind_events: function() { let me = this; + this.$results.on('click', '.list-item-container', function (e) { if (!$(e.target).is(':checkbox') && !$(e.target).is('a')) { $(this).find(':checkbox').trigger('click'); @@ -119,14 +131,6 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ me.get_results(); }, 300)); }); - - this.$parent.on('click', '.btn[data-fieldname="make_new"]', (e) => { - frappe.route_options = {}; - Object.keys(this.setters).forEach(function(setter) { - frappe.route_options[setter] = me.dialog.fields_dict[setter].get_value() || undefined; - }); - frappe.new_doc(this.doctype, true); - }); }, get_checked_values: function() { @@ -170,23 +174,21 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ render_result_list: function(results, more = 0) { var me = this; - this.$results.empty(); - if(results.length === 0) { - this.$make_new_btn.addClass('hide'); - this.$results.append(me.$placeholder); - return; - } - this.$make_new_btn.removeClass('hide'); - this.$results.append(this.make_list_row()); + var more_btn = me.dialog.fields_dict.more_btn.$wrapper; + if(results.length === 0) { + this.$results.empty(); + more_btn.hide(); + return; + } else { + more_btn.show(); + } + results.forEach((result) => { me.$results.append(me.make_list_row(result)); - }) - if (more) { - let message = __("Only {0} entries shown. Please filter for more specific results.", [this.page_length]); - me.$results.append($(`
${message}
`)); - } + }); + + this.$results.animate({scrollTop: me.$results.prop('scrollHeight')}, 500); }, get_results: function() { @@ -208,7 +210,8 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ txt: me.dialog.fields_dict["search_term"].get_value(), filters: filters, filter_fields: Object.keys(me.setters).concat([me.date_field]), - page_length: this.page_length, + start: this.start, + page_length: this.page_length + 1, query: this.get_query ? this.get_query().query : '', as_dict: 1 } @@ -219,7 +222,11 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ args: args, callback: function(r) { let results = [], more = 0; - if(r.values.length) { + if (r.values.length) { + if (r.values.length > me.page_length) { + r.values.pop(); + more = 1; + } r.values.forEach(function(result) { if(me.date_field in result) { result["Date"] = result[me.date_field] @@ -237,7 +244,9 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ }); // Preselect oldest entry - results[0].checked = 1 + if (me.start < 1) { + results[0].checked = 1; + } } me.render_result_list(results, more); } From c7c1d88adc7f8991a3ac2a24981db3687d1f005c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 14 May 2019 14:34:14 +0530 Subject: [PATCH 107/176] fixed test cases --- frappe/desk/doctype/auto_repeat/auto_repeat.py | 5 +++-- frappe/desk/doctype/auto_repeat/auto_repeat_schedule.html | 2 +- frappe/desk/doctype/auto_repeat/test_auto_repeat.py | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/frappe/desk/doctype/auto_repeat/auto_repeat.py b/frappe/desk/doctype/auto_repeat/auto_repeat.py index a3b3c014b2..8a90b011d8 100644 --- a/frappe/desk/doctype/auto_repeat/auto_repeat.py +++ b/frappe/desk/doctype/auto_repeat/auto_repeat.py @@ -35,9 +35,10 @@ class AutoRepeat(Document): def before_submit(self): start_date_copy = self.start_date + today_copy = add_days(today(), -1) - if start_date_copy < today(): - start_date_copy = today() + if start_date_copy <= today_copy: + start_date_copy = today_copy if not self.next_schedule_date: self.next_schedule_date = get_next_schedule_date( diff --git a/frappe/desk/doctype/auto_repeat/auto_repeat_schedule.html b/frappe/desk/doctype/auto_repeat/auto_repeat_schedule.html index 562a527797..7e579821c5 100644 --- a/frappe/desk/doctype/auto_repeat/auto_repeat_schedule.html +++ b/frappe/desk/doctype/auto_repeat/auto_repeat_schedule.html @@ -12,7 +12,7 @@
- + {% } %} diff --git a/frappe/desk/doctype/auto_repeat/test_auto_repeat.py b/frappe/desk/doctype/auto_repeat/test_auto_repeat.py index 19cf039759..cf8ff610d5 100644 --- a/frappe/desk/doctype/auto_repeat/test_auto_repeat.py +++ b/frappe/desk/doctype/auto_repeat/test_auto_repeat.py @@ -8,7 +8,7 @@ import unittest import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe.desk.doctype.auto_repeat.auto_repeat import get_auto_repeat_entries, create_repeated_entries, disable_auto_repeat -from frappe.utils import today, add_days, getdate +from frappe.utils import today, add_days, getdate, add_months def add_custom_fields(): @@ -44,8 +44,8 @@ class TestAutoRepeat(unittest.TestCase): self.assertEqual(todo.get('description'), new_todo.get('description')) def test_monthly_auto_repeat(self): - start_date = '2018-01-01' - end_date = '2018-12-31' + start_date = today() + end_date = add_months(start_date, 12) todo = frappe.get_doc( dict(doctype='ToDo', description='test recurring todo', assigned_by='Administrator')).insert() @@ -103,7 +103,7 @@ def make_auto_repeat(**args): 'reference_document': args.reference_document or frappe.db.get_value('ToDo', {'docstatus': 1}, 'name'), 'frequency': args.frequency or 'Daily', 'start_date': args.start_date or add_days(today(), -1), - 'end_date': args.end_date or add_days(today(), 1), + 'end_date': args.end_date or add_days(today(), 2), 'submit_on_creation': args.submit_on_creation or 0, 'notify_by_email': args.notify or 0, 'recipients': args.recipients or "", From 73964e7a615d561f9a187e50bcaa3c9c716456ee Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 15 May 2019 13:42:42 +0530 Subject: [PATCH 108/176] fix: permission fixes for prepared report --- frappe/core/doctype/prepared_report/prepared_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index cc087ae784..e83a73d1ec 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -51,14 +51,14 @@ def run_background(prepared_report): instance.status = "Completed" instance.columns = json.dumps(result["columns"]) instance.report_end_time = frappe.utils.now() - instance.save() + instance.save(ignore_permissions=True) except Exception: frappe.log_error(frappe.get_traceback()) instance = frappe.get_doc("Prepared Report", prepared_report) instance.status = "Error" instance.error_message = frappe.get_traceback() - instance.save() + instance.save(ignore_permissions=True) frappe.publish_realtime( 'report_generated', From f59c4eb554a23ba345c1397293313da4a6ba767b Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 15 May 2019 15:41:46 +0530 Subject: [PATCH 109/176] fix: Don't show no value type fields in options for custom columns (#7496) * fix: Don't show no value type fields in options for custom columns * fix: Use filter instead of pushing in list --- frappe/public/js/frappe/views/reports/query_report.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index edebb11a43..938fe915c9 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -999,9 +999,11 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { change: () => { let doctype = d.get_value('doctype'); frappe.model.with_doctype(doctype, () => { - let fields = frappe.meta.get_docfields(doctype) + let options = frappe.meta.get_docfields(doctype) + .filter(frappe.model.is_value_type) .map(df => ({ label: df.label, value: df.fieldname })); - d.set_df_property('field', 'options', fields); + + d.set_df_property('field', 'options', options); }); } From 07340d7852f08a8fa2873fa1ed2aac85647e2b99 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 16 May 2019 09:31:05 +0530 Subject: [PATCH 110/176] fix: Add a patch to apply customization to custom doctype This patch aims to apply & delete all the customization on custom doctypes done through customize form This is required because customize form in now blocked for custom doctypes and user may not be able to see previous customization --- frappe/patches.txt | 3 +- .../apply_customization_to_custom_doctype.py | 52 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 frappe/patches/v11_0/apply_customization_to_custom_doctype.py diff --git a/frappe/patches.txt b/frappe/patches.txt index b085379e09..50ba7b6ec3 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -235,4 +235,5 @@ frappe.patches.v11_0.delete_all_prepared_reports frappe.patches.v11_0.fix_order_by_in_reports_json execute:frappe.delete_doc('Page', 'applications', ignore_missing=True) frappe.patches.v11_0.set_missing_creation_and_modified_value_for_user_permissions -frappe.patches.v11_0.remove_doctype_user_permissions_for_page_and_report #2019-05-01 \ No newline at end of file +frappe.patches.v11_0.remove_doctype_user_permissions_for_page_and_report #2019-05-01 +frappe.patches.v11_0.apply_customization _to_custom_doctype \ No newline at end of file diff --git a/frappe/patches/v11_0/apply_customization_to_custom_doctype.py b/frappe/patches/v11_0/apply_customization_to_custom_doctype.py new file mode 100644 index 0000000000..574097090d --- /dev/null +++ b/frappe/patches/v11_0/apply_customization_to_custom_doctype.py @@ -0,0 +1,52 @@ +import frappe +from frappe.utils import cint + +# This patch aims to apply & delete all the customization +# on custom doctypes done through customize form + +# This is required because customize form in now blocked +# for custom doctypes and user may not be able to +# see previous customization + +def execute(): + custom_doctypes = frappe.get_all('DocType', filters={ + 'custom': 1 + }) + + for doctype in custom_doctypes: + property_setters = frappe.get_all('Property Setter', filters={ + 'doc_type': doctype.name, + 'doctype_or_field': 'DocField' + }, fields=['name', 'property', 'value', 'property_type', 'field_name']) + + custom_fields = frappe.get_all('Custom Field', + filters={'dt': doctype.name}, + fields=['*'] + ) + + property_setters_map = {} + + for property in property_setters: + property_setters_map[property.field_name] = property + frappe.db.sql('DELETE FROM `tabProperty Setter` WHERE `name`=%s', property.name) + + meta = frappe.get_doc('DocType', doctype.name) + + for df in meta.fields: + ps = property_setters_map.get(df.fieldname, None) + if ps: + value = cint(ps.value) if ps.property_type == 'Int' else ps.value + df.set(ps.property, value) + + for cf in custom_fields: + df = frappe.new_doc('DocField', meta, 'fields') + cf.pop('parenttype') + cf.pop('parentfield') + cf.pop('parent') + cf.pop('name') + df.update(cf) + meta.fields.append(df) + frappe.db.sql('DELETE FROM `tabCustom Field` WHERE name=%s', cf.name) + + meta.save() + From 62eb5e68f4a784d0a85d5aadfc1353cd520b5cfb Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 16 May 2019 12:44:29 +0530 Subject: [PATCH 111/176] style: Use prop instead of property as it's a keyword --- .../v11_0/apply_customization_to_custom_doctype.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/patches/v11_0/apply_customization_to_custom_doctype.py b/frappe/patches/v11_0/apply_customization_to_custom_doctype.py index 574097090d..d7c10a903d 100644 --- a/frappe/patches/v11_0/apply_customization_to_custom_doctype.py +++ b/frappe/patches/v11_0/apply_customization_to_custom_doctype.py @@ -24,16 +24,16 @@ def execute(): fields=['*'] ) - property_setters_map = {} + property_setter_map = {} - for property in property_setters: - property_setters_map[property.field_name] = property - frappe.db.sql('DELETE FROM `tabProperty Setter` WHERE `name`=%s', property.name) + for prop in property_setters: + property_setter_map[prop.field_name] = prop + frappe.db.sql('DELETE FROM `tabProperty Setter` WHERE `name`=%s', prop.name) meta = frappe.get_doc('DocType', doctype.name) for df in meta.fields: - ps = property_setters_map.get(df.fieldname, None) + ps = property_setter_map.get(df.fieldname, None) if ps: value = cint(ps.value) if ps.property_type == 'Int' else ps.value df.set(ps.property, value) From 2d62cd60ee71ca92cb91ec788186186d51214b75 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 16 May 2019 17:37:06 +0530 Subject: [PATCH 112/176] fix: Report Print format for indented rows (#7506) --- .../js/frappe/views/reports/print_grid.html | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/print_grid.html b/frappe/public/js/frappe/views/reports/print_grid.html index 12854e74f3..e99600c2e6 100644 --- a/frappe/public/js/frappe/views/reports/print_grid.html +++ b/frappe/public/js/frappe/views/reports/print_grid.html @@ -31,15 +31,17 @@ {% var value = col.fieldname ? row[col.fieldname] : row[col.id]; %} {% endif %} {% endfor %} From 183564f3af2bc4b070bf86fcfd9045bb89c845ca Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 19 May 2019 17:44:46 +0530 Subject: [PATCH 113/176] fix: Module view fix for custom reports --- frappe/desk/moduleview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/moduleview.py b/frappe/desk/moduleview.py index 0b4f1eb853..f697211ac3 100644 --- a/frappe/desk/moduleview.py +++ b/frappe/desk/moduleview.py @@ -253,7 +253,7 @@ def get_report_list(module, is_standard="No"): out.append({ "type": "report", "doctype": r.ref_doctype, - "is_query_report": 1 if r.report_type in ("Query Report", "Script Report") else 0, + "is_query_report": 1 if r.report_type in ("Query Report", "Script Report", "Custom Report") else 0, "label": _(r.name), "name": r.name }) From bcf645ad9b68f26260ed8aebd642aabe980f0159 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 20 May 2019 10:18:05 +0530 Subject: [PATCH 114/176] fix: Typo --- frappe/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/patches.txt b/frappe/patches.txt index 50ba7b6ec3..d196430bbc 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -236,4 +236,4 @@ frappe.patches.v11_0.fix_order_by_in_reports_json execute:frappe.delete_doc('Page', 'applications', ignore_missing=True) frappe.patches.v11_0.set_missing_creation_and_modified_value_for_user_permissions frappe.patches.v11_0.remove_doctype_user_permissions_for_page_and_report #2019-05-01 -frappe.patches.v11_0.apply_customization _to_custom_doctype \ No newline at end of file +frappe.patches.v11_0.apply_customization_to_custom_doctype \ No newline at end of file From 90bc741c982d8ddd4242d83cc574fc26bb288965 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 20 May 2019 12:19:22 +0530 Subject: [PATCH 115/176] fix: Create new button for dynamic link fields (#7510) * fix: Create new button for dynamic links * fix: Use get_options for fetching options --- frappe/public/js/frappe/form/controls/link.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 86a99c970f..458f3efa6d 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -167,13 +167,12 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ } if(!me.df.only_select) { - if(frappe.model.can_create(doctype) - && me.df.fieldtype !== "Dynamic Link") { + if(frappe.model.can_create(doctype)) { // new item r.results.push({ label: "" + " " - + __("Create a new {0}", [__(me.df.options)]) + + __("Create a new {0}", [__(me.get_options())]) + "", value: "create_new__link_option", action: me.new_doc From e4aedc3e66173bdc79f7e11ab9becb0b8f337171 Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Tue, 21 May 2019 14:27:05 +0550 Subject: [PATCH 116/176] bumped to version 11.1.29 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 164e5c6a3a..8624e8df2b 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '11.1.28' +__version__ = '11.1.29' __title__ = "Frappe Framework" local = Local() From f4778027d6c417538c605d4bb96786fceef42c24 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 21 May 2019 14:49:56 +0530 Subject: [PATCH 117/176] fix: Allow eligible fields to be updated from notifications (#7517) --- .../doctype/notification/notification.py | 11 ++++++--- .../doctype/ldap_settings/ldap_settings.py | 23 +++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index acddf36777..84029a0933 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -122,9 +122,14 @@ def get_context(context): self.send_a_slack_msg(doc, context) if self.set_property_after_alert: - frappe.db.set_value(doc.doctype, doc.name, self.set_property_after_alert, - self.property_value, update_modified = False) - doc.set(self.set_property_after_alert, self.property_value) + allow_update = True + if doc.docstatus == 1 and not doc.meta.get_field(self.set_property_after_alert).allow_on_submit: + allow_update = False + + if allow_update: + frappe.db.set_value(doc.doctype, doc.name, self.set_property_after_alert, + self.property_value, update_modified = False) + doc.set(self.set_property_after_alert, self.property_value) def send_an_email(self, doc, context): from email.utils import formataddr diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index 0a4d871be8..a42b75fad4 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -10,17 +10,20 @@ from frappe.model.document import Document class LDAPSettings(Document): def validate(self): + if not self.enabled: + return + if not self.flags.ignore_mandatory: - if self.ldap_search_string.endswith("={0}"): - if self.enabled: - connect_to_ldap(server_url=self.ldap_server_url, - base_dn=self.base_dn, - password=self.get_password(raise_exception=False), - ssl_tls_mode=self.ssl_tls_mode, - trusted_cert=self.require_trusted_certificate, - private_key_file=self.local_private_key_file, - server_cert_file=self.local_server_certificate_file, - ca_certs_file=self.local_ca_certs_file) + if self.ldap_search_string and self.ldap_search_string.endswith("={0}"): + connect_to_ldap(server_url=self.ldap_server_url, + base_dn=self.base_dn, + password=self.get_password(raise_exception=False), + ssl_tls_mode=self.ssl_tls_mode, + trusted_cert=self.require_trusted_certificate, + private_key_file=self.local_private_key_file, + server_cert_file=self.local_server_certificate_file, + ca_certs_file=self.local_ca_certs_file + ) else: frappe.throw(_("LDAP Search String needs to end with a placeholder, eg sAMAccountName={0}")) From e2171bbb03cd74dd2487cefb8a64ea8760657389 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 21 May 2019 14:51:09 +0530 Subject: [PATCH 118/176] fix(Addresses and Contacts Report): Frappe throw when doctype has no records (#7503) --- .../report/addresses_and_contacts/addresses_and_contacts.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py index 118a6f29e6..3cf7dfeab9 100644 --- a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py +++ b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals from six import iteritems import frappe - +from frappe import _ field_map = { "Contact": [ "first_name", "last_name", "phone", "mobile_no", "email_id", "is_primary_contact" ], @@ -94,6 +94,9 @@ def get_reference_details(reference_doctype, doctype, reference_list, reference_ for d in records: temp_records.append(d[1:]) + if not reference_list: + frappe.throw(_("No records present in {0}".format(reference_doctype))) + reference_details[reference_list[0]][frappe.scrub(doctype)] = temp_records return reference_details From 8612056aab71bee13dd8ff85cd3bdea5a2a39a12 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 21 May 2019 16:27:07 +0530 Subject: [PATCH 119/176] fix: check payment status before capturing the payment --- .../doctype/razorpay_settings/razorpay_settings.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index cb66ed7f2a..98b6a0bdc8 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -312,9 +312,13 @@ def capture_payment(is_sandbox=False, sanbox_response=None): data = json.loads(doc.data) settings = controller.get_settings(data) - resp = make_post_request("https://api.razorpay.com/v1/payments/{0}/capture".format(data.get("razorpay_payment_id")), + resp = make_get_request("https://api.razorpay.com/v1/payments/{0}".format(data.get("razorpay_payment_id")), auth=(settings.api_key, settings.api_secret), data={"amount": data.get("amount")}) + if resp.get('status') == "authorized": + resp = make_post_request("https://api.razorpay.com/v1/payments/{0}/capture".format(data.get("razorpay_payment_id")), + auth=(settings.api_key, settings.api_secret), data={"amount": data.get("amount")}) + if resp.get("status") == "captured": frappe.db.set_value("Integration Request", doc.name, "status", "Completed") From 5c8acb33037f076550559c81596528801c949ffe Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Wed, 22 May 2019 14:57:51 +0530 Subject: [PATCH 120/176] fix: Doctype some test and fixes (#7535) * fix: code refactor * test: added validation test --- frappe/core/doctype/doctype/doctype.py | 51 +++++++++++-------- frappe/core/doctype/doctype/test_doctype.py | 56 ++++++++++++++++++++- 2 files changed, 84 insertions(+), 23 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 6a2c2272c9..a06a33df14 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -24,6 +24,14 @@ import pymysql from pymysql.constants import ER class InvalidFieldNameError(frappe.ValidationError): pass +class UniqueFieldnameError(frappe.ValidationError): pass +class IllegalMandatoryError(frappe.ValidationError): pass +class DoctypeLinkError(frappe.ValidationError): pass +class WrongOptionsDoctypeLinkError(frappe.ValidationError): pass +class HiddenAndMandatoryWithoutDefaultError(frappe.ValidationError): pass +class NonUniqueError(frappe.ValidationError): pass +class CannotIndexedError(frappe.ValidationError): pass +class CannotCreateStandardDoctypeError(frappe.ValidationError): pass form_grid_templates = { "fields": "templates/form_grid/fields.html" @@ -102,7 +110,7 @@ class DocType(Document): return if not frappe.conf.get("developer_mode") and not self.custom: - frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType.")) + frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType."), CannotCreateStandardDoctypeError) def setup_fields_to_fetch(self): '''Setup query to update values for newly set fetch values''' @@ -461,7 +469,6 @@ def validate_fields_for_doctype(doctype): # this is separate because it is also called via custom field def validate_fields(meta): """Validate doctype fields. Checks - 1. There are no illegal characters in fieldnames 2. If fieldnames are unique. 3. Validate column length. @@ -481,38 +488,38 @@ def validate_fields(meta): def check_illegal_characters(fieldname): validate_column_name(fieldname) - def check_unique_fieldname(fieldname): + def check_unique_fieldname(docname, fieldname): duplicates = list(filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields))) if len(duplicates) > 1: - frappe.throw(_("{0}: Fieldname {1} appears multiple times in rows {2}").format(self.name, fieldname, ", ".join(duplicates))) + frappe.throw(_("{0}: Fieldname {1} appears multiple times in rows {2}").format(docname, fieldname, ", ".join(duplicates)), UniqueFieldnameError) def check_fieldname_length(fieldname): validate_column_length(fieldname) - def check_illegal_mandatory(d): + def check_illegal_mandatory(docname, d): if (d.fieldtype in no_value_fields) and d.fieldtype!="Table" and d.reqd: - frappe.throw(_("{0}: Field {1} of type {2} cannot be mandatory").format(self.name, d.label, d.fieldtype)) + frappe.throw(_("{0}: Field {1} of type {2} cannot be mandatory").format(docname, d.label, d.fieldtype), IllegalMandatoryError) - def check_link_table_options(d): + def check_link_table_options(docname, d): if d.fieldtype in ("Link", "Table"): if not d.options: - frappe.throw(_("{0}: Options required for Link or Table type field {1} in row {2}").format(self.name, d.label, d.idx)) + frappe.throw(_("{0}: Options required for Link or Table type field {1} in row {2}").format(docname, d.label, d.idx), DoctypeLinkError) if d.options=="[Select]" or d.options==d.parent: return if d.options != d.parent: options = frappe.db.get_value("DocType", d.options, "name") if not options: - frappe.throw(_("{0}: Options must be a valid DocType for field {1} in row {2}").format(self.name, d.label, d.idx)) + frappe.throw(_("{0}: Options must be a valid DocType for field {1} in row {2}").format(docname, d.label, d.idx), WrongOptionsDoctypeLinkError) elif not (options == d.options): - frappe.throw(_("{0}: Options {1} must be the same as doctype name {2} for the field {3}") - .format(self.name, d.options, options, d.label)) + frappe.throw(_("{0}: Options {1} must be the same as doctype name {2} for the field {3}", DoctypeLinkError) + .format(docname, d.options, options, d.label)) else: # fix case d.options = options - def check_hidden_and_mandatory(d): + def check_hidden_and_mandatory(docname, d): if d.hidden and d.reqd and not d.default: - frappe.throw(_("{0}: Field {1} in row {2} cannot be hidden and mandatory without default").format(self.name, d.label, d.idx)) + frappe.throw(_("{0}: Field {1} in row {2} cannot be hidden and mandatory without default").format(docname, d.label, d.idx), HiddenAndMandatoryWithoutDefaultError) def check_width(d): if d.fieldtype == "Currency" and cint(d.width) < 100: @@ -544,14 +551,14 @@ def validate_fields(meta): if d.fieldtype in ("Currency", "Float", "Percent") and d.precision is not None and not (1 <= cint(d.precision) <= 6): frappe.throw(_("Precision should be between 1 and 6")) - def check_unique_and_text(d): + def check_unique_and_text(docname, d): if meta.issingle: d.unique = 0 d.search_index = 0 if getattr(d, "unique", False): if d.fieldtype not in ("Data", "Link", "Read Only"): - frappe.throw(_("{0}: Fieldtype {1} for {2} cannot be unique").format(self.name, d.fieldtype, d.label)) + frappe.throw(_("{0}: Fieldtype {1} for {2} cannot be unique").format(docname, d.fieldtype, d.label), NonUniqueError) if not d.get("__islocal"): try: @@ -571,10 +578,10 @@ def validate_fields(meta): else: # else of try block if has_non_unique_values and has_non_unique_values[0][0]: - frappe.throw(_("{0}: Field '{1}' cannot be set as Unique as it has non-unique values").format(self.name, d.label)) + frappe.throw(_("{0}: Field '{1}' cannot be set as Unique as it has non-unique values").format(docname, d.label), NonUniqueError) if d.search_index and d.fieldtype in ("Text", "Long Text", "Small Text", "Code", "Text Editor"): - frappe.throw(_("Fieldtype {0} for {1} cannot be indexed").format(d.fieldtype, d.label)) + frappe.throw(_("{0}:Fieldtype {1} for {2} cannot be indexed").format(docname, d.fieldtype, d.label), CannotIndexedError) def check_fold(fields): fold_exists = False @@ -713,16 +720,16 @@ def validate_fields(meta): d.fieldname = d.fieldname.lower() check_illegal_characters(d.fieldname) - check_unique_fieldname(d.fieldname) + check_unique_fieldname(meta.get("name"), d.fieldname) check_fieldname_length(d.fieldname) - check_illegal_mandatory(d) - check_link_table_options(d) + check_illegal_mandatory(meta.get("name"), d) + check_link_table_options(meta.get("name"), d) check_dynamic_link_options(d) - check_hidden_and_mandatory(d) + check_hidden_and_mandatory(meta.get("name"), d) check_in_list_view(d) check_in_global_search(d) check_illegal_default(d) - check_unique_and_text(d) + check_unique_and_text(meta.get("name"), d) check_illegal_depends_on_conditions(d) scrub_options_in_select(d) scrub_fetch_from(d) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 41a4267c97..03052ecdd0 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -5,6 +5,8 @@ from __future__ import unicode_literals import frappe import unittest +from frappe.core.doctype.doctype.doctype import UniqueFieldnameError, IllegalMandatoryError, DoctypeLinkError, WrongOptionsDoctypeLinkError,\ + HiddenAndMandatoryWithoutDefaultError, CannotIndexedError, CannotCreateStandardDoctypeError # test_records = frappe.get_test_records('DocType') @@ -104,4 +106,56 @@ 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)) \ No newline at end of file + self.assertFalse(re.match(pattern, condition)) + + def test_unique_field_name_for_two_fields(self): + doc = self.new_doctype('Test Unique Field') + field_1 = doc.append('fields', {}) + field_1.fieldname = 'some_fieldname_1' + field_1.fieldtype = 'Data' + + field_2 = doc.append('fields', {}) + field_2.fieldname = 'some_fieldname_1' + field_2.fieldtype = 'Data' + + self.assertRaises(UniqueFieldnameError, doc.insert) + + def test_illegal_mandatory_validation(self): + doc = self.new_doctype('Test Illegal mandatory') + field_1 = doc.append('fields', {}) + field_1.fieldname = 'some_fieldname_1' + field_1.fieldtype = 'Section Break' + field_1.reqd = 1 + + self.assertRaises(IllegalMandatoryError, doc.insert) + + def test_link_with_wrong_and_no_options(self): + doc = self.new_doctype('Test link') + field_1 = doc.append('fields', {}) + field_1.fieldname = 'some_fieldname_1' + field_1.fieldtype = 'Link' + + self.assertRaises(DoctypeLinkError, doc.insert) + + field_1.options = 'wrongdoctype' + + self.assertRaises(WrongOptionsDoctypeLinkError, doc.insert) + + def test_hidden_and_mandatory_without_default(self): + doc = self.new_doctype('Test hidden and mandatory') + field_1 = doc.append('fields', {}) + field_1.fieldname = 'some_fieldname_1' + field_1.fieldtype = 'Data' + field_1.reqd = 1 + field_1.hidden = 1 + + self.assertRaises(HiddenAndMandatoryWithoutDefaultError, doc.insert) + + def test_field_can_not_be_indexed_validation(self): + doc = self.new_doctype('Test index') + field_1 = doc.append('fields', {}) + field_1.fieldname = 'some_fieldname_1' + field_1.fieldtype = 'Long Text' + field_1.search_index = 1 + + self.assertRaises(CannotIndexedError, doc.insert) \ No newline at end of file From 4aaea40a72ed90765c5814ef389ce6a8cddc4491 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 22 May 2019 14:58:48 +0530 Subject: [PATCH 121/176] fix: not able to make print format using Print Format Builder (#7533) * fix: not able to make print format using Print Format Builder * fix: Set default based_on as "Standard" --- .../printing/page/print_format_builder/print_format_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/printing/page/print_format_builder/print_format_builder.py b/frappe/printing/page/print_format_builder/print_format_builder.py index 17baa9314e..c6ccc29d96 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder.py +++ b/frappe/printing/page/print_format_builder/print_format_builder.py @@ -1,7 +1,7 @@ import frappe @frappe.whitelist() -def create_custom_format(doctype, name, based_on): +def create_custom_format(doctype, name, based_on='Standard'): doc = frappe.new_doc('Print Format') doc.doc_type = doctype doc.name = name From 44e6d3338fcdca7d73cc91dc0168722e857b51c5 Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Wed, 22 May 2019 16:13:32 +0550 Subject: [PATCH 122/176] bumped to version 11.1.30 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 8624e8df2b..a348aeb77d 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '11.1.29' +__version__ = '11.1.30' __title__ = "Frappe Framework" local = Local() From 5e4f3d6e1799ccc159d7e30bea8457b0cf7d301c Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 23 May 2019 09:37:04 +0530 Subject: [PATCH 123/176] fix: print format builder setup (#7526) * fix: print format builder setup * style: change formatting --- .../print_format_builder.js | 22 +++++++++++++------ .../print_format_builder.py | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/frappe/printing/page/print_format_builder/print_format_builder.js b/frappe/printing/page/print_format_builder/print_format_builder.js index e853035620..e34388b719 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder.js +++ b/frappe/printing/page/print_format_builder/print_format_builder.js @@ -130,13 +130,21 @@ frappe.PrintFormatBuilder = Class.extend({ }); }, setup_new_print_format: function(doctype, name, based_on) { - frappe.call('frappe.printing.page.print_format_builder.print_format_builder.create_custom_format', { - doctype, - name, - based_on - }).then((r) => { - this.print_format = r.message; - this.refresh(); + frappe.call({ + method: 'frappe.printing.page.print_format_builder.print_format_builder.create_custom_format', + args: { + doctype: doctype, + name: name, + based_on: based_on + }, + callback: (r) => { + if(!r.exc) { + if(r.message) { + this.print_format = r.message; + this.refresh(); + } + } + }, }); }, setup_print_format: function() { diff --git a/frappe/printing/page/print_format_builder/print_format_builder.py b/frappe/printing/page/print_format_builder/print_format_builder.py index c6ccc29d96..d9f57762b0 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder.py +++ b/frappe/printing/page/print_format_builder/print_format_builder.py @@ -9,4 +9,4 @@ def create_custom_format(doctype, name, based_on='Standard'): doc.format_data = frappe.db.get_value('Print Format', based_on, 'format_data') \ if based_on != 'Standard' else None doc.insert() - return doc + return doc \ No newline at end of file From 18015dfcf77a1a8b4408223d86cb1b7280180cb8 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 23 May 2019 13:16:02 +0530 Subject: [PATCH 124/176] fix: has permission --- frappe/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/permissions.py b/frappe/permissions.py index 753019cc7a..23d522371a 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -30,7 +30,7 @@ def print_has_permission_check_logs(func): # print only if access denied # and if user is checking his own permission if not result and self_perm_check and raise_exception: - msgprint(('
').join(frappe.flags.get('has_permission_check_logs'))) + msgprint(('
').join(frappe.flags.get('has_permission_check_logs', []))) frappe.flags.pop('has_permission_check_logs', None) return result return inner From a0ac4143117b1ece7bca0d8114d6b3205cb89098 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 23 May 2019 15:00:43 +0530 Subject: [PATCH 125/176] fix: Upgrade frappe datatable --- package.json | 2 +- yarn.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 8e938ac137..3bf733cf8a 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "cookie": "^0.3.1", "express": "^4.16.2", "fast-deep-equal": "^2.0.1", - "frappe-datatable": "^1.13.0", + "frappe-datatable": "^1.13.1", "frappe-gantt": "^0.1.0", "fuse.js": "^3.2.0", "highlight.js": "^9.12.0", diff --git a/yarn.lock b/yarn.lock index 1ac0efb420..611503e063 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1224,10 +1224,10 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -frappe-datatable@^1.13.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.13.0.tgz#b0b283664e946aeedf1ec6a4e678bac98b274a34" - integrity sha512-Vmtkzgtk7fQ4RTuNNHtAE3doeIKg8m1YUVP5K8srS2eYepPQLmR1HIzBWcMlzkXPNOyYIEEmWK+hZ0l///Ka8w== +frappe-datatable@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.13.1.tgz#dbfa27fe735832cea54a0b35e3d3bfb839778c8d" + integrity sha512-FOC8dpsOSI+KnF5sBrYja9b2Y+3qUvYy/H6108QchKSvXYvuWVr/uuLk2N5xlz38PWU3d7n5lK0dkx+zXTKJ0w== dependencies: hyperlist "^1.0.0-beta" lodash "^4.17.5" @@ -3578,9 +3578,9 @@ sort-keys@^1.0.0: is-plain-obj "^1.0.0" sortablejs@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.7.0.tgz#80a2b2370abd568e1cec8c271131ef30a904fa28" - integrity sha1-gKKyNwq9Vo4c7IwnETHvMKkE+ig= + version "1.9.0" + resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.9.0.tgz#2d1e74ae6bac2cb4ad0622908f340848969eb88d" + integrity sha512-Ot6bYJ6PoqPmpsqQYXjn1+RKrY2NWQvQt/o4jfd/UYwVWndyO5EPO8YHbnm5HIykf8ENsm4JUrdAvolPT86yYA== source-map@0.6.*, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" From 5fb0f468bd66cc755b56c0971e122239793ec941 Mon Sep 17 00:00:00 2001 From: Don-Leopardo <46027152+Don-Leopardo@users.noreply.github.com> Date: Thu, 23 May 2019 06:50:49 -0300 Subject: [PATCH 126/176] fix: Order and filters in query report custom template (#7359) * fix ordered with no template and new property: original_data * fix no extra method --- .../public/js/frappe/views/reports/query_report.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 938fe915c9..84c4991cbf 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -797,8 +797,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { print_settings: print_settings, landscape: landscape, filters: this.get_filter_values(), - data: custom_format ? this.data : this.get_data_for_print(), + data: this.get_data_for_print(), columns: custom_format ? this.columns : this.get_columns_for_print(), + original_data: this.data, report: this }); } @@ -810,7 +811,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { const custom_format = this.report_settings.html_format || null; const columns = custom_format ? this.columns : this.get_columns_for_print(); - const data = custom_format ? this.data : this.get_data_for_print(); + const data = this.get_data_for_print(); const applied_filters = this.get_filter_values(); const filters_html = this.get_filters_html_for_print(); @@ -819,6 +820,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { subtitle: filters_html, filters: applied_filters, data: data, + original_data: this.data, columns: columns, report: this }); @@ -923,8 +925,11 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } get_data_for_print() { - const indices = this.datatable.datamanager.getFilteredRowIndices(); - let rows = indices.map(i => this.data[i]); + const rows = this.datatable.datamanager.rowViewOrder.map(index => { + if (this.datatable.bodyRenderer.visibleRowIndices.includes(index)) { + return this.data[index]; + } + }).filter(Boolean); let totalRow = this.datatable.bodyRenderer.getTotalRow().reduce((row, cell) => { row[cell.column.id] = cell.content; return row; From 2a4a658c3c4e5fdc8eecb059c3ac3a05d72ccc95 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Fri, 24 May 2019 08:37:50 +0530 Subject: [PATCH 127/176] fix: Bulk user permission unhandled condition(v11) (#7402) * fix: unhandled condition * test: if changed from apply_to_all to apply_to_all * fix: test_cases * Update frappe/core/doctype/user_permission/test_user_permission.py Co-Authored-By: Suraj Shetty * fix: tests * fix: refactor --- .../user_permission/test_user_permission.py | 64 ++++++++++++++----- .../user_permission/user_permission.py | 9 ++- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/frappe/core/doctype/user_permission/test_user_permission.py b/frappe/core/doctype/user_permission/test_user_permission.py index b83d103013..bcf155f0a2 100644 --- a/frappe/core/doctype/user_permission/test_user_permission.py +++ b/frappe/core/doctype/user_permission/test_user_permission.py @@ -8,43 +8,75 @@ import frappe import unittest class TestUserPermission(unittest.TestCase): + def setUp(self): + frappe.db.sql("Delete from `tabUser Permission` where user='test_bulk_creation_update@example.com'") + def test_apply_to_all(self): ''' Create User permission for User having access to all applicable Doctypes''' user = get_user() - param = get_params(user, apply = 1) - created = add_user_permissions(param) - self.assertEquals(created, 1) + param = get_params(user, apply_to_all = 1) + is_created = add_user_permissions(param) + self.assertEquals(is_created, 1) + + def test_for_apply_to_all_on_update_from_apply_all(self): + user = get_user() + param = get_params(user, apply_to_all=1) + + # Initially create User Permission document with apply_to_all checked + is_created = add_user_permissions(param) + + self.assertEquals(is_created, 1) + is_created = add_user_permissions(param) + + # User Permission should not be changed + self.assertEquals(is_created, 0) def test_for_applicable_on_update_from_apply_to_all(self): ''' Update User Permission from all to some applicable Doctypes''' user = get_user() param = get_params(user, applicable = ["Chat Room", "Chat Message"]) - create = add_user_permissions(param) + + # Initially create User Permission document with apply_to_all checked + is_created = add_user_permissions(get_params(user, apply_to_all= 1)) + + self.assertEquals(is_created, 1) + is_created = add_user_permissions(param) frappe.db.commit() removed_apply_to_all = frappe.db.exists("User Permission", get_exists_param(user)) - created_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Room")) - created_applicable_second = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Message")) + is_created_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Room")) + is_created_applicable_second = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Message")) + # Check that apply_to_all is removed self.assertIsNone(removed_apply_to_all) - self.assertIsNotNone(created_applicable_first) - self.assertIsNotNone(created_applicable_second) - self.assertEquals(create, 1) + + # Check that User Permissions for applicable is created + self.assertIsNotNone(is_created_applicable_first) + self.assertIsNotNone(is_created_applicable_second) + self.assertEquals(is_created, 1) def test_for_apply_to_all_on_update_from_applicable(self): ''' Update User Permission from some to all applicable Doctypes''' user = get_user() - param = get_params(user, apply = 1) - created = add_user_permissions(param) - created_apply_to_all = frappe.db.exists("User Permission", get_exists_param(user)) + param = get_params(user, apply_to_all = 1) + + # create User permissions that with applicable + is_created = add_user_permissions(get_params(user, applicable = ["Chat Room", "Chat Message"])) + + self.assertEquals(is_created, 1) + + is_created = add_user_permissions(param) + is_created_apply_to_all = frappe.db.exists("User Permission", get_exists_param(user)) removed_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Room")) removed_applicable_second = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Message")) + # To check that a User permission with apply_to_all exists + self.assertIsNotNone(is_created_apply_to_all) - self.assertIsNotNone(created_apply_to_all) + # Check that all User Permission with applicable is removed self.assertIsNone(removed_applicable_first) self.assertIsNone(removed_applicable_second) - self.assertEquals(created, 1) + self.assertEquals(is_created, 1) def get_user(): if frappe.db.exists('User', 'test_bulk_creation_update@example.com'): @@ -56,14 +88,14 @@ def get_user(): user.add_roles("System Manager") return user -def get_params(user, apply = None , applicable = None): +def get_params(user, apply_to_all = None , applicable = None): ''' Return param to insert ''' param = { "user": user.name, "doctype":"User", "docname":user.name } - if apply: + if apply_to_all: param.update({"apply_to_all_doctypes": 1}) param.update({"applicable_doctypes": []}) if applicable: diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index 4dd152b54e..4f20e93a4e 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -158,12 +158,17 @@ def add_user_permissions(data): data = frappe._dict(data) d = check_applicable_doc_perm(data.user, data.doctype, data.docname) - exists = frappe.db.exists("User Permission", {"user": data.user, "allow": data.doctype, "for_value": data.docname, "apply_to_all_doctypes": 1}) + exists = frappe.db.exists("User Permission", { + "user": data.user, + "allow": data.doctype, + "for_value": data.docname, + "apply_to_all_doctypes": 1 + }) if data.apply_to_all_doctypes == 1 and not exists: remove_applicable(d, data.user, data.doctype, data.docname) insert_user_perm(data.user, data.doctype, data.docname, apply_to_all = 1) return 1 - else: + elif len(data.applicable_doctypes) > 0 and data.apply_to_all_doctypes != 1: remove_apply_to_all(data.user, data.doctype, data.docname) update_applicable(d, data.applicable_doctypes, data.user, data.doctype, data.docname) for applicable in data.applicable_doctypes : From e6b3e91510f49cadad63b182482989e17a585d85 Mon Sep 17 00:00:00 2001 From: Felipe Orellana Date: Fri, 24 May 2019 08:28:20 +0000 Subject: [PATCH 128/176] fix: Fixes route rewrite issue causing dynamic links on addresses to disapear --- frappe/public/js/frappe/form/save.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js index 23a8b41129..9daa6b8f3b 100644 --- a/frappe/public/js/frappe/form/save.js +++ b/frappe/public/js/frappe/form/save.js @@ -185,7 +185,10 @@ frappe.ui.form.save = function (frm, action, callback, btn) { throw "saving"; } - frappe.ui.form.remove_old_form_route(); + // ensure we remove new docs routes ONLY + if ( frm.is_new() ) { + frappe.ui.form.remove_old_form_route(); + } frappe.ui.form.is_saving = true; return frappe.call({ @@ -219,14 +222,9 @@ frappe.ui.form.save = function (frm, action, callback, btn) { } frappe.ui.form.remove_old_form_route = () => { - let index = -1; - let current_route = frappe.get_route(); - frappe.route_history.map((arr, i) => { - if (arr.join("/") === current_route.join("/")) { - index = i; - } - }); - frappe.route_history.splice(index, 1); + let current_route = frappe.get_route().join("/"); + frappe.route_history = frappe.route_history + .filter((route) => route.join("/") !== current_route); } frappe.ui.form.update_calling_link = (newdoc) => { From b7712ebbd6a71b72a2d31d0373bca2c6abafbf5c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 24 May 2019 18:21:48 +0530 Subject: [PATCH 129/176] fix: incorrect schedule showing in the draft mode --- frappe/desk/doctype/auto_repeat/auto_repeat.js | 17 +++++++++-------- frappe/desk/doctype/auto_repeat/auto_repeat.py | 9 ++++++--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/frappe/desk/doctype/auto_repeat/auto_repeat.js b/frappe/desk/doctype/auto_repeat/auto_repeat.js index 935d4de9a4..ca96cb2b45 100644 --- a/frappe/desk/doctype/auto_repeat/auto_repeat.js +++ b/frappe/desk/doctype/auto_repeat/auto_repeat.js @@ -29,9 +29,9 @@ frappe.ui.form.on('Auto Repeat', { }, refresh: function(frm) { - + if(frm.doc.docstatus == 1) { - + let label = __('View {0}', [__(frm.doc.reference_doctype)]); frm.add_custom_button(__(label), function() { @@ -57,12 +57,13 @@ frappe.ui.form.on('Auto Repeat', { } ); } - - if(frm.doc.docstatus!= 0 && !frm.doc.status.includes('Stopped', 'Cancelled') && frm.doc.next_schedule_date >= frappe.datetime.get_today()){ - frappe.auto_repeat.render_schedule(frm); - } } - + + frm.toggle_display('auto_repeat_schedule', !in_list(['Stopped', 'Cancelled'], frm.doc.status)); + if(frm.doc.start_date && !in_list(['Stopped', 'Cancelled'], frm.doc.status)){ + frappe.auto_repeat.render_schedule(frm); + } + }, stop_resume_auto_repeat: function(frm, status) { @@ -138,6 +139,6 @@ frappe.auto_repeat.render_schedule = function(frm) { }).done((r) => { var wrapper = $(frm.fields_dict["auto_repeat_schedule"].wrapper); wrapper.html(frappe.render_template ("auto_repeat_schedule", {"schedule_details" : r.message || []} )); + frm.refresh_fields(); }); - frm.refresh_fields() ; }; \ No newline at end of file diff --git a/frappe/desk/doctype/auto_repeat/auto_repeat.py b/frappe/desk/doctype/auto_repeat/auto_repeat.py index 8a90b011d8..1394d128a7 100644 --- a/frappe/desk/doctype/auto_repeat/auto_repeat.py +++ b/frappe/desk/doctype/auto_repeat/auto_repeat.py @@ -19,9 +19,6 @@ month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12} class AutoRepeat(Document): - def onload(self): - self.set_onload("auto_repeat_schedule", self.get_auto_repeat_schedule()) - def validate(self): self.update_status() self.validate_reference_doctype() @@ -47,6 +44,9 @@ class AutoRepeat(Document): def on_submit(self): self.update_auto_repeat_id() + def on_cancel(self): + self.update_status() + def on_update_after_submit(self): self.validate_dates() self.set_next_schedule_date() @@ -109,6 +109,9 @@ class AutoRepeat(Document): if status and status != 'Resumed': self.status = status + if self.docstatus == 2: + self.db_set("status", self.status) + def get_auto_repeat_schedule(self): schedule_details = [] start_date_copy = getdate(self.start_date) From 6fdbb6fd792aa9bc6487eb67f10611f42700371b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 25 May 2019 11:55:47 +0530 Subject: [PATCH 130/176] fix: Error message for invalid Workflow Transition (#7552) When you import a document of a DocType which has a workflow, you shouldn't be able to set the workflow_state to a state except the first. This patch shows a better error message. --- frappe/model/workflow.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index 6a89ccce5b..cdbf0de774 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -128,10 +128,20 @@ def validate_workflow(doc): # if transitioning, check if user is allowed to transition if current_state != next_state: + bold_current = frappe.bold(current_state) + bold_next = frappe.bold(next_state) + + if not doc._doc_before_save: + # transitioning directly to a state other than the first + # e.g from data import + frappe.throw(_('Workflow State transition not allowed from {0} to {1}').format(bold_current, bold_next), + WorkflowPermissionError) + transitions = get_transitions(doc._doc_before_save) transition = [d for d in transitions if d.next_state == next_state] if not transition: - frappe.throw(_('Workflow State {0} is not allowed').format(frappe.bold(next_state)), WorkflowPermissionError) + frappe.throw(_('Workflow State transition not allowed from {0} to {1}').format(bold_current, bold_next), + WorkflowPermissionError) def get_workflow(doctype): return frappe.get_doc('Workflow', get_workflow_name(doctype)) From 1f395ea91f1169f3a8ef932dfdc33d169bb29ac1 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 28 May 2019 10:23:08 +0530 Subject: [PATCH 131/176] fix: Skip website attr for Custom DocTypes (#7565) --- frappe/website/router.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/website/router.py b/frappe/website/router.py index e9ab0a5269..b6af1f4b9a 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -97,7 +97,10 @@ def get_page_info_from_doctypes(path=None): values = [] controller = get_controller(doctype) meta = frappe.get_meta(doctype) - condition_field = meta.is_published_field or controller.website.condition_field + + condition_field = (meta.is_published_field or + # custom doctypes dont have controllers and no website attribute + (controller.website.condition_field if not meta.custom else None)) if condition_field: condition ="where {0}=1".format(condition_field) From ac99dbaf49bca22595c3b43f13df47e17864e44a Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Tue, 28 May 2019 15:08:39 +0550 Subject: [PATCH 132/176] bumped to version 11.1.31 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index a348aeb77d..5cb0df79ca 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '11.1.30' +__version__ = '11.1.31' __title__ = "Frappe Framework" local = Local() From 568ae60b7b44d828938bf3852afe6c3912bd6a38 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 28 May 2019 20:39:12 +0530 Subject: [PATCH 133/176] fix: same result set shwoing multiple times in the multi-select popup --- .../public/js/frappe/form/multi_select_dialog.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index 3438c59a1f..4e569033ba 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -59,6 +59,7 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ { fieldtype: "Button", fieldname: "more_btn", label: __("More"), click: function(){ me.start += 20; + frappe.flags.auto_scroll = true; me.get_results(); } } @@ -117,10 +118,12 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ }); this.$parent.find('.input-with-feedback').on('change', (e) => { + frappe.flags.auto_scroll = false; this.get_results(); }); this.$parent.find('[data-fieldname="date_range"]').on('blur', (e) => { + frappe.flags.auto_scroll = false; this.get_results(); }); @@ -128,6 +131,7 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ var $this = $(this); clearTimeout($this.data('timeout')); $this.data('timeout', setTimeout(function() { + frappe.flags.auto_scroll = false; me.get_results(); }, 300)); }); @@ -176,11 +180,17 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ var me = this; var more_btn = me.dialog.fields_dict.more_btn.$wrapper; + + // Make empty result set if filter is set + if (!frappe.flags.auto_scroll) { + this.$results.empty(); + } + if(results.length === 0) { this.$results.empty(); more_btn.hide(); return; - } else { + } else if(more) { more_btn.show(); } @@ -188,7 +198,9 @@ frappe.ui.form.MultiSelectDialog = Class.extend({ me.$results.append(me.make_list_row(result)); }); - this.$results.animate({scrollTop: me.$results.prop('scrollHeight')}, 500); + if (frappe.flags.auto_scroll) { + this.$results.animate({scrollTop: me.$results.prop('scrollHeight')}, 500); + } }, get_results: function() { From 3992d7ab21766f4187b1f7f8c213264f0947ffe2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 29 May 2019 10:07:11 +0530 Subject: [PATCH 134/176] fix: Update frappe datatable (#7579) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3bf733cf8a..8052734731 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "cookie": "^0.3.1", "express": "^4.16.2", "fast-deep-equal": "^2.0.1", - "frappe-datatable": "^1.13.1", + "frappe-datatable": "^1.13.2", "frappe-gantt": "^0.1.0", "fuse.js": "^3.2.0", "highlight.js": "^9.12.0", diff --git a/yarn.lock b/yarn.lock index 611503e063..75aa246a07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1224,10 +1224,10 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -frappe-datatable@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.13.1.tgz#dbfa27fe735832cea54a0b35e3d3bfb839778c8d" - integrity sha512-FOC8dpsOSI+KnF5sBrYja9b2Y+3qUvYy/H6108QchKSvXYvuWVr/uuLk2N5xlz38PWU3d7n5lK0dkx+zXTKJ0w== +frappe-datatable@^1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.13.2.tgz#8b36c7cfc0ea660fc72eea8b1ae3c5dcc2a7d67d" + integrity sha512-4PyPDX22K4e4S3WGlLQx3oyxIW+ENsbGiN9L6aUpmjU+fOCC7J/FfSwGKdua2f+4yD+2ObpkyJYazBl3inAeCA== dependencies: hyperlist "^1.0.0-beta" lodash "^4.17.5" From 778bc4349b96a4c802495c6cb1f592e30d60f903 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 29 May 2019 11:40:53 +0530 Subject: [PATCH 135/176] fix: Check if value is string (#7583) Breaks the check when value is a map. So we reverse the check. --- frappe/model/db_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index e009dd354c..a7ff19a2cd 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -381,7 +381,7 @@ class DatabaseQuery(object): elif f.operator.lower() in ('in', 'not in'): values = f.value or '' - if not isinstance(values, (list, tuple)): + if isinstance(values, frappe.string_types): values = values.split(",") fallback = "''" From 5a194be77bae6cb4f41da69c8048b542070fec19 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 29 May 2019 13:03:48 +0530 Subject: [PATCH 136/176] fix: UniqueFieldnameError while migration --- .../v11_0/apply_customization_to_custom_doctype.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frappe/patches/v11_0/apply_customization_to_custom_doctype.py b/frappe/patches/v11_0/apply_customization_to_custom_doctype.py index d7c10a903d..0855616711 100644 --- a/frappe/patches/v11_0/apply_customization_to_custom_doctype.py +++ b/frappe/patches/v11_0/apply_customization_to_custom_doctype.py @@ -39,13 +39,17 @@ def execute(): df.set(ps.property, value) for cf in custom_fields: - df = frappe.new_doc('DocField', meta, 'fields') cf.pop('parenttype') cf.pop('parentfield') cf.pop('parent') cf.pop('name') - df.update(cf) - meta.fields.append(df) + field = meta.get_field(cf.fieldname) + if field: + field.update(cf) + else: + df = frappe.new_doc('DocField', meta, 'fields') + df.update(cf) + meta.fields.append(df) frappe.db.sql('DELETE FROM `tabCustom Field` WHERE name=%s', cf.name) meta.save() From be427b146439d5dace46d18e2b18a6b21fc9a98c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 29 May 2019 13:13:10 +0530 Subject: [PATCH 137/176] fix: issue in the latest version of babel, set it to 2.6.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cc94f7cc5c..e832dbdf13 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ rauth>=0.6.2 requests redis==2.10.6 selenium -babel +babel==2.6.0 ipython html2text==2016.9.19 email_reply_parser From a1f441578cce51c342b91a5f96b07eff1864e546 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 29 May 2019 13:25:54 +0530 Subject: [PATCH 138/176] fix: Update regex for capturing touched tables from query Previous regex used to yield false positives and false negatives for queries like UPDATE tabToDo SET description = "something" Instead of yielding "tabToDo" it used to yield "tabToDo SET". Now two separate regexes handle single word and multi-word names In case of multi-word surrounding quotes are a must --- frappe/database.py | 20 +++++++++++++++++--- frappe/tests/test_db.py | 5 +++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/frappe/database.py b/frappe/database.py index 9323aa9518..3a2e0ce160 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -987,12 +987,26 @@ class Database: if values: query = self._cursor.mogrify(query, values) if query.strip().lower().split()[0] in ('insert', 'delete', 'update', 'alter'): - # ([`\"']?) Captures ', " or ` at the begining of the table name (if provided) + # single_word_regex is designed to match following patterns + # `tabXxx`, tabXxx and "tabXxx" + + # multi_word_regex is designed to match following patterns + # `tabXxx Xxx` and "tabXxx Xxx" + + # ([`"]?) Captures " or ` at the begining of the table name (if provided) + # \1 matches the first captured group (quote character) at the end of the table name + # multi word table name must have surrounding quotes. + # (tab([A-Z]\w+)( [A-Z]\w+)*) Captures table names that start with "tab" # and are continued with multiple words that start with a captital letter # e.g. 'tabXxx' or 'tabXxx Xxx' or 'tabXxx Xxx Xxx' and so on - # \1 matches the first captured group (quote character) at the end of the table name - tables = [groups[1] for groups in re.findall(r'([`"\']?)(tab([A-Z]\w+)( [A-Z]\w+)*)\1', query)] + + single_word_regex = r'([`"]?)(tab([A-Z]\w+))\1' + multi_word_regex = r'([`"])(tab([A-Z]\w+)( [A-Z]\w+)+)\1' + tables = [] + for regex in (single_word_regex, multi_word_regex): + tables += [groups[1] for groups in re.findall(regex, query)] + if frappe.flags.touched_tables is None: frappe.flags.touched_tables = set() frappe.flags.touched_tables.update(tables) diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index e3c1a62a78..551b0f2f27 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -45,6 +45,11 @@ class TestDB(unittest.TestCase): todo.save() self.assertIn('tabToDo', frappe.flags.touched_tables) + frappe.flags.touched_tables = set() + frappe.db.sql("UPDATE tabToDo SET description = 'Updated Description'") + self.assertNotIn('tabToDo SET', frappe.flags.touched_tables) + self.assertIn('tabToDo', frappe.flags.touched_tables) + frappe.flags.touched_tables = set() todo.delete() self.assertIn('tabToDo', frappe.flags.touched_tables) From abd822b943b36fdfe2fd33b16d5b7398ef210645 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 29 May 2019 14:36:10 +0530 Subject: [PATCH 139/176] fix: Remove dynamic link logic from make_new function --- frappe/public/js/legacy/client_script_helpers.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/frappe/public/js/legacy/client_script_helpers.js b/frappe/public/js/legacy/client_script_helpers.js index d9a2d59740..60baeb16bd 100644 --- a/frappe/public/js/legacy/client_script_helpers.js +++ b/frappe/public/js/legacy/client_script_helpers.js @@ -509,12 +509,7 @@ _f.Frm.prototype.make_new = function(doctype) { // set link fields (if found) frappe.get_meta(doctype).fields.forEach(function(df) { - if(df.fieldtype==='Link' && df.options==="DocType") { - new_doc[df.fieldname] = me.doctype; - } - - if((df.fieldtype==='Link' && df.options===me.doctype) - || (df.fieldtype==='Dynamic Link' && new_doc[df.options] === me.doctype)) { + if(df.fieldtype==='Link' && df.options===me.doctype) { new_doc[df.fieldname] = me.doc.name; } }); From 0fa0d4324a6951af296f722b79c31bec92adb9c2 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 29 May 2019 17:49:48 +0530 Subject: [PATCH 140/176] perf: Don't fetch complete global unsubscribe (#7578) * Update queue.py * Update email_unsubscribe.json * Update email_unsubscribe.json --- .../email_unsubscribe/email_unsubscribe.json | 6 ++-- frappe/email/queue.py | 34 ++++++++++++++----- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/frappe/email/doctype/email_unsubscribe/email_unsubscribe.json b/frappe/email/doctype/email_unsubscribe/email_unsubscribe.json index 3624a55641..b24359f82f 100644 --- a/frappe/email/doctype/email_unsubscribe/email_unsubscribe.json +++ b/frappe/email/doctype/email_unsubscribe/email_unsubscribe.json @@ -3,7 +3,7 @@ "allow_import": 0, "allow_rename": 0, "beta": 0, - "creation": "2015-03-18 09:41:20.216319", + "creation": "2015-03-18 09:41:20.216320", "custom": 0, "docstatus": 0, "doctype": "DocType", @@ -35,7 +35,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, - "search_index": 0, + "search_index": 1, "set_only_once": 0, "unique": 0 }, @@ -172,4 +172,4 @@ "sort_order": "DESC", "track_changes": 1, "track_seen": 0 -} \ No newline at end of file +} diff --git a/frappe/email/queue.py b/frappe/email/queue.py index e62e7ca674..d0893d66d9 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -78,19 +78,35 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content= except HTMLParser.HTMLParseError: text_content = "See html attachment" - if reference_doctype and reference_name: - unsubscribed = [d.email for d in frappe.db.get_all("Email Unsubscribe", "email", - {"reference_doctype": reference_doctype, "reference_name": reference_name})] + recipients = list(set(recipients)) + cc = list(set(cc)) - unsubscribed += [d.email for d in frappe.db.get_all("Email Unsubscribe", "email", - {"global_unsubscribe": 1})] - else: - unsubscribed = [] + all_ids = recipients + cc - recipients = [r for r in list(set(recipients)) if r and r not in unsubscribed] + unsubscribed = frappe.db.sql_list(''' + SELECT + distinct email + from + `tabEmail Unsubscribe` + where + email in %(all_ids)s + and ( + ( + reference_doctype = %(reference_doctype)s + and reference_name = %(reference_name)s + ) + or global_unsubscribe = 1 + ) + ''', { + 'all_ids': all_ids, + 'reference_doctype': reference_doctype, + 'reference_name': reference_name, + }) + + recipients = [r for r in recipients if r and r not in unsubscribed] if cc: - cc = [r for r in list(set(cc)) if r and r not in unsubscribed] + cc = [r for r in cc if r and r not in unsubscribed] if not recipients and not cc: # Recipients may have been unsubscribed, exit quietly From 279ec5e5331ad14ac967574a3283d2c23aaca254 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 29 May 2019 17:57:19 +0530 Subject: [PATCH 141/176] fix(email): Search contacts by email_id as well Email auto suggest now performs lookup on contact name and email_id fields --- frappe/email/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py index 53c6140d04..a48dbec195 100644 --- a/frappe/email/__init__.py +++ b/frappe/email/__init__.py @@ -23,7 +23,7 @@ def get_contact_list(txt, page_length=20): out = frappe.db.sql("""select email_id as value, concat(first_name, ifnull(concat(' ',last_name), '' )) as description from tabContact - where name like %(txt)s + where name like %(txt)s or email_id like %(txt)s %(condition)s limit %(page_length)s """, {'txt': "%%%s%%" % frappe.db.escape(txt), From e9c54ff6a8138b3dbaeb375708a17a7a29cef664 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 30 May 2019 10:52:36 +0530 Subject: [PATCH 142/176] fix(list-view): Escape quotes in data-name (#7599) Fixes misbehaviour in bulk actions when selected documents have names with quotes. --- frappe/public/js/frappe/list/list_view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 841f62d6a3..b5b94339e1 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -642,7 +642,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { .includes(user) ? '' : 'bold'; let subject_html = ` - +
{{ schedule_details[i].reference_document }} {{ schedule_details[i].frequency }} {{ schedule_details[i].next_scheduled_date }} {{ frappe.format_date(schedule_details[i].next_scheduled_date) }}
{{ schedule_details[i].reference_document }} {{ schedule_details[i].frequency }} {{ frappe.format_date(schedule_details[i].next_scheduled_date) }} {{ schedule_details[i].next_scheduled_date }}
- {{ - col.formatter - ? col.formatter(row._index, col._index, value, col, row, true) - : col.format - ? col.format(value, row, col, data) - : col.docfield - ? frappe.format(value, col.docfield) - : value - }} + + {{ + col.formatter + ? col.formatter(row._index, col._index, value, col, row, true) + : col.format + ? col.format(value, row, col, data) + : col.docfield + ? frappe.format(value, col.docfield) + : value + }} +