Merge barnch develop into mandatory-depends-on
This commit is contained in:
commit
33ec59ad53
59 changed files with 581 additions and 367 deletions
|
|
@ -1,9 +1,15 @@
|
|||
context('Form', () => {
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
});
|
||||
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
|
||||
});
|
||||
});
|
||||
it('create a new form', () => {
|
||||
cy.visit('/desk#Form/ToDo/New ToDo 1');
|
||||
cy.fill_field('description', 'this is a test todo', 'Text Editor').blur();
|
||||
|
|
@ -13,4 +19,21 @@ context('Form', () => {
|
|||
cy.location('hash').should('eq', '#List/ToDo/List');
|
||||
cy.get('.list-row').should('contain', 'this is a test todo');
|
||||
});
|
||||
it('navigates between documents with child table list filters applied', () => {
|
||||
cy.visit('/desk#List/Contact');
|
||||
cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
|
||||
cy.get('.fieldname-select-area').should('exist');
|
||||
cy.get('.fieldname-select-area input').type('Number{enter}', { force: true });
|
||||
cy.get('.filter-field .input-with-feedback.form-control').type('123', { force: true });
|
||||
cy.get('.filter-box .btn:contains("Apply")').click({ force: true });
|
||||
cy.visit('/desk#Form/Contact/Test Form Contact 3');
|
||||
cy.get('.prev-doc').click();
|
||||
cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible');
|
||||
cy.get('.modal-backdrop').click();
|
||||
cy.get('.next-doc').click();
|
||||
cy.contains('Test Form Contact 2').should('not.exist');
|
||||
cy.get('.page-title .title-text').should('contain', 'Test Form Contact 1');
|
||||
cy.visit('/desk#List/Contact');
|
||||
cy.get('.clear-filters.btn').click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ from six import text_type
|
|||
@click.argument('site')
|
||||
@click.option('--db-name', help='Database name')
|
||||
@click.option('--db-type', default='mariadb', type=click.Choice(['mariadb', 'postgres']), help='Optional "postgres" or "mariadb". Default is "mariadb"')
|
||||
@click.option('--db-host', help='Database Host')
|
||||
@click.option('--db-port', type=int, help='Database Port')
|
||||
@click.option('--mariadb-root-username', default='root', help='Root username for MariaDB')
|
||||
@click.option('--mariadb-root-password', help='Root password for MariaDB')
|
||||
@click.option('--admin-password', help='Administrator password for new site', default=None)
|
||||
|
|
@ -21,22 +23,22 @@ from six import text_type
|
|||
@click.option('--source_sql', help='Initiate database with a SQL file')
|
||||
@click.option('--install-app', multiple=True, help='Install app after installation')
|
||||
def new_site(site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None,
|
||||
verbose=False, install_apps=None, source_sql=None, force=None, install_app=None,
|
||||
db_name=None, db_type=None):
|
||||
verbose=False, install_apps=None, source_sql=None, force=None, install_app=None,
|
||||
db_name=None, db_type=None, db_host=None, db_port=None):
|
||||
"Create a new site"
|
||||
frappe.init(site=site, new_site=True)
|
||||
|
||||
_new_site(db_name, site, mariadb_root_username=mariadb_root_username,
|
||||
mariadb_root_password=mariadb_root_password, admin_password=admin_password,
|
||||
verbose=verbose, install_apps=install_app, source_sql=source_sql, force=force,
|
||||
db_type=db_type)
|
||||
mariadb_root_password=mariadb_root_password, admin_password=admin_password,
|
||||
verbose=verbose, install_apps=install_app, source_sql=source_sql, force=force,
|
||||
db_type=db_type, db_host=db_host, db_port=db_port)
|
||||
|
||||
if len(frappe.utils.get_sites()) == 1:
|
||||
use(site)
|
||||
|
||||
def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=None,
|
||||
admin_password=None, verbose=False, install_apps=None, source_sql=None, force=False,
|
||||
reinstall=False, db_type=None):
|
||||
admin_password=None, verbose=False, install_apps=None, source_sql=None, force=False,
|
||||
reinstall=False, db_type=None, db_host=None, db_port=None):
|
||||
"""Install a new Frappe site"""
|
||||
|
||||
if not force and os.path.exists(site):
|
||||
|
|
@ -65,8 +67,8 @@ def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=N
|
|||
installing = touch_file(get_site_path('locks', 'installing.lock'))
|
||||
|
||||
install_db(root_login=mariadb_root_username, root_password=mariadb_root_password,
|
||||
db_name=db_name, admin_password=admin_password, verbose=verbose,
|
||||
source_sql=source_sql, force=force, reinstall=reinstall, db_type=db_type)
|
||||
db_name=db_name, admin_password=admin_password, verbose=verbose,
|
||||
source_sql=source_sql, force=force, reinstall=reinstall, db_type=db_type, db_host=db_host, db_port=db_port)
|
||||
|
||||
apps_to_install = ['frappe'] + (frappe.conf.get("install_apps") or []) + (list(install_apps) or [])
|
||||
for app in apps_to_install:
|
||||
|
|
|
|||
|
|
@ -60,10 +60,6 @@ class Address(Document):
|
|||
if not [row for row in self.links if row.link_doctype == "Company"]:
|
||||
frappe.throw(_("Company is mandatory, as it is your company address"))
|
||||
|
||||
# removing other links
|
||||
to_remove = [row for row in self.links if row.link_doctype != "Company"]
|
||||
[ self.remove(row) for row in to_remove ]
|
||||
|
||||
def get_display(self):
|
||||
return get_address_display(self.as_dict())
|
||||
|
||||
|
|
|
|||
|
|
@ -398,7 +398,7 @@ def get_bcc(doc, recipients=None, fetched_from_email_account=False):
|
|||
return bcc
|
||||
|
||||
def add_attachments(name, attachments):
|
||||
'''Add attachments to the given Communiction'''
|
||||
'''Add attachments to the given Communication'''
|
||||
# loop through attachments
|
||||
for a in attachments:
|
||||
if isinstance(a, string_types):
|
||||
|
|
@ -411,7 +411,9 @@ def add_attachments(name, attachments):
|
|||
"file_url": attach.file_url,
|
||||
"attached_to_doctype": "Communication",
|
||||
"attached_to_name": name,
|
||||
"folder": "Home/Attachments"})
|
||||
"folder": "Home/Attachments",
|
||||
"is_private": attach.is_private
|
||||
})
|
||||
_file.save(ignore_permissions=True)
|
||||
|
||||
def filter_email_list(doc, email_list, exclude, is_cc=False, is_bcc=False):
|
||||
|
|
|
|||
|
|
@ -89,8 +89,9 @@ class File(Document):
|
|||
|
||||
def validate(self):
|
||||
if self.is_new():
|
||||
self.set_is_private()
|
||||
self.set_file_name()
|
||||
self.validate_duplicate_entry()
|
||||
self.validate_file_name()
|
||||
self.validate_folder()
|
||||
|
||||
if not self.file_url and not self.flags.ignore_file_validate:
|
||||
|
|
@ -133,6 +134,9 @@ class File(Document):
|
|||
frappe.db.set_value(self.attached_to_doctype, self.attached_to_name,
|
||||
self.attached_to_field, self.file_url)
|
||||
|
||||
if self.file_url and (self.is_private != self.file_url.startswith('/private')):
|
||||
frappe.throw(_('Invalid file URL. Please contact System Administrator.'))
|
||||
|
||||
def set_folder_name(self):
|
||||
"""Make parent folders if not exists based on reference doctype and name"""
|
||||
if self.attached_to_doctype and not self.folder:
|
||||
|
|
@ -157,9 +161,11 @@ class File(Document):
|
|||
|
||||
def validate_duplicate_entry(self):
|
||||
if not self.flags.ignore_duplicate_entry_error and not self.is_folder:
|
||||
# check duplicate name
|
||||
if not self.content_hash:
|
||||
self.generate_content_hash()
|
||||
|
||||
# check duplicate assignement
|
||||
# check duplicate name
|
||||
# check duplicate assignment
|
||||
filters = {
|
||||
'content_hash': self.content_hash,
|
||||
'is_private': self.is_private,
|
||||
|
|
@ -184,21 +190,20 @@ class File(Document):
|
|||
else:
|
||||
self.file_url = duplicate_file.file_url
|
||||
|
||||
def validate_file_name(self):
|
||||
def set_file_name(self):
|
||||
if not self.file_name and self.file_url:
|
||||
self.file_name = self.file_url.split('/')[-1]
|
||||
|
||||
def generate_content_hash(self):
|
||||
if self.content_hash or not self.file_url:
|
||||
if self.content_hash or not self.file_url or self.file_url.startswith('http'):
|
||||
return
|
||||
|
||||
if self.file_url.startswith("/files/"):
|
||||
try:
|
||||
with open(get_files_path(self.file_name.lstrip("/")), "rb") as f:
|
||||
self.content_hash = get_content_hash(f.read())
|
||||
except IOError:
|
||||
frappe.msgprint(_("File {0} does not exist").format(self.file_url))
|
||||
raise
|
||||
try:
|
||||
with open(get_files_path(self.file_name.lstrip("/"), is_private=self.is_private), "rb") as f:
|
||||
self.content_hash = get_content_hash(f.read())
|
||||
except IOError:
|
||||
frappe.msgprint(_("File {0} does not exist").format(self.file_url))
|
||||
raise
|
||||
|
||||
def on_trash(self):
|
||||
if self.is_home_folder or self.is_attachments_folder:
|
||||
|
|
@ -563,6 +568,9 @@ class File(Document):
|
|||
except frappe.DoesNotExistError:
|
||||
frappe.clear_messages()
|
||||
|
||||
def set_is_private(self):
|
||||
if self.file_url:
|
||||
self.is_private = cint(self.file_url.startswith('/private'))
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"])
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Frequency",
|
||||
"options": "All\nHourly\nDaily\nDaily Long\nWeekly\nWeekly Long\nMonthly\nMonthly Long\nCron\nYearly\nAnnual",
|
||||
"options": "All\nHourly\nHourly Long\nDaily\nDaily Long\nWeekly\nWeekly Long\nMonthly\nMonthly Long\nCron\nYearly\nAnnual",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@
|
|||
"link_fieldname": "scheduled_job_type"
|
||||
}
|
||||
],
|
||||
"modified": "2019-09-27 12:19:23.259989",
|
||||
"modified": "2019-12-09 11:10:21.259929",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Scheduled Job Type",
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ class ScheduledJobType(Document):
|
|||
def enqueue(self):
|
||||
# enqueue event if last execution is done
|
||||
if self.is_event_due():
|
||||
self.update_last_execution()
|
||||
if frappe.flags.enqueued_jobs:
|
||||
frappe.flags.enqueued_jobs.append(self.method)
|
||||
|
||||
|
|
@ -39,11 +38,8 @@ class ScheduledJobType(Document):
|
|||
|
||||
def is_event_due(self, current_time = None):
|
||||
'''Return true if event is due based on time lapsed since last execution'''
|
||||
# save last execution in expected execution time as per cron
|
||||
self.last_execution = self.get_next_execution()
|
||||
|
||||
# if the next scheduled event is before NOW, then its due!
|
||||
return self.last_execution <= (current_time or now_datetime())
|
||||
return self.get_next_execution() <= (current_time or now_datetime())
|
||||
|
||||
def is_job_in_queue(self):
|
||||
queued_jobs = get_jobs(site=frappe.local.site, key='job_type')[frappe.local.site]
|
||||
|
|
@ -68,7 +64,7 @@ class ScheduledJobType(Document):
|
|||
self.cron_format = CRON_MAP[self.frequency]
|
||||
|
||||
return croniter(self.cron_format,
|
||||
get_datetime(self.last_execution)).get_next(datetime)
|
||||
get_datetime(self.last_execution or datetime(2000, 1, 1))).get_next(datetime)
|
||||
|
||||
def execute(self):
|
||||
self.scheduler_log = None
|
||||
|
|
@ -94,15 +90,16 @@ class ScheduledJobType(Document):
|
|||
self.scheduler_log.db_set('status', status)
|
||||
if status == 'Failed':
|
||||
self.scheduler_log.db_set('details', frappe.get_traceback())
|
||||
frappe.db.commit()
|
||||
|
||||
def update_last_execution(self):
|
||||
self.db_set('last_execution', self.last_execution, update_modified=False)
|
||||
if status == 'Start':
|
||||
self.db_set('last_execution', now_datetime(), update_modified=False)
|
||||
frappe.db.commit()
|
||||
|
||||
def get_queue_name(self):
|
||||
return 'long' if ('Long' in self.frequency) else 'default'
|
||||
|
||||
def on_trash(self):
|
||||
frappe.db.sql('delete from `tabScheduled Job Log` where scheduled_job_type=%s', self.name)
|
||||
|
||||
@frappe.whitelist()
|
||||
def execute_event(doc):
|
||||
frappe.only_for('System Manager')
|
||||
|
|
|
|||
|
|
@ -97,7 +97,9 @@ class User(Document):
|
|||
self.share_with_self()
|
||||
clear_notifications(user=self.name)
|
||||
frappe.clear_cache(user=self.name)
|
||||
self.send_password_notification(self.__new_password)
|
||||
if self.__new_password:
|
||||
self.send_password_notification(self.__new_password)
|
||||
self.reset_password_key = ''
|
||||
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)
|
||||
|
|
@ -1071,4 +1073,4 @@ def generate_keys(user):
|
|||
user_details.save()
|
||||
|
||||
return {"api_secret": api_secret}
|
||||
frappe.throw(frappe._("Not Permitted"), frappe.PermissionError)
|
||||
frappe.throw(frappe._("Not Permitted"), frappe.PermissionError)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<tbody>
|
||||
{% for j in jobs %}
|
||||
<tr>
|
||||
<td><span class="indicator {{ j.color }}" title="{{ j.status }}">{{ j.queue.split(".").slice(-1)[0] }}</span></td>
|
||||
<td><span class="indicator {{ j.color }}" title="{{ j.get_status() }}">{{ j.queue.split(".").slice(-1)[0] }}</span></td>
|
||||
<td style="overflow: auto;">
|
||||
<div>
|
||||
{{ frappe.utils.encode_tags(j.job_name) }}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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": [
|
||||
{
|
||||
134
frappe/desk/doctype/onboarding_slide/onboarding_slide.py
Normal file
134
frappe/desk/doctype/onboarding_slide/onboarding_slide.py
Normal file
|
|
@ -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 <b>{0}</b> 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]
|
||||
|
|
@ -6,5 +6,5 @@ from __future__ import unicode_literals
|
|||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestSetupWizardSlide(unittest.TestCase):
|
||||
class TestOnboardingSlide(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -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",
|
||||
|
|
@ -6,5 +6,5 @@ from __future__ import unicode_literals
|
|||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class SetupWizardHelpLink(Document):
|
||||
class OnboardingSlideField(Document):
|
||||
pass
|
||||
|
|
@ -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,
|
||||
|
|
@ -6,5 +6,5 @@ from __future__ import unicode_literals
|
|||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class SetupWizardSlideField(Document):
|
||||
class OnboardingSlideHelpLink(Document):
|
||||
pass
|
||||
|
|
@ -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()
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = $(`<div class="from-date-field"></div>`).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];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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' });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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": "<p>The letter head will appear across all print formats and PDFs</p>\n<p class=\"text-muted\">Keep it web friendly as 1024px by 128px</p>",
|
||||
"slide_fields": [
|
||||
{
|
||||
"align": "center",
|
||||
|
|
@ -32,6 +32,5 @@
|
|||
],
|
||||
"slide_order": 20,
|
||||
"slide_title": "Company Letter Head",
|
||||
"slide_type": "Create",
|
||||
"submit_method": ""
|
||||
"slide_type": "Create"
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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 = $(
|
||||
`<a target="_blank" class="small text-muted">${link.label}</a>
|
||||
<span class="small">
|
||||
<i class="fa fa-question-circle-o" aria-hidden="true"></i>
|
||||
</span>`
|
||||
`<a target="_blank" class="small text-muted">${link.label || __("Need Help?")}</a>`
|
||||
);
|
||||
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(
|
||||
`<span class="onboarding-icon">
|
||||
<i class="fa fa-rocket" aria-hidden="true"></i>
|
||||
</span>`
|
||||
);
|
||||
this.$wrapper.find('.modal-header').remove();
|
||||
}
|
||||
|
||||
show() {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ frappe.ui.Slide = class Slide {
|
|||
<div class="form"></div>
|
||||
<div class="add-more text-center" style="margin-top: 5px;">
|
||||
<a class="form-more-btn hide btn btn-default btn-xs">
|
||||
<span><i class="fa fa-plus small" aria-hidden="true"></i></span>
|
||||
<span>Add More</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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(
|
||||
$(`<img src="${this.image_src}" style="margin: 20px;">`));
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ p {
|
|||
margin: 10px 0;
|
||||
}
|
||||
|
||||
details > summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-color {
|
||||
color: @text-color !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue