- | {{ j.queue.split(".").slice(-1)[0] }} |
+ {{ j.queue.split(".").slice(-1)[0] }} |
{{ frappe.utils.encode_tags(j.job_name) }}
diff --git a/frappe/core/page/background_jobs/background_jobs.py b/frappe/core/page/background_jobs/background_jobs.py
index 10cb7b97ac..c8a2352968 100644
--- a/frappe/core/page/background_jobs/background_jobs.py
+++ b/frappe/core/page/background_jobs/background_jobs.py
@@ -29,9 +29,9 @@ def get_info(show_failed=False):
jobs.append({
'job_name': j.kwargs.get('kwargs', {}).get('playbook_method') \
or str(j.kwargs.get('job_name')),
- 'status': j.status, 'queue': name,
+ 'status': j.get_status(), 'queue': name,
'creation': format_datetime(convert_utc_to_user_timezone(j.created_at)),
- 'color': colors[j.status]
+ 'color': colors[j.get_status()]
})
if j.exc_info:
jobs[-1]['exc_info'] = j.exc_info
diff --git a/frappe/database/db_manager.py b/frappe/database/db_manager.py
index 0447f97273..80236b2dc2 100644
--- a/frappe/database/db_manager.py
+++ b/frappe/database/db_manager.py
@@ -80,12 +80,14 @@ class DbManager:
if pipe:
print('Creating Database...')
- command = '{pipe} mysql -u {user} -p{password} -h{host} {target} {source}'.format(
+ command = '{pipe} mysql -u {user} -p{password} -h{host} ' + ('-P{port}' if frappe.db.port else '') + ' {target} {source}'
+ command = command.format(
pipe=pipe,
user=esc(user),
password=esc(password),
host=esc(frappe.db.host),
target=esc(target),
- source=source
+ source=source,
+ port=frappe.db.port
)
os.system(command)
diff --git a/frappe/desk/doctype/global_search_settings/global_search_settings.py b/frappe/desk/doctype/global_search_settings/global_search_settings.py
index 0729fca5cb..85c9687ab3 100644
--- a/frappe/desk/doctype/global_search_settings/global_search_settings.py
+++ b/frappe/desk/doctype/global_search_settings/global_search_settings.py
@@ -29,12 +29,16 @@ class GlobalSearchSettings(Document):
repeated_dts = (", ".join([frappe.bold(dt) for dt in repeated_dts]))
frappe.throw(_("Document Type {0} has been repeated.").format(repeated_dts))
-def get_doctypes_for_global_search():
- doctypes = frappe.get_list("Global Search DocType", fields=["document_type"], order_by="idx ASC")
- if not doctypes:
- return []
+ # reset cache
+ frappe.cache().hdel('global_search', 'search_priorities')
+
+def get_doctypes_for_global_search():
+ def get_from_db():
+ doctypes = frappe.get_list("Global Search DocType", fields=["document_type"], order_by="idx ASC")
+ return [d.document_type for d in doctypes] or []
+
+ return frappe.cache().hget("global_search", "search_priorities", get_from_db)
- return [d.document_type for d in doctypes]
@frappe.whitelist()
def reset_global_search_settings_doctypes():
@@ -57,7 +61,7 @@ def update_global_search_doctypes():
if search_doctypes.get(domain):
global_search_doctypes.extend(search_doctypes.get(domain))
- doctype_list = set([dt.name for dt in frappe.get_list("DocType")])
+ doctype_list = set([dt.name for dt in frappe.get_all("DocType")])
allowed_in_global_search = []
for dt in global_search_doctypes:
diff --git a/frappe/desk/doctype/setup_wizard_help_link/__init__.py b/frappe/desk/doctype/onboarding_slide/__init__.py
similarity index 100%
rename from frappe/desk/doctype/setup_wizard_help_link/__init__.py
rename to frappe/desk/doctype/onboarding_slide/__init__.py
diff --git a/frappe/desk/doctype/setup_wizard_slide/setup_wizard_slide.js b/frappe/desk/doctype/onboarding_slide/onboarding_slide.js
similarity index 73%
rename from frappe/desk/doctype/setup_wizard_slide/setup_wizard_slide.js
rename to frappe/desk/doctype/onboarding_slide/onboarding_slide.js
index 7a802ad2b6..dc91f42913 100644
--- a/frappe/desk/doctype/setup_wizard_slide/setup_wizard_slide.js
+++ b/frappe/desk/doctype/onboarding_slide/onboarding_slide.js
@@ -1,10 +1,10 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Setup Wizard Slide', {
+frappe.ui.form.on('Onboarding Slide', {
refresh: function(frm) {
- frm.toggle_reqd('ref_doctype', frm.doc.slide_type!='Information');
- frm.toggle_reqd('slide_module', frm.doc.slide_type=='Information');
+ frm.toggle_reqd('ref_doctype', (frm.doc.slide_type=='Create' || frm.doc.slide_type=='Settings'));
+ frm.toggle_reqd('slide_module', (frm.doc.slide_type=='Information' || frm.doc.slide_type=='Continue'));
},
ref_doctype: function(frm) {
@@ -33,7 +33,7 @@ frappe.ui.form.on('Setup Wizard Slide', {
reqd: 1
});
$.each(fields, function(_i, data) {
- let row = frappe.model.add_child(frm.doc, 'Setup Wizard Slide', 'slide_fields');
+ let row = frappe.model.add_child(frm.doc, 'Onboarding Slide', 'slide_fields');
row.label = data.label;
row.fieldtype = data.fieldtype;
row.fieldname = data.fieldname;
diff --git a/frappe/desk/doctype/setup_wizard_slide/setup_wizard_slide.json b/frappe/desk/doctype/onboarding_slide/onboarding_slide.json
similarity index 78%
rename from frappe/desk/doctype/setup_wizard_slide/setup_wizard_slide.json
rename to frappe/desk/doctype/onboarding_slide/onboarding_slide.json
index 94d94851c3..3f6f0d719f 100644
--- a/frappe/desk/doctype/setup_wizard_slide/setup_wizard_slide.json
+++ b/frappe/desk/doctype/onboarding_slide/onboarding_slide.json
@@ -15,7 +15,6 @@
"slide_desc",
"action_section_break",
"slide_type",
- "submit_method",
"column_break_6",
"max_count",
"add_more_button",
@@ -25,7 +24,8 @@
"section_break_10",
"domains",
"column_break_12",
- "help_links"
+ "help_links",
+ "is_completed"
],
"fields": [
{
@@ -36,13 +36,6 @@
"reqd": 1,
"unique": 1
},
- {
- "depends_on": "eval:doc.slide_type!='Information'",
- "description": "By default the code inside `create_onboarding_docs` method of the `Reference Document Type` is executed. If your method is not on the doctype level, place this method in {app_name}.utilities.onboarding_utils.{method_name} and specify the method name here",
- "fieldname": "submit_method",
- "fieldtype": "Data",
- "label": "Submit Method"
- },
{
"fieldname": "slide_desc",
"fieldtype": "HTML Editor",
@@ -58,17 +51,17 @@
},
{
"default": "0",
- "depends_on": "eval:doc.slide_type!='Information'",
+ "depends_on": "eval:doc.slide_type=='Create' || doc.slide_type=='Settings'",
"fieldname": "add_more_button",
"fieldtype": "Check",
"label": "Add More Button"
},
{
- "depends_on": "eval:doc.slide_type!='Information'",
+ "depends_on": "eval:doc.slide_type=='Create' || doc.slide_type=='Settings'",
"fieldname": "slide_fields",
"fieldtype": "Table",
"label": "Slide Fields",
- "options": "Setup Wizard Slide Field"
+ "options": "Onboarding Slide Field"
},
{
"fieldname": "section_break_10",
@@ -90,7 +83,7 @@
"fieldname": "help_links",
"fieldtype": "Table",
"label": "Help Links",
- "options": "Setup Wizard Help Link"
+ "options": "Onboarding Slide Help Link"
},
{
"fieldname": "action_section_break",
@@ -98,11 +91,11 @@
"label": "Action Settings"
},
{
- "description": "If slide type is Action there should be a submit method bound to be executed after the slide is completed.",
+ "description": "If Slide Type is Create or Settings there should be a 'create_onboarding_docs' method in the {ref_doctype}.py file bound to be executed after the slide is completed.",
"fieldname": "slide_type",
"fieldtype": "Select",
"label": "Slide Type",
- "options": "Information\nCreate\nSettings",
+ "options": "Information\nCreate\nSettings\nContinue",
"reqd": 1
},
{
@@ -131,7 +124,7 @@
"label": "Description"
},
{
- "depends_on": "eval:doc.slide_type!='Information'",
+ "depends_on": "eval:doc.slide_type=='Create' || doc.slide_type=='Settings'",
"fieldname": "ref_doctype",
"fieldtype": "Link",
"label": "Reference Document Type",
@@ -145,22 +138,31 @@
"label": "Slide Order"
},
{
- "depends_on": "eval:doc.slide_type=='Information'",
+ "depends_on": "eval:doc.slide_type=='Information' || doc.slide_type=='Continue'",
"fieldname": "slide_module",
"fieldtype": "Link",
"label": "Module",
"options": "Module Def"
},
{
+ "collapsible_depends_on": "eval:doc.slide_type=='Create' || doc.slide_type=='Settings'",
"fieldname": "section_break_18",
"fieldtype": "Section Break",
"label": "Fields"
+ },
+ {
+ "default": "0",
+ "fieldname": "is_completed",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Is Completed",
+ "print_hide": 1
}
],
- "modified": "2019-11-26 17:33:34.553367",
+ "modified": "2019-12-04 10:50:43.528901",
"modified_by": "Administrator",
"module": "Desk",
- "name": "Setup Wizard Slide",
+ "name": "Onboarding Slide",
"owner": "Administrator",
"permissions": [
{
diff --git a/frappe/desk/doctype/onboarding_slide/onboarding_slide.py b/frappe/desk/doctype/onboarding_slide/onboarding_slide.py
new file mode 100644
index 0000000000..8c75d10b9a
--- /dev/null
+++ b/frappe/desk/doctype/onboarding_slide/onboarding_slide.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import json
+from frappe import _
+from frappe.model.document import Document
+from frappe.modules.export_file import export_to_files
+
+class OnboardingSlide(Document):
+ def validate(self):
+ if self.slide_type == 'Continue' and frappe.db.exists('Onboarding Slide', {'slide_type': 'Continue', 'name': ('!=', self.name)}):
+ frappe.throw(_('An Onboarding Slide of Slide Type Continue already exists.'))
+
+ if self.slide_order:
+ same_order_slide = frappe.db.exists('Onboarding Slide', {'slide_order': self.slide_order, 'name': ('!=', self.name)})
+ if same_order_slide:
+ frappe.throw(_('An Onboarding Slide {0} with the same slide order already exists').format(same_order_slide))
+
+ def on_update(self):
+ if self.ref_doctype:
+ module = frappe.db.get_value('DocType', self.ref_doctype, 'module')
+ else:
+ module = self.slide_module
+ export_to_files(record_list=[['Onboarding Slide', self.name]], record_module=module)
+
+def get_onboarding_slides_as_list():
+ slides = []
+ slide_docs = frappe.db.get_all('Onboarding Slide',
+ filters={'is_completed': 0},
+ or_filters={'slide_order': ('!=', 0), 'slide_type': 'Continue'},
+ order_by='slide_order')
+
+ # to check if continue slide is required
+ first_slide = get_first_slide()
+
+ for entry in slide_docs:
+ # using get_doc because child table fields are not fetched in get_all
+ slide_doc = frappe.get_doc('Onboarding Slide', entry.name)
+ if frappe.scrub(slide_doc.app) in frappe.get_installed_apps():
+ slide = frappe._dict(
+ slide_type=slide_doc.slide_type,
+ title=slide_doc.slide_title,
+ help=slide_doc.slide_desc,
+ fields=slide_doc.slide_fields,
+ help_links=get_help_links(slide_doc),
+ add_more=slide_doc.add_more_button,
+ max_count=slide_doc.max_count,
+ image_src=get_slide_image(slide_doc),
+ ref_doctype=slide_doc.ref_doctype,
+ app=slide_doc.app
+ )
+ if slide.slide_type == 'Continue':
+ if is_continue_slide_required(first_slide):
+ slides.insert(0, slide)
+ else:
+ slides.append(slide)
+
+ return slides
+
+@frappe.whitelist()
+def get_onboarding_slides():
+ slides = []
+ slide_list = get_onboarding_slides_as_list()
+
+ active_domains = frappe.get_active_domains()
+ for slide in slide_list:
+ if not slide.domains or any(domain in active_domains for domain in slide.domains):
+ slides.append(slide)
+ return slides
+
+def get_help_links(slide_doc):
+ links=[]
+ for link in slide_doc.help_links:
+ links.append({
+ 'label': link.label,
+ 'video_id': link.video_id
+ })
+ return links
+
+def get_slide_image(slide_doc):
+ if slide_doc.image_src:
+ return slide_doc.image_src
+ return None
+
+def is_continue_slide_required(first_slide):
+ # check if first slide itself is not completed
+ if not first_slide.is_completed:
+ return False
+
+ # check if there is any active slide which is not completed
+ return frappe.db.exists('Onboarding Slide', {
+ 'is_completed': 0,
+ 'slide_order': ('!=', 0),
+ 'slide_type': ('!=', 'Continue')
+ })
+
+@frappe.whitelist()
+def create_onboarding_docs(values, doctype=None, app=None, slide_type=None):
+ data = json.loads(values)
+ doc = frappe.new_doc(doctype)
+ if hasattr(doc, 'create_onboarding_docs'):
+ doc.create_onboarding_docs(data)
+ else:
+ create_generic_onboarding_doc(data, doctype, slide_type)
+
+def create_generic_onboarding_doc(data, doctype, slide_type):
+ if slide_type == 'Settings':
+ doc = frappe.get_single(doctype)
+ for entry in data:
+ doc.set(entry, data.get(entry))
+ doc.save()
+
+ elif slide_type == 'Create':
+ doc = frappe.new_doc(doctype)
+ for entry in data:
+ doc.set(entry, data.get(entry))
+ doc.flags.ignore_mandatory = True
+ doc.flags.ignore_links = True
+ doc.insert()
+
+@frappe.whitelist()
+def mark_slide_as_completed(slide_title):
+ frappe.db.set_value('Onboarding Slide', slide_title, 'is_completed', 1)
+
+def get_first_slide():
+ slides = frappe.db.get_all('Onboarding Slide',
+ filters={'slide_order': ('!=', 0), 'slide_type': ('!=', 'Continue')},
+ order_by='slide_order',
+ fields=['name', 'is_completed']
+ )
+ return slides[0]
\ No newline at end of file
diff --git a/frappe/desk/doctype/setup_wizard_slide/test_setup_wizard_slide.py b/frappe/desk/doctype/onboarding_slide/test_onboarding_slide.py
similarity index 79%
rename from frappe/desk/doctype/setup_wizard_slide/test_setup_wizard_slide.py
rename to frappe/desk/doctype/onboarding_slide/test_onboarding_slide.py
index 58652c4ec2..d78b9b6158 100644
--- a/frappe/desk/doctype/setup_wizard_slide/test_setup_wizard_slide.py
+++ b/frappe/desk/doctype/onboarding_slide/test_onboarding_slide.py
@@ -6,5 +6,5 @@ from __future__ import unicode_literals
# import frappe
import unittest
-class TestSetupWizardSlide(unittest.TestCase):
+class TestOnboardingSlide(unittest.TestCase):
pass
diff --git a/frappe/desk/doctype/setup_wizard_slide/__init__.py b/frappe/desk/doctype/onboarding_slide_field/__init__.py
similarity index 100%
rename from frappe/desk/doctype/setup_wizard_slide/__init__.py
rename to frappe/desk/doctype/onboarding_slide_field/__init__.py
diff --git a/frappe/desk/doctype/setup_wizard_slide_field/setup_wizard_slide_field.json b/frappe/desk/doctype/onboarding_slide_field/onboarding_slide_field.json
similarity index 94%
rename from frappe/desk/doctype/setup_wizard_slide_field/setup_wizard_slide_field.json
rename to frappe/desk/doctype/onboarding_slide_field/onboarding_slide_field.json
index 03d58d842b..0992aed092 100644
--- a/frappe/desk/doctype/setup_wizard_slide_field/setup_wizard_slide_field.json
+++ b/frappe/desk/doctype/onboarding_slide_field/onboarding_slide_field.json
@@ -33,12 +33,6 @@
"in_list_view": 1,
"label": "Fieldname"
},
- {
- "fieldname": "options",
- "fieldtype": "Text",
- "in_list_view": 1,
- "label": "Options"
- },
{
"fieldname": "align",
"fieldtype": "Select",
@@ -60,13 +54,19 @@
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "options",
+ "fieldtype": "Text",
+ "in_list_view": 1,
+ "label": "Options"
}
],
"istable": 1,
- "modified": "2019-11-25 16:50:53.994656",
+ "modified": "2019-12-02 16:43:51.930018",
"modified_by": "Administrator",
"module": "Desk",
- "name": "Setup Wizard Slide Field",
+ "name": "Onboarding Slide Field",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
diff --git a/frappe/desk/doctype/setup_wizard_help_link/setup_wizard_help_link.py b/frappe/desk/doctype/onboarding_slide_field/onboarding_slide_field.py
similarity index 86%
rename from frappe/desk/doctype/setup_wizard_help_link/setup_wizard_help_link.py
rename to frappe/desk/doctype/onboarding_slide_field/onboarding_slide_field.py
index fbb96bbd91..74b6782ff8 100644
--- a/frappe/desk/doctype/setup_wizard_help_link/setup_wizard_help_link.py
+++ b/frappe/desk/doctype/onboarding_slide_field/onboarding_slide_field.py
@@ -6,5 +6,5 @@ from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
-class SetupWizardHelpLink(Document):
+class OnboardingSlideField(Document):
pass
diff --git a/frappe/desk/doctype/setup_wizard_slide_field/__init__.py b/frappe/desk/doctype/onboarding_slide_help_link/__init__.py
similarity index 100%
rename from frappe/desk/doctype/setup_wizard_slide_field/__init__.py
rename to frappe/desk/doctype/onboarding_slide_help_link/__init__.py
diff --git a/frappe/desk/doctype/setup_wizard_help_link/setup_wizard_help_link.json b/frappe/desk/doctype/onboarding_slide_help_link/onboarding_slide_help_link.json
similarity index 94%
rename from frappe/desk/doctype/setup_wizard_help_link/setup_wizard_help_link.json
rename to frappe/desk/doctype/onboarding_slide_help_link/onboarding_slide_help_link.json
index b97b482cac..a09ba50553 100644
--- a/frappe/desk/doctype/setup_wizard_help_link/setup_wizard_help_link.json
+++ b/frappe/desk/doctype/onboarding_slide_help_link/onboarding_slide_help_link.json
@@ -25,7 +25,7 @@
"modified": "2019-11-19 13:39:57.716248",
"modified_by": "Administrator",
"module": "Desk",
- "name": "Setup Wizard Help Link",
+ "name": "Onboarding Slide Help Link",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
diff --git a/frappe/desk/doctype/setup_wizard_slide_field/setup_wizard_slide_field.py b/frappe/desk/doctype/onboarding_slide_help_link/onboarding_slide_help_link.py
similarity index 85%
rename from frappe/desk/doctype/setup_wizard_slide_field/setup_wizard_slide_field.py
rename to frappe/desk/doctype/onboarding_slide_help_link/onboarding_slide_help_link.py
index 1b880ef916..6ee5e4f7d7 100644
--- a/frappe/desk/doctype/setup_wizard_slide_field/setup_wizard_slide_field.py
+++ b/frappe/desk/doctype/onboarding_slide_help_link/onboarding_slide_help_link.py
@@ -6,5 +6,5 @@ from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
-class SetupWizardSlideField(Document):
+class OnboardingSlideHelpLink(Document):
pass
diff --git a/frappe/desk/doctype/setup_wizard_slide/setup_wizard_slide.py b/frappe/desk/doctype/setup_wizard_slide/setup_wizard_slide.py
deleted file mode 100644
index 9109d52a85..0000000000
--- a/frappe/desk/doctype/setup_wizard_slide/setup_wizard_slide.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2019, Frappe Technologies and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-import json
-from frappe.model.document import Document
-from frappe.modules.export_file import export_to_files
-
-class SetupWizardSlide(Document):
- def on_update(self):
- if self.ref_doctype:
- module = frappe.db.get_value('DocType', self.ref_doctype, 'module')
- else:
- module = self.slide_module
- export_to_files(record_list=[['Setup Wizard Slide', self.name]], record_module=module)
-
-def get_onboarding_slides_as_list():
- slides = []
- slide_docs = frappe.get_all('Setup Wizard Slide',
- filters={'slide_order': ('!=', 0)},
- order_by='slide_order')
- for entry in slide_docs:
- # using get_doc because child table fields are not fetched in get_all
- slide_doc = frappe.get_doc('Setup Wizard Slide', entry.name)
- if frappe.scrub(slide_doc.app) in frappe.get_installed_apps():
- slides.append(frappe._dict(
- slide_type=slide_doc.slide_type,
- title=slide_doc.slide_title,
- help=slide_doc.slide_desc,
- fields=slide_doc.slide_fields,
- help_links=get_help_links(slide_doc),
- add_more=slide_doc.add_more_button,
- max_count=slide_doc.max_count,
- submit_method=slide_doc.submit_method,
- image_src=get_slide_image(slide_doc),
- ref_doctype=slide_doc.ref_doctype,
- app=slide_doc.app
- ))
- return slides
-
-@frappe.whitelist()
-def get_onboarding_slides():
- slides = []
- slide_list = get_onboarding_slides_as_list()
-
- active_domains = frappe.get_active_domains()
- for slide in slide_list:
- if not slide.domains or any(domain in active_domains for domain in slide.domains):
- slides.append(slide)
- return slides
-
-def get_help_links(slide_doc):
- links=[]
- for link in slide_doc.help_links:
- links.append({
- 'label': link.label,
- 'video_id': link.video_id
- })
- return links
-
-def get_slide_image(slide_doc):
- if slide_doc.image_src:
- return slide_doc.image_src
- return None
-
-@frappe.whitelist()
-def create_onboarding_docs(values, doctype=None, submit_method=None, app=None, slide_type=None):
- data = json.loads(values)
- if submit_method:
- try:
- method = frappe.scrub(app) + '.utilities.onboarding_utils.' + submit_method
- frappe.call(method, data)
- except AttributeError:
- create_generic_onboarding_doc(data, doctype, slide_type)
- else:
- doc = frappe.new_doc(doctype)
- if hasattr(doc, 'create_onboarding_docs'):
- doc.create_onboarding_docs(data)
- else:
- create_generic_onboarding_doc(data, doctype, slide_type)
-
-def create_generic_onboarding_doc(data, doctype, slide_type):
- if slide_type == 'Settings':
- doc = frappe.get_single(doctype)
- for entry in data:
- doc.set(entry, data.get(entry))
- doc.save()
-
- elif slide_type == 'Create':
- doc = frappe.new_doc(doctype)
- for entry in data:
- doc.set(entry, data.get(entry))
- doc.flags.ignore_mandatory = True
- doc.flags.ignore_links = True
- doc.insert()
\ No newline at end of file
diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py
index 6676bd1908..cf2a8f0879 100644
--- a/frappe/desk/form/utils.py
+++ b/frappe/desk/form/utils.py
@@ -105,7 +105,7 @@ def get_next(doctype, value, prev, filters, sort_order, sort_field):
res = frappe.get_list(doctype,
fields = ["name"],
filters = filters,
- order_by = sort_field + " " + sort_order,
+ order_by = "`tab{0}`.{1}".format(doctype, sort_field) + " " + sort_order,
limit_start=0, limit_page_length=1, as_list=True)
if not res:
diff --git a/frappe/desk/listview.py b/frappe/desk/listview.py
index fd0833eb51..e7f56d313e 100644
--- a/frappe/desk/listview.py
+++ b/frappe/desk/listview.py
@@ -48,7 +48,7 @@ def get_group_by_count(doctype, current_filters, field):
else:
return frappe.db.get_list(doctype,
filters=current_filters,
- group_by=field,
+ group_by='`tab{0}`.{1}'.format(doctype, field),
fields=['count(*) as count', '`{}` as name'.format(field)],
order_by='count desc',
limit=50,
diff --git a/frappe/desk/page/leaderboard/leaderboard.css b/frappe/desk/page/leaderboard/leaderboard.css
index dbe9cca5b8..a3cb4d09c4 100644
--- a/frappe/desk/page/leaderboard/leaderboard.css
+++ b/frappe/desk/page/leaderboard/leaderboard.css
@@ -19,6 +19,14 @@
background: #f0f4f7;
}
+.from-date-field .clearfix{
+ display: none;
+}
+
+.from-date-field {
+ margin-left: 10px;
+}
+
.select-time:focus, .select-doctype:focus, .select-filter:focus, .select-sort:focus {
background: #f0f4f7;
}
diff --git a/frappe/desk/page/leaderboard/leaderboard.js b/frappe/desk/page/leaderboard/leaderboard.js
index 3e4c36add0..c64d2dcb4f 100644
--- a/frappe/desk/page/leaderboard/leaderboard.js
+++ b/frappe/desk/page/leaderboard/leaderboard.js
@@ -41,7 +41,11 @@ class Leaderboard {
return field;
});
}
- this.timespans = ["Week", "Month", "Quarter", "Year", "All Time"];
+ this.timespans = [
+ "This Week", "This Month", "This Quarter", "This Year",
+ "Last Week", "Last Month", "Last Quarter", "Last Year",
+ "All Time", "Select From Date"
+ ];
// for saving current selected filters
const _initial_doctype = frappe.get_route()[1] || this.doctypes[0];
@@ -103,7 +107,8 @@ class Leaderboard {
this.timespans.map(d => {
return {"label": __(d), value: d };
})
- );
+ );
+ this.create_from_date_field();
this.type_select = this.page.add_select(__("Field"),
this.options.selected_filter.map(d => {
@@ -113,7 +118,12 @@ class Leaderboard {
this.timespan_select.on("change", (e) => {
this.options.selected_timespan = e.currentTarget.value;
- this.make_request();
+ if (this.options.selected_timespan === 'Select From Date') {
+ this.from_date_field.show();
+ } else {
+ this.from_date_field.hide();
+ this.make_request();
+ }
});
this.type_select.on("change", (e) => {
@@ -122,6 +132,28 @@ class Leaderboard {
});
}
+ create_from_date_field() {
+ let timespan_field = $(this.parent).find(`.frappe-control[data-original-title='Timespan']`);
+ this.from_date_field = $(` `).insertAfter(timespan_field).hide();
+
+ let date_field = frappe.ui.form.make_control({
+ df: {
+ fieldtype: 'Date',
+ fieldname: 'selected_from_date',
+ placeholder: frappe.datetime.month_start(),
+ default: frappe.datetime.month_start(),
+ input_class: 'input-sm',
+ reqd: 1,
+ change: () => {
+ this.selected_from_date = date_field.get_value();
+ if (this.selected_from_date) this.make_request();
+ }
+ },
+ parent: $(this.parent).find('.from-date-field'),
+ render_input: 1
+ });
+ }
+
render_selected_doctype() {
this.$sidebar_list.on("click", "li", (e)=> {
@@ -207,7 +239,6 @@ class Leaderboard {
this.leaderboard_config[this.options.selected_doctype].method,
{
'from_date': this.get_from_date(),
- 'timespan': this.options.selected_timespan,
'company': this.options.selected_company,
'field': this.options.selected_filter_item,
'limit': this.leaderboard_limit,
@@ -360,17 +391,20 @@ class Leaderboard {
get_from_date() {
let timespan = this.options.selected_timespan.toLowerCase();
let current_date = frappe.datetime.now_date();
- let date = '';
- if (timespan === "month") {
- date = frappe.datetime.add_months(current_date, -1);
- } else if (timespan === "quarter") {
- date = frappe.datetime.add_months(current_date, -3);
- } else if (timespan === "year") {
- date = frappe.datetime.add_months(current_date, -12);
- } else if (timespan === "week") {
- date = frappe.datetime.add_days(current_date, -7);
+ let get_from_date = {
+ "this week": frappe.datetime.week_start(),
+ "this month": frappe.datetime.month_start(),
+ "this quarter": frappe.datetime.quarter_start(),
+ "this year": frappe.datetime.year_start(),
+ "last week": frappe.datetime.add_days(current_date, -7),
+ "last month": frappe.datetime.add_months(current_date, -1),
+ "last quarter": frappe.datetime.add_months(current_date, -3),
+ "last year": frappe.datetime.add_months(current_date, -12),
+ "all time": "",
+ "select from date": this.selected_from_date || frappe.datetime.month_start()
}
- return date;
+
+ return get_from_date[timespan];
}
}
diff --git a/frappe/desk/search.py b/frappe/desk/search.py
index 7bcfe646ab..c70b650945 100644
--- a/frappe/desk/search.py
+++ b/frappe/desk/search.py
@@ -50,7 +50,7 @@ def sanitize_searchfield(searchfield):
# this is called by the Link Field
@frappe.whitelist()
def search_link(doctype, txt, query=None, filters=None, page_length=20, searchfield=None, reference_doctype=None, ignore_user_permissions=False):
- search_widget(doctype, txt, query, searchfield=searchfield, page_length=page_length, filters=filters, reference_doctype=reference_doctype, ignore_user_permissions=ignore_user_permissions)
+ search_widget(doctype, txt.strip(), query, searchfield=searchfield, page_length=page_length, filters=filters, reference_doctype=reference_doctype, ignore_user_permissions=ignore_user_permissions)
frappe.response['results'] = build_for_autosuggest(frappe.response["values"])
del frappe.response["values"]
diff --git a/frappe/email/receive.py b/frappe/email/receive.py
index bccad1fec5..e5c8457b4e 100644
--- a/frappe/email/receive.py
+++ b/frappe/email/receive.py
@@ -456,9 +456,9 @@ class Email:
def show_attached_email_headers_in_content(self, part):
# get the multipart/alternative message
try:
- from html import escape # python 3.x
+ from html import escape # python 3.x
except ImportError:
- from cgi import escape # python 2.x
+ from cgi import escape # python 2.x
message = list(part.walk())[1]
headers = []
@@ -480,7 +480,7 @@ class Email:
"""Detect chartset."""
charset = part.get_content_charset()
if not charset:
- charset = chardet.detect(frappe.safe_encode(part))['encoding']
+ charset = chardet.detect(cstr(part))['encoding']
return charset
@@ -514,7 +514,7 @@ class Email:
'fcontent': fcontent,
})
- cid = (part.get("Content-Id") or "").strip("><")
+ cid = (cstr(part.get("Content-Id")) or "").strip("><")
if cid:
self.cid_map[fname] = cid
diff --git a/frappe/geo/doctype/currency/currency.js b/frappe/geo/doctype/currency/currency.js
index 1bc9865999..af2d6ebc4e 100644
--- a/frappe/geo/doctype/currency/currency.js
+++ b/frappe/geo/doctype/currency/currency.js
@@ -7,10 +7,5 @@ frappe.ui.form.on('Currency', {
if(!frm.doc.enabled) {
frm.set_intro(__("This Currency is disabled. Enable to use in transactions"));
}
- },
-
- after_save(frm) {
- if (frm.doc.enabled)
- locals[':Currency'][frm.doc.name] = Object.assign(frm.doc, { doctype: ':Currency' });
}
});
diff --git a/frappe/installer.py b/frappe/installer.py
index f691a6cb22..4b07ab8ce8 100755
--- a/frappe/installer.py
+++ b/frappe/installer.py
@@ -21,13 +21,13 @@ from frappe.database import setup_database
from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs
def install_db(root_login="root", root_password=None, db_name=None, source_sql=None,
- admin_password=None, verbose=True, force=0, site_config=None, reinstall=False,
- db_type=None):
+ admin_password=None, verbose=True, force=0, site_config=None, reinstall=False,
+ db_type=None, db_host=None, db_port=None):
if not db_type:
db_type = frappe.conf.db_type or 'mariadb'
- make_conf(db_name, site_config=site_config, db_type=db_type)
+ make_conf(db_name, site_config=site_config, db_type=db_type, db_host=db_host, db_port=db_port)
frappe.flags.in_install_db = True
frappe.flags.root_login = root_login
@@ -191,14 +191,14 @@ def init_singles():
doc.flags.ignore_validate=True
doc.save()
-def make_conf(db_name=None, db_password=None, site_config=None, db_type=None):
+def make_conf(db_name=None, db_password=None, site_config=None, db_type=None, db_host=None, db_port=None):
site = frappe.local.site
- make_site_config(db_name, db_password, site_config, db_type=db_type)
+ make_site_config(db_name, db_password, site_config, db_type=db_type, db_host=db_host, db_port=db_port)
sites_path = frappe.local.sites_path
frappe.destroy()
frappe.init(site, sites_path=sites_path)
-def make_site_config(db_name=None, db_password=None, site_config=None, db_type=None):
+def make_site_config(db_name=None, db_password=None, site_config=None, db_type=None, db_host=None, db_port=None):
frappe.create_folder(os.path.join(frappe.local.site_path))
site_file = get_site_config_path()
@@ -209,6 +209,12 @@ def make_site_config(db_name=None, db_password=None, site_config=None, db_type=N
if db_type:
site_config['db_type'] = db_type
+ if db_host:
+ site_config['db_host'] = db_host
+
+ if db_port:
+ site_config['db_port'] = db_port
+
with open(site_file, "w") as f:
f.write(json.dumps(site_config, indent=1, sort_keys=True))
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index 64e242d6af..f9016d7fcf 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -501,6 +501,10 @@ class DatabaseQuery(object):
value = f.value or "''"
fallback = "''"
+ elif f.fieldname == 'name':
+ value = f.value or "''"
+ fallback = "''"
+
else:
value = flt(f.value)
fallback = 0
diff --git a/frappe/model/sync.py b/frappe/model/sync.py
index 989e1fbb99..b50ddb1160 100644
--- a/frappe/model/sync.py
+++ b/frappe/model/sync.py
@@ -44,9 +44,9 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe
("data_migration", "data_migration_mapping"),
("data_migration", "data_migration_plan_mapping"),
("data_migration", "data_migration_plan"),
- ("desk", "setup_wizard_slide_field"),
- ("desk", "setup_wizard_help_link"),
- ("desk", "setup_wizard_slide")):
+ ("desk", "onboarding_slide_field"),
+ ("desk", "onboarding_slide_help_link"),
+ ("desk", "onboarding_slide")):
files.append(os.path.join(frappe.get_app_path("frappe"), d[0],
"doctype", d[1], d[1] + ".json"))
@@ -75,7 +75,7 @@ def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=F
# load in sequence - warning for devs
document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format',
'website_theme', 'web_form', 'notification', 'print_style',
- 'data_migration_mapping', 'data_migration_plan', 'setup_wizard_slide']
+ 'data_migration_mapping', 'data_migration_plan', 'onboarding_slide']
for doctype in document_types:
doctype_path = os.path.join(start_path, doctype)
if os.path.exists(doctype_path):
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 603df8d778..90abc8c31d 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -6,11 +6,11 @@ frappe.patches.v8_0.update_global_search_table
frappe.patches.v7_0.update_auth
frappe.patches.v8_0.drop_in_dialog #2017-09-22
frappe.patches.v7_2.remove_in_filter
-frappe.patches.v11_0.drop_column_apply_user_permissions
-execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2017-09-22
-execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2018-02-20
execute:frappe.reload_doc('core', 'doctype', 'doctype_action', force=True) #2019-09-23
execute:frappe.reload_doc('core', 'doctype', 'doctype_link', force=True) #2019-09-23
+execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2017-09-22
+execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2018-02-20
+frappe.patches.v11_0.drop_column_apply_user_permissions
execute:frappe.reload_doc('core', 'doctype', 'custom_docperm')
execute:frappe.reload_doc('core', 'doctype', 'docperm') #2018-05-29
execute:frappe.reload_doc('core', 'doctype', 'comment')
diff --git a/frappe/patches/v11_0/make_all_prepared_report_attachments_private.py b/frappe/patches/v11_0/make_all_prepared_report_attachments_private.py
index dd212d157e..f7b9e476a9 100644
--- a/frappe/patches/v11_0/make_all_prepared_report_attachments_private.py
+++ b/frappe/patches/v11_0/make_all_prepared_report_attachments_private.py
@@ -3,6 +3,9 @@ import frappe
def execute():
+ if frappe.db.count("File", filters={"attached_to_doctype": "Prepared Report", "is_private": 0}) > 10000:
+ frappe.db.auto_commit_on_many_writes = True
+
files = frappe.get_all("File", fields=["name", "attached_to_name"], filters={"attached_to_doctype": "Prepared Report", "is_private": 0})
for file_dict in files:
# For some reason Prepared Report doc might not exist, check if it exists first
@@ -17,3 +20,7 @@ def execute():
else:
# If Prepared Report doc doesn't exist then the file doc is useless. Delete it.
frappe.delete_doc("File", file_dict.name)
+
+ if frappe.db.auto_commit_on_many_writes:
+ frappe.db.auto_commit_on_many_writes = False
+
diff --git a/frappe/printing/setup_wizard_slide/company_letter_head/company_letter_head.json b/frappe/printing/onboarding_slide/company_letter_head/company_letter_head.json
similarity index 61%
rename from frappe/printing/setup_wizard_slide/company_letter_head/company_letter_head.json
rename to frappe/printing/onboarding_slide/company_letter_head/company_letter_head.json
index 3644a54089..2f51d2e18a 100644
--- a/frappe/printing/setup_wizard_slide/company_letter_head/company_letter_head.json
+++ b/frappe/printing/onboarding_slide/company_letter_head/company_letter_head.json
@@ -3,23 +3,23 @@
"app": "ERPNext",
"creation": "2019-11-22 13:25:42.892593",
"docstatus": 0,
- "doctype": "Setup Wizard Slide",
+ "doctype": "Onboarding Slide",
"domains": [],
"help_links": [
{
- "label": "Know more about printing and branding through letterhead",
+ "label": "Need Help?",
"video_id": "cKZHcx1znMc"
}
],
"idx": 0,
- "image_src": "/assets/erpnext/images/illustrations/letterhead.png",
+ "image_src": "/assets/erpnext/images/illustrations/letterhead-onboard.png",
"max_count": 0,
- "modified": "2019-11-27 11:39:56.213373",
+ "modified": "2019-12-03 22:54:57.618989",
"modified_by": "Administrator",
"name": "Company Letter Head",
"owner": "Administrator",
"ref_doctype": "Letter Head",
- "slide_desc": "Attach Letterhead: (Keep it web friendly as 1024px by 128px)",
+ "slide_desc": " The letter head will appear across all print formats and PDFs \n Keep it web friendly as 1024px by 128px ",
"slide_fields": [
{
"align": "center",
@@ -32,6 +32,5 @@
],
"slide_order": 20,
"slide_title": "Company Letter Head",
- "slide_type": "Create",
- "submit_method": ""
+ "slide_type": "Create"
}
\ No newline at end of file
diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js
index 206094dd71..92194acdca 100644
--- a/frappe/public/js/frappe/desk.js
+++ b/frappe/public/js/frappe/desk.js
@@ -486,22 +486,14 @@ frappe.Application = Class.extend({
},
setup_onboarding_wizard: () => {
- var me = this;
- frappe.call('frappe.desk.doctype.setup_wizard_slide.setup_wizard_slide.get_onboarding_slides').then(res => {
+ frappe.call('frappe.desk.doctype.onboarding_slide.onboarding_slide.get_onboarding_slides').then(res => {
if (res.message) {
let slides = res.message;
if (slides.length) {
- frappe.require("assets/frappe/js/frappe/ui/onboarding_dialog.js", () => {
- me.progress_dialog = new frappe.setup.OnboardingDialog({
- slides: slides
- });
- me.progress_dialog.show();
- frappe.call({
- method: "frappe.desk.page.setup_wizard.setup_wizard.reset_is_first_startup",
- args: {},
- callback: () => {}
- });
+ this.progress_dialog = new frappe.setup.OnboardingDialog({
+ slides: slides
});
+ this.progress_dialog.show();
}
}
});
diff --git a/frappe/public/js/frappe/form/controls/autocomplete.js b/frappe/public/js/frappe/form/controls/autocomplete.js
index 026cdbec25..0b31776caf 100644
--- a/frappe/public/js/frappe/form/controls/autocomplete.js
+++ b/frappe/public/js/frappe/form/controls/autocomplete.js
@@ -10,13 +10,7 @@ frappe.ui.form.ControlAutocomplete = frappe.ui.form.ControlData.extend({
set_options() {
if (this.df.options) {
let options = this.df.options || [];
- if (typeof options === 'string') {
- options = options.split('\n');
- }
- if (typeof options[0] === 'string') {
- options = options.map(o => ({ label: o, value: o }));
- }
- this._data = options;
+ this._data = this.parse_options(options);
}
},
@@ -100,6 +94,9 @@ frappe.ui.form.ControlAutocomplete = frappe.ui.form.ControlData.extend({
},
validate(value) {
+ if (this.df.ignore_validation) {
+ return value || '';
+ }
let valid_values = this.awesomplete._list.map(d => d.value);
if (!valid_values.length) {
return value;
@@ -111,11 +108,22 @@ frappe.ui.form.ControlAutocomplete = frappe.ui.form.ControlData.extend({
}
},
+ parse_options(options) {
+ if (typeof options === 'string') {
+ options = options.split('\n');
+ }
+ if (typeof options[0] === 'string') {
+ options = options.map(o => ({ label: o, value: o }));
+ }
+ return options;
+ },
+
get_data() {
return this._data || [];
},
set_data(data) {
+ data = this.parse_options(data);
if (this.awesomplete) {
this.awesomplete.list = data;
}
diff --git a/frappe/public/js/frappe/form/controls/currency.js b/frappe/public/js/frappe/form/controls/currency.js
index 4d6b37aede..5a50ad9de6 100644
--- a/frappe/public/js/frappe/form/controls/currency.js
+++ b/frappe/public/js/frappe/form/controls/currency.js
@@ -4,11 +4,6 @@ frappe.ui.form.ControlCurrency = frappe.ui.form.ControlFloat.extend({
return isNaN(parseFloat(value)) ? "" : formatted_value;
},
- get_number_format: function() {
- var currency = frappe.meta.get_field_currency(this.df, this.get_doc());
- return get_number_format(currency);
- },
-
get_precision: function() {
// always round based on field precision or currency's precision
// this method is also called in this.parse()
diff --git a/frappe/public/js/frappe/form/controls/float.js b/frappe/public/js/frappe/form/controls/float.js
index 308d970f6e..027cfebc2a 100644
--- a/frappe/public/js/frappe/form/controls/float.js
+++ b/frappe/public/js/frappe/form/controls/float.js
@@ -1,10 +1,7 @@
frappe.ui.form.ControlFloat = frappe.ui.form.ControlInt.extend({
parse: function(value) {
value = this.eval_expression(value);
- return isNaN(parseFloat(value)) ? null : flt(value, this.get_precision(),
- // While parsing currency, get_number_format passes currency's number_format
- // In case of parsing float, it passes global number_format
- this.get_number_format());
+ return isNaN(parseFloat(value)) ? null : flt(value, this.get_precision());
},
format_for_input: function(value) {
@@ -17,8 +14,8 @@ frappe.ui.form.ControlFloat = frappe.ui.form.ControlInt.extend({
},
get_number_format: function() {
- // In case of 'Float' field currency's number_format shouldn't be used for formatting
- return get_number_format();
+ var currency = frappe.meta.get_field_currency(this.df, this.get_doc());
+ return get_number_format(currency);
},
get_precision: function() {
diff --git a/frappe/public/js/frappe/form/sidebar/review.js b/frappe/public/js/frappe/form/sidebar/review.js
index f4b8069a6e..e187ca4693 100644
--- a/frappe/public/js/frappe/form/sidebar/review.js
+++ b/frappe/public/js/frappe/form/sidebar/review.js
@@ -81,6 +81,7 @@ frappe.ui.form.Review = class Review {
label: __('To User'),
reqd: 1,
options: user_options,
+ ignore_validation: 1,
description: __('Only users involved in the document are listed')
}, {
fieldname: 'review_type',
diff --git a/frappe/public/js/frappe/ui/onboarding_dialog.js b/frappe/public/js/frappe/ui/onboarding_dialog.js
index 1798d0fe68..962cf988e8 100644
--- a/frappe/public/js/frappe/ui/onboarding_dialog.js
+++ b/frappe/public/js/frappe/ui/onboarding_dialog.js
@@ -22,11 +22,21 @@ frappe.setup.OnboardingSlide = class OnboardingSlide extends frappe.ui.Slide {
}
}
+ setup_form() {
+ super.setup_form();
+ const fields = this.get_atomic_fields();
+ if (fields.length == 1) {
+ this.$form_wrapper.addClass("text-center");
+ } else {
+ this.$form_wrapper.removeClass("text-center");
+ }
+ }
+
before_show() {
(this.id === 0) ?
- this.$next_btn.text(__('Start')) : this.$next_btn.text(__('Next'));
+ this.$next_btn.text(__('Let\'s Start')) : this.$next_btn.text(__('Next'));
//last slide
- if (this.id === this.parent[0].children.length-1) {
+ if (this.is_last_slide()) {
this.$complete_btn.removeClass('hide').addClass('action primary');
this.$next_btn.removeClass('action primary');
this.$action_button = this.$complete_btn;
@@ -35,33 +45,27 @@ frappe.setup.OnboardingSlide = class OnboardingSlide extends frappe.ui.Slide {
}
primary_action() {
- let me = this;
if (this.set_values()) {
this.$action_button.addClass('disabled');
- if (me.add_more) me.values.max_count = me.max_count;
- frappe.call({
- method: 'frappe.desk.doctype.setup_wizard_slide.setup_wizard_slide.create_onboarding_docs',
- args: {
- values: me.values,
- doctype: me.ref_doctype,
- submit_method: me.submit_method,
- app: me.app,
- slide_type: me.slide_type
- },
- callback: function() {
- if (me.id === me.parent[0].children.length-1) {
- $('.onboarding-dialog').modal('toggle');
- frappe.msgprint({
- message: __('You are all set up!'),
- indicator: 'green',
- title: __('Success')
- });
- }
- },
- onerror: function() {
- me.slides_footer.find('.primary').removeClass('disabled');
- },
- freeze: true
+ const primary_method = 'frappe.desk.doctype.onboarding_slide.onboarding_slide.create_onboarding_docs';
+ if (this.add_more) {
+ this.values.max_count = this.max_count;
+ }
+ frappe.call(primary_method, {
+ values: this.values,
+ doctype: this.ref_doctype,
+ app: this.app,
+ slide_type: this.slide_type
+ }).then(() => {
+ if (this.is_last_slide()) {
+ this.reset_is_first_startup();
+ $('.onboarding-dialog').modal('toggle');
+ frappe.msgprint({
+ message: __('You are all set up!'),
+ indicator: 'green',
+ title: __('Success')
+ });
+ }
});
}
}
@@ -74,10 +78,7 @@ frappe.setup.OnboardingSlide = class OnboardingSlide extends frappe.ui.Slide {
setup_help_links() {
this.help_links.map(link => {
let $link = $(
- ` ${link.label}
-
-
- `
+ ` ${link.label || __("Need Help?")}`
);
if (link.video_id) {
$link.on('click', () => {
@@ -89,11 +90,34 @@ frappe.setup.OnboardingSlide = class OnboardingSlide extends frappe.ui.Slide {
}
setup_action_button() {
- if (this.slide_type !== 'Information') {
+ if (this.slide_type === 'Create' || this.slide_type == 'Settings' || this.is_last_slide()) {
this.$action_button.addClass('primary');
} else {
this.$action_button.removeClass('primary');
}
+
+ this.$action_button.on('click', () => {
+ if (this.slide_type != 'Continue') {
+ this.mark_as_completed();
+ }
+ });
+ }
+
+ mark_as_completed() {
+ frappe.call({
+ method: 'frappe.desk.doctype.onboarding_slide.onboarding_slide.mark_slide_as_completed',
+ args: {slide_title: this.title},
+ callback: () => {},
+ freeze: true
+ });
+ }
+
+ reset_is_first_startup() {
+ frappe.call({
+ method: "frappe.desk.page.setup_wizard.setup_wizard.reset_is_first_startup",
+ args: {},
+ callback: () => {}
+ });
}
};
@@ -109,7 +133,6 @@ frappe.setup.OnboardingDialog = class OnboardingDialog {
this.dialog = new frappe.ui.Dialog({
static: true,
minimizable: false,
- title: __("Let's Onboard!")
});
this.$wrapper = $(this.dialog.$wrapper).addClass('onboarding-dialog');
this.slide_container = new frappe.ui.Slides({
@@ -126,11 +149,7 @@ frappe.setup.OnboardingDialog = class OnboardingDialog {
}
});
- this.$wrapper.find('.modal-title').prepend(
- `
-
- `
- );
+ this.$wrapper.find('.modal-header').remove();
}
show() {
diff --git a/frappe/public/js/frappe/ui/slides.js b/frappe/public/js/frappe/ui/slides.js
index d2e504337c..19a6cf9263 100644
--- a/frappe/public/js/frappe/ui/slides.js
+++ b/frappe/public/js/frappe/ui/slides.js
@@ -27,7 +27,7 @@ frappe.ui.Slide = class Slide {
@@ -36,6 +36,7 @@ frappe.ui.Slide = class Slide {
this.$content = this.$body.find(".content");
this.$form = this.$body.find(".form");
this.$primary_btn = this.slides_footer.find('.primary');
+ this.$form_wrapper = this.$body.find(".form-wrapper");
if(this.image_src) this.$content.append(
$(` `));
@@ -79,14 +80,14 @@ frappe.ui.Slide = class Slide {
if(this.add_more) {
this.count = 1;
fields = fields.map((field, i) => {
- if(field.fieldname) {
+ if (field.fieldname) {
field.fieldname += '_1';
}
- if(i === 1 && this.mandatory_entry) {
+ if (i === 1 && this.mandatory_entry) {
field.reqd = 1;
}
- if(!field.static) {
- if(field.label) field.label += ' 1';
+ if (!field.static) {
+ if (field.label) field.label;
}
return field;
});
@@ -106,10 +107,10 @@ frappe.ui.Slide = class Slide {
set_values() {
this.values = this.form.get_values();
- if(this.values===null) {
+ if (this.values===null) {
return false;
}
- if(this.validate && !this.validate()) {
+ if (this.validate && !this.validate()) {
return false;
}
return true;
@@ -121,14 +122,16 @@ frappe.ui.Slide = class Slide {
.on('click', () => {
this.count++;
var fields = JSON.parse(JSON.stringify(this.fields));
+
this.form.add_fields(fields.map(field => {
- if(field.fieldname) field.fieldname += '_' + this.count;
- if(!field.static) {
- if(field.label) field.label += ' ' + this.count;
+ if (field.fieldname) field.fieldname += '_' + this.count;
+ if (!field.static) {
+ if (field.label) field.label;
}
field.reqd = 0;
return field;
}));
+
if(this.count === this.max_count) {
this.$more.addClass('hide');
}
@@ -156,7 +159,7 @@ frappe.ui.Slide = class Slide {
var empty_fields = this.reqd_fields.filter((field) => {
return !field.get_value();
});
- if(empty_fields.length) {
+ if (empty_fields.length) {
this.slides_footer.find('.action').addClass('disabled');
} else {
this.slides_footer.find('.action').removeClass('disabled');
@@ -173,6 +176,13 @@ frappe.ui.Slide = class Slide {
});
}
+ is_last_slide() {
+ if (this.id === this.parent[0].children.length-1) {
+ return true;
+ }
+ return false;
+ }
+
before_show() { }
show_slide() {
diff --git a/frappe/public/js/frappe/utils/datetime.js b/frappe/public/js/frappe/utils/datetime.js
index 8481fec5c5..12badc5353 100644
--- a/frappe/public/js/frappe/utils/datetime.js
+++ b/frappe/public/js/frappe/utils/datetime.js
@@ -90,6 +90,14 @@ $.extend(frappe.datetime, {
return moment().endOf("month").format();
},
+ quarter_start: function() {
+ return moment().startOf("quarter").format();
+ },
+
+ quarter_end: function() {
+ return moment().endOf("quarter").format();
+ },
+
year_start: function(){
return moment().startOf("year").format();
},
diff --git a/frappe/public/js/frappe/utils/number_format.js b/frappe/public/js/frappe/utils/number_format.js
index b8a18610bd..57022b22c5 100644
--- a/frappe/public/js/frappe/utils/number_format.js
+++ b/frappe/public/js/frappe/utils/number_format.js
@@ -144,10 +144,7 @@ function get_currency_symbol(currency) {
}
function get_number_format(currency) {
- let format = null;
- if (currency) format = frappe.model.get_value(":Currency", currency, "number_format");
-
- return format || (frappe.boot && frappe.boot.sysdefaults && frappe.boot.sysdefaults.number_format) || "#,###.##";
+ return (frappe.boot && frappe.boot.sysdefaults && frappe.boot.sysdefaults.number_format) || "#,###.##";
}
function get_number_format_info(format) {
diff --git a/frappe/public/js/frappe/views/calendar/calendar.js b/frappe/public/js/frappe/views/calendar/calendar.js
index b84d37cff4..71ac175ba0 100644
--- a/frappe/public/js/frappe/views/calendar/calendar.js
+++ b/frappe/public/js/frappe/views/calendar/calendar.js
@@ -308,6 +308,7 @@ frappe.views.Calendar = Class.extend({
doctype: this.doctype,
start: this.get_system_datetime(start),
end: this.get_system_datetime(end),
+ fields: this.fields,
filters: this.list_view.filter_area.get(),
field_map: this.field_map
};
@@ -356,9 +357,13 @@ frappe.views.Calendar = Class.extend({
prepare_colors: function(d) {
let color, color_name;
if(this.get_css_class) {
- color_name = this.color_map[this.get_css_class(d)];
- color_name = frappe.ui.color.validate_hex(color_name) ?
- color_name : 'blue';
+ color_name = this.color_map[this.get_css_class(d)] || 'blue';
+
+ if (color_name.startsWith("#")) {
+ color_name = frappe.ui.color.validate_hex(color_name) ?
+ color_name : 'blue';
+ }
+
d.backgroundColor = frappe.ui.color.get(color_name, 'extra-light');
d.textColor = frappe.ui.color.get(color_name, 'dark');
} else {
diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js
index a7505ff8f3..0771306298 100644
--- a/frappe/public/js/frappe/views/reports/query_report.js
+++ b/frappe/public/js/frappe/views/reports/query_report.js
@@ -507,6 +507,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
})
};
}
+ options.axisOptions = {
+ shortenYAxisNumbers: 1
+ };
return options;
}
@@ -561,7 +564,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
get_possible_chart_options() {
const columns = this.columns;
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 first_row = Array.isArray(rows[0]) ? rows[0] : columns.map(col => rows[0][col.fieldname]);
const me = this
const indices = first_row.reduce((accumulator, current_value, current_index) => {
diff --git a/frappe/public/less/common.less b/frappe/public/less/common.less
index 0f49c43de1..d6dc56cf6c 100644
--- a/frappe/public/less/common.less
+++ b/frappe/public/less/common.less
@@ -28,6 +28,10 @@ p {
margin: 10px 0;
}
+details > summary {
+ cursor: pointer;
+}
+
.text-color {
color: @text-color !important;
}
diff --git a/frappe/public/less/desk.less b/frappe/public/less/desk.less
index d8f5a141f4..0a8062efa2 100644
--- a/frappe/public/less/desk.less
+++ b/frappe/public/less/desk.less
@@ -897,6 +897,7 @@ input[type="checkbox"] {
&:focus {
outline: none;
}
+
.fa-circle {
font-size: 10px;
margin: 0px 2px;
@@ -928,6 +929,7 @@ input[type="checkbox"] {
}
.lead {
margin-top: 20px;
+ font-weight: 500;
}
.success-state {
margin-bottom: 20px;
@@ -962,6 +964,12 @@ input[type="checkbox"] {
// Onboarding Dialog
.onboarding-dialog {
+ .slide-body {
+ width: 65%;
+ margin-right: auto;
+ margin-left: auto;
+ }
+
.modal-dialog {
width: 50%;
height: 80%;
@@ -969,7 +977,7 @@ input[type="checkbox"] {
}
.onboarding-icon {
- color: #3246F5;
+ color: @text-muted;
margin-right: 5px;
}
@@ -980,8 +988,8 @@ input[type="checkbox"] {
}
img {
- max-width: 128px;
- max-height: 128px;
+ max-height: 175px;
+ width: auto;
}
.slides-progress {
diff --git a/frappe/public/less/form.less b/frappe/public/less/form.less
index 9c40131067..9a0eec46ea 100644
--- a/frappe/public/less/form.less
+++ b/frappe/public/less/form.less
@@ -239,6 +239,18 @@
.progress-area {
padding-top: 15px;
padding-bottom: 15px;
+
+ .progress-chart {
+ padding-top: 15px;
+ }
+
+ .progress {
+ margin-bottom: 5px;
+ }
+
+ .progress-message {
+ margin-top: 0px;
+ }
}
.form-links {
diff --git a/frappe/public/less/list.less b/frappe/public/less/list.less
index bd6ea7e9d0..ea292daca2 100644
--- a/frappe/public/less/list.less
+++ b/frappe/public/less/list.less
@@ -10,7 +10,8 @@
// To compensate for percieved centering
.null-state {
- height: 12em !important;
+ height: 15rem !important;
+ max-height: 150px;
width: auto;
}
@@ -616,4 +617,4 @@ input.list-check-all, input.list-row-checkbox {
.file-title {
margin-top: 5px;
-}
\ No newline at end of file
+}
diff --git a/frappe/tests/test_global_search.py b/frappe/tests/test_global_search.py
index 3fcf1fe9ed..01067c85dd 100644
--- a/frappe/tests/test_global_search.py
+++ b/frappe/tests/test_global_search.py
@@ -80,7 +80,7 @@ class TestGlobalSearch(unittest.TestCase):
make_property_setter(doctype, "repeat_on", "in_global_search", 1, "Int")
global_search.rebuild_for_doctype(doctype)
results = global_search.search('Monthly')
- self.assertEqual(len(results), 2)
+ self.assertEqual(len(results), 3)
def test_delete_doc(self):
self.insert_test_events()
diff --git a/frappe/tests/test_listview.py b/frappe/tests/test_listview.py
index 5c0657e9ee..3a73301608 100644
--- a/frappe/tests/test_listview.py
+++ b/frappe/tests/test_listview.py
@@ -6,7 +6,7 @@ import unittest
import frappe
import json
-from frappe.desk.listview import get_list_settings, set_list_settings
+from frappe.desk.listview import get_list_settings, set_list_settings, get_group_by_count
class TestListView(unittest.TestCase):
def setUp(self):
@@ -51,3 +51,13 @@ class TestListView(unittest.TestCase):
self.assertEqual(settings.disable_count, 0)
self.assertEqual(settings.disable_sidebar_stats, 0)
+ def test_list_view_child_table_filter_with_created_by_filter(self):
+ if frappe.db.exists("Note", "Test created by filter with child table filter"):
+ frappe.delete_doc("Note", "Test created by filter with child table filter")
+
+ doc = frappe.get_doc({"doctype": "Note", "title": "Test created by filter with child table filter", "public": 1})
+ doc.append("seen_by", {"user": "Administrator"})
+ doc.insert()
+
+ data = {d.name: d.count for d in get_group_by_count('Note', '[["Note Seen By","user","=","Administrator"]]', 'owner')}
+ self.assertEqual(data['Administrator'], 1)
\ No newline at end of file
diff --git a/frappe/tests/ui_test_helpers.py b/frappe/tests/ui_test_helpers.py
index 29cfcf5bac..83b5f5aa60 100644
--- a/frappe/tests/ui_test_helpers.py
+++ b/frappe/tests/ui_test_helpers.py
@@ -91,3 +91,20 @@ def create_doctype(name, fields):
}],
"name": name
}).insert()
+
+def create_contact_records():
+ if frappe.db.get_all('Contact', {'first_name': 'Test Form Contact 1'}):
+ return
+
+ insert_contact('Test Form Contact 1', '12345')
+ insert_contact('Test Form Contact 2', '54321')
+ insert_contact('Test Form Contact 3', '12345')
+
+
+def insert_contact(first_name, phone_number):
+ doc = frappe.get_doc({
+ 'doctype': 'Contact',
+ 'first_name': first_name
+ })
+ doc.append('phone_nos', {'phone': phone_number})
+ doc.insert()
diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py
index ff23a09c1a..7012b737c0 100644
--- a/frappe/utils/global_search.py
+++ b/frappe/utils/global_search.py
@@ -418,25 +418,31 @@ def search(text, start=0, limit=20, doctype=""):
from frappe.desk.doctype.global_search_settings.global_search_settings import get_doctypes_for_global_search
results = []
- texts = [t.strip() for t in text.split('&') if t]
- priorities = get_doctypes_for_global_search()
- allowed_doctypes = ",".join(["'{0}'".format(dt) for dt in priorities])
- for text in texts:
- mariadb_conditions = ''
- postgres_conditions = ''
- offset = ''
+ sorted_results = []
- if doctype:
- mariadb_conditions = postgres_conditions = '`doctype` = {} AND '.format(frappe.db.escape(doctype))
+ allowed_doctypes = get_doctypes_for_global_search()
+
+ for text in set(text.split('&')):
+ text = text.strip()
+ if not text:
+ continue
+
+ conditions = '1=1'
+ offset = ''
mariadb_text = frappe.db.escape('+' + text + '*')
mariadb_fields = '`doctype`, `name`, `content`, MATCH (`content`) AGAINST ({} IN BOOLEAN MODE) AS rank'.format(mariadb_text)
postgres_fields = '`doctype`, `name`, `content`, TO_TSVECTOR("content") @@ PLAINTO_TSQUERY({}) AS rank'.format(frappe.db.escape(text))
- if allowed_doctypes:
- mariadb_conditions += '`doctype` IN ({})'.format(allowed_doctypes)
- postgres_conditions += '`doctype` IN ({})'.format(allowed_doctypes)
+ values = {}
+
+ if doctype:
+ conditions = '`doctype` = %(doctype)s'
+ values['doctype'] = doctype
+ elif allowed_doctypes:
+ conditions = '`doctype` IN %(allowed_doctypes)s'
+ values['allowed_doctypes'] = tuple(allowed_doctypes)
if int(start) > 0:
offset = 'OFFSET {}'.format(start)
@@ -451,41 +457,27 @@ def search(text, start=0, limit=20, doctype=""):
"""
result = frappe.db.multisql({
- 'mariadb': common_query.format(fields=mariadb_fields, conditions=mariadb_conditions, limit=limit, offset=offset),
- 'postgres': common_query.format(fields=postgres_fields, conditions=postgres_conditions, limit=limit, offset=offset)
- }, as_dict=True)
+ 'mariadb': common_query.format(fields=mariadb_fields, conditions=conditions, limit=limit, offset=offset),
+ 'postgres': common_query.format(fields=postgres_fields, conditions=conditions, limit=limit, offset=offset)
+ }, values=values, as_dict=True)
- tmp_result=[]
- for i in result:
- if i.rank > 0.0:
- if i in results or not results:
- tmp_result.extend([i])
- results.extend(tmp_result)
-
- for r in results:
- try:
- if frappe.get_meta(r.doctype).image_field:
- r.image = frappe.db.get_value(r.doctype, r.name, frappe.get_meta(r.doctype).image_field)
- except Exception:
- frappe.clear_messages()
-
- sorted_results = []
-
- for priority in priorities:
- tmp_result = []
- if not results:
- break
+ results.extend(result)
+ # sort results based on allowed_doctype's priority
+ for doctype in allowed_doctypes:
for index, r in enumerate(results):
- if r.doctype == priority:
- tmp_result.extend([r])
- results.pop(index)
+ if r.doctype == doctype and r.rank > 0.0:
+ try:
+ meta = frappe.get_meta(r.doctype)
+ if meta.image_field:
+ r.image = frappe.db.get_value(r.doctype, r.name, meta.image_field)
+ except Exception:
+ frappe.clear_messages()
- sorted_results.extend(tmp_result)
+ sorted_results.extend([r])
return sorted_results
-
@frappe.whitelist(allow_guest=True)
def web_search(text, scope=None, start=0, limit=20):
"""
diff --git a/frappe/utils/oauth.py b/frappe/utils/oauth.py
index 79bcee7a74..969623c369 100644
--- a/frappe/utils/oauth.py
+++ b/frappe/utils/oauth.py
@@ -283,6 +283,12 @@ def update_oauth_user(user, data, provider):
if save:
user.flags.ignore_permissions = True
user.flags.no_welcome_mail = True
+
+ # set default signup role as per Portal Settings
+ default_role = frappe.db.get_single_value("Portal Settings", "default_role")
+ if default_role:
+ user.add_roles(default_role)
+
user.save()
def get_first_name(data):
diff --git a/frappe/utils/response.py b/frappe/utils/response.py
index 8169986e44..1dfbbe5516 100644
--- a/frappe/utils/response.py
+++ b/frappe/utils/response.py
@@ -210,7 +210,7 @@ def send_private_file(path):
blacklist = ['.svg', '.html', '.htm', '.xml']
if extension.lower() in blacklist:
- response.headers.add(b'Content-Disposition', b'attachment', filename=filename.encode("utf-8"))
+ response.headers.add('Content-Disposition', 'attachment', filename=filename.encode("utf-8"))
response.mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py
index 7c0a642cb3..708bec887f 100755
--- a/frappe/utils/scheduler.py
+++ b/frappe/utils/scheduler.py
@@ -14,6 +14,7 @@ import frappe, os, time
import schedule
from frappe.utils import now_datetime, get_datetime
from frappe.utils import get_sites
+from frappe.installer import update_site_config
from frappe.core.doctype.user.user import STANDARD_USERS
from frappe.utils.background_jobs import get_jobs
|