@@ -16,3 +16,6 @@ fe20515c23a3ac41f1092bf0eaf0a0a452ec2e85 | |||
# Refactor "not a in b" -> "a not in b" | |||
745297a49d516e5e3c4bb3e1b0c4235e7d31165d | |||
# Clean up whitespace | |||
b2fc959307c7c79f5584625569d5aed04133ba13 |
@@ -1,15 +1,24 @@ | |||
name: Semgrep | |||
name: Linters | |||
on: | |||
pull_request: { } | |||
jobs: | |||
semgrep: | |||
linters: | |||
name: Frappe Linter | |||
runs-on: ubuntu-latest | |||
steps: | |||
- uses: actions/checkout@v2 | |||
- name: Set up Python 3.8 | |||
uses: actions/setup-python@v2 | |||
with: | |||
python-version: 3.8 | |||
- name: Install and Run Pre-commit | |||
uses: pre-commit/action@v2.0.3 | |||
- name: Download Semgrep rules | |||
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules | |||
@@ -0,0 +1,23 @@ | |||
exclude: 'node_modules|.git' | |||
default_stages: [commit] | |||
fail_fast: false | |||
repos: | |||
- repo: https://github.com/pre-commit/pre-commit-hooks | |||
rev: v4.0.1 | |||
hooks: | |||
- id: trailing-whitespace | |||
files: "frappe.*" | |||
exclude: ".*json$|.*txt$|.*csv|.*md|.*svg" | |||
- id: check-yaml | |||
- id: no-commit-to-branch | |||
args: ['--branch', 'develop'] | |||
- id: check-merge-conflict | |||
- id: check-ast | |||
ci: | |||
autoupdate_schedule: weekly | |||
skip: [] | |||
submodules: false |
@@ -3,7 +3,6 @@ codecov: | |||
coverage: | |||
status: | |||
patch: off | |||
project: | |||
default: false | |||
server: | |||
@@ -0,0 +1,57 @@ | |||
context('Control Autocomplete', () => { | |||
before(() => { | |||
cy.login(); | |||
cy.visit('/app/website'); | |||
}); | |||
function get_dialog_with_autocomplete(options) { | |||
cy.visit('/app/website'); | |||
return cy.dialog({ | |||
title: 'Autocomplete', | |||
fields: [ | |||
{ | |||
'label': 'Select an option', | |||
'fieldname': 'autocomplete', | |||
'fieldtype': 'Autocomplete', | |||
'options': options || ['Option 1', 'Option 2', 'Option 3'], | |||
} | |||
] | |||
}); | |||
} | |||
it('should set the valid value', () => { | |||
get_dialog_with_autocomplete().as('dialog'); | |||
cy.get('.frappe-control[data-fieldname=autocomplete] input').focus().as('input'); | |||
cy.wait(1000); | |||
cy.get('@input').type('2', { delay: 300 }); | |||
cy.get('.frappe-control[data-fieldname=autocomplete]').findByRole('listbox').should('be.visible'); | |||
cy.get('.frappe-control[data-fieldname=autocomplete] input').type('{enter}', { delay: 300 }); | |||
cy.get('.frappe-control[data-fieldname=autocomplete] input').blur(); | |||
cy.get('@dialog').then(dialog => { | |||
let value = dialog.get_value('autocomplete'); | |||
expect(value).to.eq('Option 2'); | |||
dialog.clear(); | |||
}); | |||
}); | |||
it('should set the valid value with different label', () => { | |||
const options_with_label = [ | |||
{ label: "Option 1", value: "option_1" }, | |||
{ label: "Option 2", value: "option_2" } | |||
]; | |||
get_dialog_with_autocomplete(options_with_label).as('dialog'); | |||
cy.get('.frappe-control[data-fieldname=autocomplete] input').focus().as('input'); | |||
cy.get('.frappe-control[data-fieldname=autocomplete]').findByRole('listbox').should('be.visible'); | |||
cy.get('@input').type('2', { delay: 300 }); | |||
cy.get('.frappe-control[data-fieldname=autocomplete] input').type('{enter}', { delay: 300 }); | |||
cy.get('.frappe-control[data-fieldname=autocomplete] input').blur(); | |||
cy.get('@dialog').then(dialog => { | |||
let value = dialog.get_value('autocomplete'); | |||
expect(value).to.eq('option_2'); | |||
dialog.clear(); | |||
}); | |||
}); | |||
}); |
@@ -19,36 +19,38 @@ from frappe.exceptions import SiteNotSpecifiedError | |||
@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('--db-root-username', '--mariadb-root-username', help='Root username for MariaDB or PostgreSQL, Default is "root"') | |||
@click.option('--db-root-password', '--mariadb-root-password', help='Root password for MariaDB or PostgreSQL') | |||
@click.option('--no-mariadb-socket', is_flag=True, default=False, help='Set MariaDB host to % and use TCP/IP Socket instead of using the UNIX Socket') | |||
@click.option('--admin-password', help='Administrator password for new site', default=None) | |||
@click.option('--verbose', is_flag=True, default=False, help='Verbose') | |||
@click.option('--force', help='Force restore if site/database already exists', is_flag=True, default=False) | |||
@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, no_mariadb_socket=False, | |||
install_app=None, db_name=None, db_password=None, db_type=None, db_host=None, db_port=None): | |||
@click.option('--set-default', is_flag=True, default=False, help='Set the new site as default site') | |||
def new_site(site, db_root_username=None, db_root_password=None, admin_password=None, | |||
verbose=False, install_apps=None, source_sql=None, force=None, no_mariadb_socket=False, | |||
install_app=None, db_name=None, db_password=None, db_type=None, db_host=None, db_port=None, | |||
set_default=False): | |||
"Create a new site" | |||
from frappe.installer import _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, | |||
no_mariadb_socket=no_mariadb_socket, db_password=db_password, db_type=db_type, db_host=db_host, | |||
db_port=db_port, new_site=True) | |||
_new_site(db_name, site, db_root_username=db_root_username, | |||
db_root_password=db_root_password, admin_password=admin_password, | |||
verbose=verbose, install_apps=install_app, source_sql=source_sql, force=force, | |||
no_mariadb_socket=no_mariadb_socket, db_password=db_password, db_type=db_type, db_host=db_host, | |||
db_port=db_port, new_site=True) | |||
if len(frappe.utils.get_sites()) == 1: | |||
if set_default: | |||
use(site) | |||
@click.command('restore') | |||
@click.argument('sql-file-path') | |||
@click.option('--mariadb-root-username', default='root', help='Root username for MariaDB') | |||
@click.option('--mariadb-root-password', help='Root password for MariaDB') | |||
@click.option('--db-root-username', '--mariadb-root-username', help='Root username for MariaDB or PostgreSQL, Default is "root"') | |||
@click.option('--db-root-password', '--mariadb-root-password', help='Root password for MariaDB or PostgreSQL') | |||
@click.option('--db-name', help='Database name for site in case it is a new one') | |||
@click.option('--admin-password', help='Administrator password for new site') | |||
@click.option('--install-app', multiple=True, help='Install app after installation') | |||
@@ -57,7 +59,7 @@ def new_site(site, mariadb_root_username=None, mariadb_root_password=None, admin | |||
@click.option('--force', is_flag=True, default=False, help='Ignore the validations and downgrade warnings. This action is not recommended') | |||
@click.option('--encryption-key', help='Backup encryption key') | |||
@pass_context | |||
def restore(context, sql_file_path, encryption_key=None, mariadb_root_username=None, mariadb_root_password=None, | |||
def restore(context, sql_file_path, encryption_key=None, db_root_username=None, db_root_password=None, | |||
db_name=None, verbose=None, install_app=None, admin_password=None, force=None, with_public_files=None, | |||
with_private_files=None): | |||
"Restore site database from an sql file" | |||
@@ -150,8 +152,8 @@ def restore(context, sql_file_path, encryption_key=None, mariadb_root_username=N | |||
try: | |||
_new_site(frappe.conf.db_name, site, mariadb_root_username=mariadb_root_username, | |||
mariadb_root_password=mariadb_root_password, admin_password=admin_password, | |||
_new_site(frappe.conf.db_name, site, db_root_username=db_root_username, | |||
db_root_password=db_root_password, admin_password=admin_password, | |||
verbose=context.verbose, install_apps=install_app, source_sql=decompressed_file_name, | |||
force=True, db_type=frappe.conf.db_type) | |||
@@ -290,16 +292,16 @@ def partial_restore(context, sql_file_path, verbose, encryption_key=None): | |||
@click.command('reinstall') | |||
@click.option('--admin-password', help='Administrator Password for reinstalled site') | |||
@click.option('--mariadb-root-username', help='Root username for MariaDB') | |||
@click.option('--mariadb-root-password', help='Root password for MariaDB') | |||
@click.option('--db-root-username', '--mariadb-root-username', help='Root username for MariaDB or PostgreSQL, Default is "root"') | |||
@click.option('--db-root-password', '--mariadb-root-password', help='Root password for MariaDB or PostgreSQL') | |||
@click.option('--yes', is_flag=True, default=False, help='Pass --yes to skip confirmation') | |||
@pass_context | |||
def reinstall(context, admin_password=None, mariadb_root_username=None, mariadb_root_password=None, yes=False): | |||
def reinstall(context, admin_password=None, db_root_username=None, db_root_password=None, yes=False): | |||
"Reinstall site ie. wipe all data and start over" | |||
site = get_site(context) | |||
_reinstall(site, admin_password, mariadb_root_username, mariadb_root_password, yes, verbose=context.verbose) | |||
_reinstall(site, admin_password, db_root_username, db_root_password, yes, verbose=context.verbose) | |||
def _reinstall(site, admin_password=None, mariadb_root_username=None, mariadb_root_password=None, yes=False, verbose=False): | |||
def _reinstall(site, admin_password=None, db_root_username=None, db_root_password=None, yes=False, verbose=False): | |||
from frappe.installer import _new_site | |||
if not yes: | |||
@@ -319,7 +321,7 @@ def _reinstall(site, admin_password=None, mariadb_root_username=None, mariadb_ro | |||
frappe.init(site=site) | |||
_new_site(frappe.conf.db_name, site, verbose=verbose, force=True, reinstall=True, install_apps=installed, | |||
mariadb_root_username=mariadb_root_username, mariadb_root_password=mariadb_root_password, | |||
db_root_username=db_root_username, db_root_password=db_root_password, | |||
admin_password=admin_password) | |||
@click.command('install-app') | |||
@@ -656,16 +658,16 @@ def uninstall(context, app, dry_run, yes, no_backup, force): | |||
@click.command('drop-site') | |||
@click.argument('site') | |||
@click.option('--root-login', default='root') | |||
@click.option('--root-password') | |||
@click.option('--db-root-username', '--mariadb-root-username', '--root-login', help='Root username for MariaDB or PostgreSQL, Default is "root"') | |||
@click.option('--db-root-password', '--mariadb-root-password', '--root-password', help='Root password for MariaDB or PostgreSQL') | |||
@click.option('--archived-sites-path') | |||
@click.option('--no-backup', is_flag=True, default=False) | |||
@click.option('--force', help='Force drop-site even if an error is encountered', is_flag=True, default=False) | |||
def drop_site(site, root_login='root', root_password=None, archived_sites_path=None, force=False, no_backup=False): | |||
_drop_site(site, root_login, root_password, archived_sites_path, force, no_backup) | |||
def drop_site(site, db_root_username='root', db_root_password=None, archived_sites_path=None, force=False, no_backup=False): | |||
_drop_site(site, db_root_username, db_root_password, archived_sites_path, force, no_backup) | |||
def _drop_site(site, root_login='root', root_password=None, archived_sites_path=None, force=False, no_backup=False): | |||
def _drop_site(site, db_root_username=None, db_root_password=None, archived_sites_path=None, force=False, no_backup=False): | |||
"Remove site from database and filesystem" | |||
from frappe.database import drop_user_and_database | |||
from frappe.utils.backups import scheduled_backup | |||
@@ -690,7 +692,7 @@ def _drop_site(site, root_login='root', root_password=None, archived_sites_path= | |||
click.echo("\n".join(messages)) | |||
sys.exit(1) | |||
drop_user_and_database(frappe.conf.db_name, root_login, root_password) | |||
drop_user_and_database(frappe.conf.db_name, db_root_username, db_root_password) | |||
archived_sites_path = archived_sites_path or os.path.join(frappe.get_app_path('frappe'), '..', '..', '..', 'archived', 'sites') | |||
@@ -166,7 +166,7 @@ class Importer: | |||
if not self.data_import.status == "Partial Success": | |||
self.data_import.db_set("status", "Partial Success") | |||
# commit after every successful import | |||
frappe.db.commit() | |||
@@ -99,7 +99,7 @@ | |||
"label": "Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
@@ -547,7 +547,7 @@ | |||
"index_web_pages_for_search": 1, | |||
"istable": 1, | |||
"links": [], | |||
"modified": "2022-01-27 21:22:20.529072", | |||
"modified": "2022-02-14 11:56:19.812863", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "DocField", | |||
@@ -2,7 +2,8 @@ | |||
# See license.txt | |||
# import frappe | |||
import unittest | |||
from frappe.tests.utils import FrappeTestCase | |||
class Test{classname}(unittest.TestCase): | |||
class Test{classname}(FrappeTestCase): | |||
pass |
@@ -314,23 +314,24 @@ result = [ | |||
{ | |||
"parent_column": "Parent 1", | |||
"column_1": 200, | |||
"column_2": 150.50 | |||
"column_2": 150.50 | |||
}, | |||
{ | |||
"parent_column": "Child 1", | |||
"column_1": 100, | |||
"column_2": 75.25, | |||
"parent_value": "Parent 1" | |||
"parent_value": "Parent 1" | |||
}, | |||
{ | |||
"parent_column": "Child 2", | |||
"column_1": 100, | |||
"column_2": 75.25, | |||
"parent_value": "Parent 1" | |||
"parent_value": "Parent 1" | |||
} | |||
] | |||
result = add_total_row(result, columns, meta=None, report_settings=report_settings) | |||
result = add_total_row(result, columns, meta=None, is_tree=report_settings['tree'], | |||
parent_field=report_settings['parent_field']) | |||
self.assertEqual(result[-1][0], "Total") | |||
self.assertEqual(result[-1][1], 200) | |||
self.assertEqual(result[-1][2], 150.50) |
@@ -122,7 +122,7 @@ | |||
"label": "Field Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break", | |||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break", | |||
"reqd": 1 | |||
}, | |||
{ | |||
@@ -431,7 +431,7 @@ | |||
"idx": 1, | |||
"index_web_pages_for_search": 1, | |||
"links": [], | |||
"modified": "2022-01-27 21:47:01.065556", | |||
"modified": "2022-02-14 15:42:21.885999", | |||
"modified_by": "Administrator", | |||
"module": "Custom", | |||
"name": "Custom Field", | |||
@@ -600,4 +600,4 @@ ALLOWED_FIELDTYPE_CHANGE = ( | |||
('Code', 'Geolocation'), | |||
('Table', 'Table MultiSelect')) | |||
ALLOWED_OPTIONS_CHANGE = ('Read Only', 'HTML', 'Select', 'Data') | |||
ALLOWED_OPTIONS_CHANGE = ('Read Only', 'HTML', 'Data') |
@@ -85,7 +85,7 @@ | |||
"label": "Type", | |||
"oldfieldname": "fieldtype", | |||
"oldfieldtype": "Select", | |||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", | |||
"reqd": 1, | |||
"search_index": 1 | |||
}, | |||
@@ -450,7 +450,7 @@ | |||
"index_web_pages_for_search": 1, | |||
"istable": 1, | |||
"links": [], | |||
"modified": "2022-02-08 19:38:16.111199", | |||
"modified": "2022-02-25 16:01:12.616736", | |||
"modified_by": "Administrator", | |||
"module": "Custom", | |||
"name": "Customize Form Field", | |||
@@ -460,4 +460,4 @@ | |||
"sort_field": "modified", | |||
"sort_order": "ASC", | |||
"states": [] | |||
} | |||
} |
@@ -52,7 +52,8 @@ class MariaDBDatabase(Database): | |||
'Barcode': ('longtext', ''), | |||
'Geolocation': ('longtext', ''), | |||
'Duration': ('decimal', '21,9'), | |||
'Icon': ('varchar', self.VARCHAR_LEN) | |||
'Icon': ('varchar', self.VARCHAR_LEN), | |||
'Autocomplete': ('varchar', self.VARCHAR_LEN), | |||
} | |||
def get_connection(self): | |||
@@ -62,7 +62,8 @@ class PostgresDatabase(Database): | |||
'Barcode': ('text', ''), | |||
'Geolocation': ('text', ''), | |||
'Duration': ('decimal', '21,9'), | |||
'Icon': ('varchar', self.VARCHAR_LEN) | |||
'Icon': ('varchar', self.VARCHAR_LEN), | |||
'Autocomplete': ('varchar', self.VARCHAR_LEN), | |||
} | |||
def get_connection(self): | |||
@@ -4,7 +4,7 @@ import frappe | |||
def setup_database(force, source_sql=None, verbose=False): | |||
root_conn = get_root_connection() | |||
root_conn = get_root_connection(frappe.flags.root_login, frappe.flags.root_password) | |||
root_conn.commit() | |||
root_conn.sql("DROP DATABASE IF EXISTS `{0}`".format(frappe.conf.db_name)) | |||
root_conn.sql("DROP USER IF EXISTS {0}".format(frappe.conf.db_name)) | |||
@@ -70,7 +70,7 @@ def import_db_from_sql(source_sql=None, verbose=False): | |||
print(f"\nSTDOUT by psql:\n{restore_proc.stdout.decode()}\nImported from Database File: {source_sql}") | |||
def setup_help_database(help_db_name): | |||
root_conn = get_root_connection() | |||
root_conn = get_root_connection(frappe.flags.root_login, frappe.flags.root_password) | |||
root_conn.sql("DROP DATABASE IF EXISTS `{0}`".format(help_db_name)) | |||
root_conn.sql("DROP USER IF EXISTS {0}".format(help_db_name)) | |||
root_conn.sql("CREATE DATABASE `{0}`".format(help_db_name)) | |||
@@ -244,7 +244,13 @@ class Query: | |||
_operator = OPERATOR_MAP[value[0]] | |||
conditions = conditions.where(_operator(Field(key), value[1])) | |||
else: | |||
conditions = conditions.where(_operator(Field(key), value)) | |||
if value is not None: | |||
conditions = conditions.where(_operator(Field(key), value)) | |||
else: | |||
_table = conditions._from[0] | |||
field = getattr(_table, key) | |||
conditions = conditions.where(field.isnull()) | |||
conditions = self.add_conditions(conditions, **kwargs) | |||
return conditions | |||
@@ -495,7 +495,7 @@ frappe.ui.form.on('Dashboard Chart', { | |||
set_parent_document_type: async function(frm) { | |||
let document_type = frm.doc.document_type; | |||
let doc_is_table = document_type && | |||
let doc_is_table = document_type && | |||
(await frappe.db.get_value('DocType', document_type, 'istable')).message.istable; | |||
frm.set_df_property('parent_document_type', 'hidden', !doc_is_table); | |||
@@ -16,7 +16,7 @@ frappe.ui.form.on('Form Tour', { | |||
frm.add_custom_button(__('Show Tour'), async () => { | |||
const issingle = await check_if_single(frm.doc.reference_doctype); | |||
let route_changed = null; | |||
if (issingle) { | |||
route_changed = frappe.set_route('Form', frm.doc.reference_doctype); | |||
} else if (frm.doc.first_document) { | |||
@@ -76,26 +76,6 @@ def archive_restore_column(board_name, column_title, status): | |||
return doc.columns | |||
@frappe.whitelist() | |||
def update_doc(doc): | |||
'''Updates the doc when card is edited''' | |||
doc = json.loads(doc) | |||
try: | |||
to_update = doc | |||
doctype = doc['doctype'] | |||
docname = doc['name'] | |||
doc = frappe.get_doc(doctype, docname) | |||
doc.update(to_update) | |||
doc.save() | |||
except: | |||
return { | |||
'doc': doc, | |||
'exc': frappe.utils.get_traceback() | |||
} | |||
return doc | |||
@frappe.whitelist() | |||
def update_order(board_name, order): | |||
'''Save the order of cards in columns''' | |||
@@ -24,7 +24,7 @@ frappe.views.calendar["ToDo"] = { | |||
"options": "reference_type", | |||
"label": __("Task") | |||
} | |||
], | |||
get_events_method: "frappe.desk.calendar.get_events" | |||
}; | |||
@@ -9,7 +9,7 @@ frappe.ui.form.on('Workspace', { | |||
refresh: function(frm) { | |||
frm.enable_save(); | |||
if (frm.doc.for_user || (frm.doc.public && !frm.has_perm('write') && | |||
if (frm.doc.for_user || (frm.doc.public && !frm.has_perm('write') && | |||
!frappe.user.has_role('Workspace Manager'))) { | |||
frm.trigger('disable_form'); | |||
} | |||
@@ -176,9 +176,9 @@ def update_page(name, title, icon, parent, public): | |||
doc = frappe.get_doc("Workspace", name) | |||
filters = { | |||
filters = { | |||
'parent_page': doc.title, | |||
'public': doc.public | |||
'public': doc.public | |||
} | |||
child_docs = frappe.get_list("Workspace", filters=filters) | |||
@@ -255,7 +255,7 @@ def delete_page(page): | |||
def sort_pages(sb_public_items, sb_private_items): | |||
if not loads(sb_public_items) and not loads(sb_private_items): | |||
return | |||
sb_public_items = loads(sb_public_items) | |||
sb_private_items = loads(sb_private_items) | |||
@@ -292,7 +292,7 @@ def last_sequence_id(doc): | |||
if not doc_exists: | |||
return 0 | |||
return frappe.db.get_list('Workspace', | |||
return frappe.db.get_list('Workspace', | |||
fields=['sequence_id'], | |||
filters={ | |||
'public': doc.public, | |||
@@ -11,8 +11,10 @@ from frappe.model.utils.user_settings import get_user_settings | |||
from frappe.permissions import get_doc_permissions | |||
from frappe.desk.form.document_follow import is_document_followed | |||
from frappe import _ | |||
from frappe import _dict | |||
from urllib.parse import quote | |||
@frappe.whitelist() | |||
def getdoc(doctype, name, user=None): | |||
""" | |||
@@ -50,8 +52,11 @@ def getdoc(doctype, name, user=None): | |||
doc.add_seen() | |||
set_link_titles(doc) | |||
if frappe.response.docs is None: | |||
frappe.response = _dict({"docs": []}) | |||
frappe.response.docs.append(doc) | |||
@frappe.whitelist() | |||
def getdoctype(doctype, with_parent=False, cached_timestamp=None): | |||
"""load doctype""" | |||
@@ -392,7 +392,7 @@ def make_records(records, debug=False): | |||
doc.flags.ignore_mandatory = True | |||
try: | |||
doc.insert(ignore_permissions=True, ignore_if_duplicate=True) | |||
doc.insert(ignore_permissions=True) | |||
frappe.db.commit() | |||
except frappe.DuplicateEntryError as e: | |||
@@ -73,7 +73,7 @@ def get_report_result(report, filters): | |||
return res | |||
@frappe.read_only() | |||
def generate_report_result(report, filters=None, user=None, custom_columns=None, report_settings=None): | |||
def generate_report_result(report, filters=None, user=None, custom_columns=None, is_tree=False, parent_field=None): | |||
user = user or frappe.session.user | |||
filters = filters or [] | |||
@@ -108,7 +108,7 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None, | |||
result = get_filtered_data(report.ref_doctype, columns, result, user) | |||
if cint(report.add_total_row) and result and not skip_total_row: | |||
result = add_total_row(result, columns, report_settings=report_settings) | |||
result = add_total_row(result, columns, is_tree=is_tree, parent_field=parent_field) | |||
return { | |||
"result": result, | |||
@@ -210,7 +210,7 @@ def get_script(report_name): | |||
@frappe.whitelist() | |||
@frappe.read_only() | |||
def run(report_name, filters=None, user=None, ignore_prepared_report=False, custom_columns=None, report_settings=None): | |||
def run(report_name, filters=None, user=None, ignore_prepared_report=False, custom_columns=None, is_tree=False, parent_field=None): | |||
report = get_report_doc(report_name) | |||
if not user: | |||
user = frappe.session.user | |||
@@ -238,7 +238,7 @@ def run(report_name, filters=None, user=None, ignore_prepared_report=False, cust | |||
dn = "" | |||
result = get_prepared_report_result(report, filters, dn, user) | |||
else: | |||
result = generate_report_result(report, filters, user, custom_columns, report_settings) | |||
result = generate_report_result(report, filters, user, custom_columns, is_tree, parent_field) | |||
result["add_total_row"] = report.add_total_row and not result.get( | |||
"skip_total_row", False | |||
@@ -435,18 +435,9 @@ def build_xlsx_data(columns, data, visible_idx, include_indentation, ignore_visi | |||
return result, column_widths | |||
def add_total_row(result, columns, meta=None, report_settings=None): | |||
def add_total_row(result, columns, meta=None, is_tree=False, parent_field=None): | |||
total_row = [""] * len(columns) | |||
has_percent = [] | |||
is_tree = False | |||
parent_field = '' | |||
if report_settings: | |||
if isinstance(report_settings, (str,)): | |||
report_settings = json.loads(report_settings) | |||
is_tree = report_settings.get('tree') | |||
parent_field = report_settings.get('parent_field') | |||
for i, col in enumerate(columns): | |||
fieldtype, options, fieldname = None, None, None | |||
@@ -61,7 +61,7 @@ def get_context(context): | |||
""") | |||
def validate_standard(self): | |||
if self.is_standard and not frappe.conf.developer_mode: | |||
if self.is_standard and self.enabled and not frappe.conf.developer_mode: | |||
frappe.throw(_('Cannot edit Standard Notification. To edit, please disable this and duplicate it')) | |||
def validate_condition(self): | |||
@@ -630,7 +630,7 @@ class InboundMail(Email): | |||
if self.reference_document(): | |||
data['reference_doctype'] = self.reference_document().doctype | |||
data['reference_name'] = self.reference_document().name | |||
else: | |||
else: | |||
if append_to and append_to != 'Communication': | |||
reference_doc = self._create_reference_document(append_to) | |||
if reference_doc: | |||
@@ -14,8 +14,8 @@ from frappe.defaults import _clear_cache | |||
def _new_site( | |||
db_name, | |||
site, | |||
mariadb_root_username=None, | |||
mariadb_root_password=None, | |||
db_root_username=None, | |||
db_root_password=None, | |||
admin_password=None, | |||
verbose=False, | |||
install_apps=None, | |||
@@ -60,8 +60,8 @@ def _new_site( | |||
installing = touch_file(get_site_path("locks", "installing.lock")) | |||
install_db( | |||
root_login=mariadb_root_username, | |||
root_password=mariadb_root_password, | |||
root_login=db_root_username, | |||
root_password=db_root_password, | |||
db_name=db_name, | |||
admin_password=admin_password, | |||
verbose=verbose, | |||
@@ -92,7 +92,7 @@ def _new_site( | |||
print("*** Scheduler is", scheduler_status, "***") | |||
def install_db(root_login="root", root_password=None, db_name=None, source_sql=None, | |||
def install_db(root_login=None, root_password=None, db_name=None, source_sql=None, | |||
admin_password=None, verbose=True, force=0, site_config=None, reinstall=False, | |||
db_password=None, db_type=None, db_host=None, db_port=None, no_mariadb_socket=False): | |||
import frappe.database | |||
@@ -101,6 +101,11 @@ def install_db(root_login="root", root_password=None, db_name=None, source_sql=N | |||
if not db_type: | |||
db_type = frappe.conf.db_type or 'mariadb' | |||
if not root_login and db_type == 'mariadb': | |||
root_login='root' | |||
elif not root_login and db_type == 'postgres': | |||
root_login='postgres' | |||
make_conf(db_name, site_config=site_config, db_password=db_password, db_type=db_type, db_host=db_host, db_port=db_port) | |||
frappe.flags.in_install_db = True | |||
@@ -3,6 +3,6 @@ | |||
frappe.ui.form.on('Razorpay Settings', { | |||
refresh: function(frm) { | |||
} | |||
}); |
@@ -35,7 +35,8 @@ data_fieldtypes = ( | |||
'Barcode', | |||
'Geolocation', | |||
'Duration', | |||
'Icon' | |||
'Icon', | |||
'Autocomplete', | |||
) | |||
attachment_fieldtypes = ( | |||
@@ -471,7 +471,7 @@ class Document(BaseDocument): | |||
# We'd probably want the creation and owner to be set via API | |||
# or Data import at some point, that'd have to be handled here | |||
if self.is_new() and not (frappe.flags.in_patch or frappe.flags.in_migrate): | |||
if self.is_new() and not (frappe.flags.in_install or frappe.flags.in_patch or frappe.flags.in_migrate): | |||
self.creation = self.modified | |||
self.owner = self.modified_by | |||
@@ -860,14 +860,14 @@ class Document(BaseDocument): | |||
def run_method(self, method, *args, **kwargs): | |||
"""run standard triggers, plus those in hooks""" | |||
if "flags" in kwargs: | |||
del kwargs["flags"] | |||
if hasattr(self, method) and hasattr(getattr(self, method), "__call__"): | |||
fn = lambda self, *args, **kwargs: getattr(self, method)(*args, **kwargs) | |||
else: | |||
# hack! to run hooks even if method does not exist | |||
fn = lambda self, *args, **kwargs: None | |||
def fn(self, *args, **kwargs): | |||
method_object = getattr(self, method, None) | |||
# Cannot have a field with same name as method | |||
# If method found in __dict__, expect it to be callable | |||
if method in self.__dict__ or callable(method_object): | |||
return method_object(*args, **kwargs) | |||
fn.__name__ = str(method) | |||
out = Document.hook(fn)(self, *args, **kwargs) | |||
@@ -1154,7 +1154,7 @@ class Document(BaseDocument): | |||
for f in hooks: | |||
add_to_return_value(self, f(self, method, *args, **kwargs)) | |||
return self._return_value | |||
return self.__dict__.pop("_return_value", None) | |||
return runner | |||
@@ -37,6 +37,7 @@ patches by using INI like file format: | |||
import configparser | |||
import time | |||
from enum import Enum | |||
from textwrap import dedent, indent | |||
from typing import List, Optional | |||
import frappe | |||
@@ -148,21 +149,36 @@ def run_single(patchmodule=None, method=None, methodargs=None, force=False): | |||
def execute_patch(patchmodule, method=None, methodargs=None): | |||
"""execute the patch""" | |||
block_user(True) | |||
frappe.db.begin() | |||
if patchmodule.startswith("execute:"): | |||
has_patch_file = False | |||
patch = patchmodule.split("execute:")[1] | |||
docstring = "" | |||
else: | |||
has_patch_file = True | |||
patch = f"{patchmodule.split()[0]}.execute" | |||
_patch = frappe.get_attr(patch) | |||
docstring = _patch.__doc__ or "" | |||
if docstring: | |||
docstring = "\n" + indent(dedent(docstring), "\t") | |||
print(f"Executing {patchmodule or methodargs} in {frappe.local.site} ({frappe.db.cur_db_name}){docstring}") | |||
start_time = time.time() | |||
frappe.db.begin() | |||
try: | |||
print('Executing {patch} in {site} ({db})'.format(patch=patchmodule or str(methodargs), | |||
site=frappe.local.site, db=frappe.db.cur_db_name)) | |||
if patchmodule: | |||
if patchmodule.startswith("finally:"): | |||
# run run patch at the end | |||
frappe.flags.final_patches.append(patchmodule) | |||
else: | |||
if patchmodule.startswith("execute:"): | |||
exec(patchmodule.split("execute:")[1],globals()) | |||
if has_patch_file: | |||
_patch() | |||
else: | |||
frappe.get_attr(patchmodule.split()[0] + ".execute")() | |||
exec(patch, globals()) | |||
update_patch_log(patchmodule) | |||
elif method: | |||
method(**methodargs) | |||
@@ -174,7 +190,7 @@ def execute_patch(patchmodule, method=None, methodargs=None): | |||
frappe.db.commit() | |||
end_time = time.time() | |||
block_user(False) | |||
print('Success: Done in {time}s'.format(time = round(end_time - start_time, 3))) | |||
print(f"Success: Done in {round(end_time - start_time, 3)}s") | |||
return True | |||
@@ -189,6 +189,7 @@ frappe.patches.v14_0.update_workspace2 # 20.09.2021 | |||
frappe.patches.v14_0.save_ratings_in_fraction #23-12-2021 | |||
frappe.patches.v14_0.transform_todo_schema | |||
frappe.patches.v14_0.remove_post_and_post_comment | |||
frappe.patches.v14_0.reset_creation_datetime | |||
[post_model_sync] | |||
frappe.patches.v14_0.drop_data_import_legacy | |||
@@ -15,7 +15,7 @@ def execute(): | |||
for file in files: | |||
file_path = file.file_url | |||
file_name = file_path.split('/')[-1] | |||
if not file_path.startswith(('/private/', '/files/')): | |||
continue | |||
@@ -0,0 +1,41 @@ | |||
import glob | |||
import json | |||
import frappe | |||
import os | |||
from frappe.query_builder import DocType as _DocType | |||
def execute(): | |||
"""Resetting creation datetimes for DocTypes""" | |||
DocType = _DocType("DocType") | |||
doctype_jsons = glob.glob( | |||
os.path.join("..", "apps", "frappe", "frappe", "**", "doctype", "**", "*.json") | |||
) | |||
frappe_modules = frappe.get_all( | |||
"Module Def", filters={"app_name": "frappe"}, pluck="name" | |||
) | |||
site_doctypes = frappe.get_all( | |||
"DocType", | |||
filters={"module": ("in", frappe_modules), "custom": False}, | |||
fields=["name", "creation"], | |||
) | |||
for dt_path in doctype_jsons: | |||
with open(dt_path) as f: | |||
try: | |||
file_schema = frappe._dict(json.load(f)) | |||
except Exception: | |||
continue | |||
if not file_schema.name: | |||
continue | |||
_site_schema = [x for x in site_doctypes if x.name == file_schema.name] | |||
if not _site_schema: | |||
continue | |||
if file_schema.creation != _site_schema[0].creation: | |||
frappe.qb.update(DocType).set( | |||
DocType.creation, file_schema.creation | |||
).where(DocType.name == file_schema.name).run() |
@@ -11,7 +11,26 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui | |||
set_options() { | |||
if (this.df.options) { | |||
let options = this.df.options || []; | |||
this._data = this.parse_options(options); | |||
this.set_data(options); | |||
} | |||
} | |||
format_for_input(value) { | |||
if (value == null) { | |||
return ""; | |||
} else if (this._data && this._data.length) { | |||
const item = this._data.find(i => i.value == value); | |||
return item ? item.label : value; | |||
} else { | |||
return value; | |||
} | |||
} | |||
get_input_value() { | |||
if (this.$input) { | |||
const label = this.$input.val(); | |||
const item = this._data?.find(i => i.label == label); | |||
return item ? item.value : label; | |||
} | |||
} | |||
@@ -23,7 +42,7 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui | |||
autoFirst: true, | |||
list: this.get_data(), | |||
data: function(item) { | |||
if (!(item instanceof Object)) { | |||
if (typeof item !== 'object') { | |||
var d = { value: item }; | |||
item = d; | |||
} | |||
@@ -65,6 +84,18 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui | |||
}; | |||
} | |||
init_option_cache() { | |||
if (!this.$input.cache) { | |||
this.$input.cache = {}; | |||
} | |||
if (!this.$input.cache[this.doctype]) { | |||
this.$input.cache[this.doctype] = {}; | |||
} | |||
if (!this.$input.cache[this.doctype][this.df.fieldname]) { | |||
this.$input.cache[this.doctype][this.df.fieldname] = {}; | |||
} | |||
} | |||
setup_awesomplete() { | |||
this.awesomplete = new Awesomplete( | |||
this.input, | |||
@@ -75,12 +106,18 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui | |||
.find('.awesomplete ul') | |||
.css('min-width', '100%'); | |||
this.$input.on( | |||
'input', | |||
frappe.utils.debounce(() => { | |||
this.init_option_cache(); | |||
this.$input.on('input', frappe.utils.debounce((e) => { | |||
const cached_options = this.$input.cache[this.doctype][this.df.fieldname][e.target.value]; | |||
if (cached_options && cached_options.length) { | |||
this.set_data(cached_options); | |||
} else if (this.get_query || this.df.get_query) { | |||
this.execute_query_if_exists(e.target.value); | |||
} else { | |||
this.awesomplete.list = this.get_data(); | |||
}, 500) | |||
); | |||
} | |||
}, 500)); | |||
this.$input.on('focus', () => { | |||
if (!this.$input.val()) { | |||
@@ -89,6 +126,17 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui | |||
} | |||
}); | |||
this.$input.on("blur", () => { | |||
if(this.selected) { | |||
this.selected = false; | |||
return; | |||
} | |||
var value = this.get_input_value(); | |||
if(value!==this.last_value) { | |||
this.parse_validate_and_set_in_model(value); | |||
} | |||
}); | |||
this.$input.on("awesomplete-open", () => { | |||
this.autocomplete_open = true; | |||
}); | |||
@@ -127,6 +175,75 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui | |||
return options; | |||
} | |||
execute_query_if_exists(term) { | |||
const args = { txt: term }; | |||
let get_query = this.get_query || this.df.get_query; | |||
if (!get_query) { | |||
return; | |||
} | |||
let set_nulls = function(obj) { | |||
$.each(obj, function(key, value) { | |||
if (value !== undefined) { | |||
obj[key] = value; | |||
} | |||
}); | |||
return obj; | |||
}; | |||
let process_query_object = function(obj) { | |||
if (obj.query) { | |||
args.query = obj.query; | |||
} | |||
if (obj.params) { | |||
set_nulls(obj.params); | |||
Object.assign(args, obj.params); | |||
} | |||
// turn off value translation | |||
if (obj.translate_values !== undefined) { | |||
this.translate_values = obj.translate_values; | |||
} | |||
}; | |||
if ($.isPlainObject(get_query)) { | |||
process_query_object(get_query); | |||
} else if (typeof get_query === "string") { | |||
args.query = get_query; | |||
} else { | |||
// get_query by function | |||
var q = get_query( | |||
(this.frm && this.frm.doc) || this.doc, | |||
this.doctype, | |||
this.docname | |||
); | |||
if (typeof q === "string") { | |||
// returns a string | |||
args.query = q; | |||
} else if ($.isPlainObject(q)) { | |||
// returns an object | |||
process_query_object(q); | |||
} | |||
} | |||
if (args.query) { | |||
frappe.call({ | |||
method: args.query, | |||
args: args, | |||
callback: ({ message }) => { | |||
if(!this.$input.is(":focus")) { | |||
return; | |||
} | |||
this.$input.cache[this.doctype][this.df.fieldname][term] = message; | |||
this.set_data(message); | |||
} | |||
}) | |||
} | |||
} | |||
get_data() { | |||
return this._data || []; | |||
} | |||
@@ -160,7 +160,7 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat | |||
get_df_options() { | |||
let df_options = this.df.options; | |||
if (!df_options) return {}; | |||
let options = {}; | |||
if (typeof df_options === 'string') { | |||
try { | |||
@@ -92,7 +92,7 @@ frappe.ui.form.ControlTable = class ControlTable extends frappe.ui.form.Control | |||
if (frappe.model.no_value_type.includes(field.fieldtype)) { | |||
return false; | |||
} | |||
const is_field_matching = () => { | |||
return ( | |||
field.fieldname.toLowerCase() === field_name || | |||
@@ -88,6 +88,9 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for | |||
make_quill_editor() { | |||
if (this.quill) return; | |||
this.quill_container = $('<div>').appendTo(this.input_area); | |||
if (this.df.max_height) { | |||
$(this.quill_container).css({'max-height': this.df.max_height, 'overflow': 'auto'}); | |||
} | |||
this.quill = new Quill(this.quill_container[0], this.get_quill_options()); | |||
this.bind_events(); | |||
} | |||
@@ -21,6 +21,9 @@ frappe.form.formatters = { | |||
} | |||
return value==null ? "" : value; | |||
}, | |||
Autocomplete: function(value) { | |||
return __(frappe.form.formatters["Data"](value)); | |||
}, | |||
Select: function(value) { | |||
return __(frappe.form.formatters["Data"](value)); | |||
}, | |||
@@ -66,7 +66,7 @@ export default class GridPagination { | |||
} | |||
// only allow numbers from 0-9 and up, down, left, right arrow keys | |||
if (charCode > 31 && (charCode < 48 || charCode > 57) && | |||
if (charCode > 31 && (charCode < 48 || charCode > 57) && | |||
![37, 38, 39, 40].includes(charCode)) { | |||
return false; | |||
} | |||
@@ -183,21 +183,20 @@ export default class GridRow { | |||
render_template() { | |||
this.set_row_index(); | |||
if(this.row_display) { | |||
if (this.row_display) { | |||
this.row_display.remove(); | |||
} | |||
// row index | |||
if(this.doc) { | |||
if(!this.row_index) { | |||
this.row_index = $('<div style="float: left; margin-left: 15px; margin-top: 8px; \ | |||
margin-right: -20px;">'+this.row_check_html+' <span></span></div>').appendTo(this.row); | |||
} | |||
if (!this.row_index) { | |||
this.row_index = $(`<div class="template-row-index">${this.row_check_html}<span></span></div>`).appendTo(this.row); | |||
} | |||
if (this.doc) { | |||
this.row_index.find('span').html(this.doc.idx); | |||
} | |||
this.row_display = $('<div class="row-data sortable-handle template-row">'+ | |||
+'</div>').appendTo(this.row) | |||
this.row_display = $('<div class="row-data sortable-handle template-row"></div>').appendTo(this.row) | |||
.html(frappe.render(this.grid.template, { | |||
doc: this.doc ? frappe.get_format_helper(this.doc) : null, | |||
frm: this.frm, | |||
@@ -616,6 +615,7 @@ export default class GridRow { | |||
if (!this.doc) { | |||
$col.attr("title", txt); | |||
} | |||
df.fieldname && $col.static_area.toggleClass('reqd', Boolean(df.reqd)); | |||
$col.df = df; | |||
$col.column_index = ci; | |||
@@ -148,6 +148,7 @@ frappe.ui.form.save = function (frm, action, callback, btn) { | |||
}); | |||
if (frm.is_new() && frm.meta.autoname === 'Prompt' && !frm.doc.__newname) { | |||
has_errors = true; | |||
error_fields = [__('Name'), ...error_fields]; | |||
} | |||
@@ -150,7 +150,7 @@ frappe.views.ListViewSelect = class ListViewSelect { | |||
const views_wrapper = this.sidebar.sidebar.find(".views-section"); | |||
views_wrapper.find(".sidebar-label").html(`${__(view)}`); | |||
const $dropdown = views_wrapper.find(".views-dropdown"); | |||
let placeholder = `${__("Select {0}", [__(view)])}`; | |||
let html = ``; | |||
@@ -112,9 +112,9 @@ frappe.ui.FieldSelect = class FieldSelect { | |||
// main table | |||
var main_table_fields = std_filters.concat(frappe.meta.docfield_list[me.doctype]); | |||
$.each(frappe.utils.sort(main_table_fields, "label", "string"), function(i, df) { | |||
let doctype = frappe.get_meta(me.doctype).istable && me.parent_doctype ? | |||
let doctype = frappe.get_meta(me.doctype).istable && me.parent_doctype ? | |||
me.parent_doctype : me.doctype; | |||
// show fields where user has read access and if report hide flag is not set | |||
if (frappe.perm.has_perm(doctype, df.permlevel, "read")) | |||
me.add_field_option(df); | |||
@@ -132,9 +132,9 @@ frappe.ui.FieldSelect = class FieldSelect { | |||
} | |||
$.each(frappe.utils.sort(child_table_fields, "label", "string"), function(i, df) { | |||
let doctype = frappe.get_meta(me.doctype).istable && me.parent_doctype ? | |||
let doctype = frappe.get_meta(me.doctype).istable && me.parent_doctype ? | |||
me.parent_doctype : me.doctype; | |||
// show fields where user has read access and if report hide flag is not set | |||
if (frappe.perm.has_perm(doctype, df.permlevel, "read")) | |||
me.add_field_option(df); | |||
@@ -73,7 +73,7 @@ frappe.ui.LinkPreview = class { | |||
} | |||
this.popover_timeout = setTimeout(() => { | |||
if (this.popover) { | |||
if (this.popover && this.popover.options) { | |||
let new_content = this.get_popover_html(preview_data); | |||
this.popover.options.content = new_content; | |||
} else { | |||
@@ -244,7 +244,7 @@ Object.assign(frappe.utils, { | |||
}; | |||
return String(txt).replace( | |||
/[&<>"'`=/]/g, | |||
/[&<>"'`=/]/g, | |||
char => escape_html_mapping[char] || char | |||
); | |||
}, | |||
@@ -262,7 +262,7 @@ Object.assign(frappe.utils, { | |||
}; | |||
return String(txt).replace( | |||
/&|<|>|"|'|/|`|=/g, | |||
/&|<|>|"|'|/|`|=/g, | |||
char => unescape_html_mapping[char] || char | |||
); | |||
}, | |||
@@ -1435,7 +1435,7 @@ Object.assign(frappe.utils, { | |||
// for link titles | |||
frappe._link_titles = {}; | |||
} | |||
frappe._link_titles[doctype + "::" + name] = value; | |||
}, | |||
@@ -150,18 +150,6 @@ frappe.provide("frappe.views"); | |||
} | |||
updater.set({ cards: cards }); | |||
}, | |||
update_doc: function(updater, doc, card) { | |||
var state = this; | |||
return frappe.call({ | |||
method: method_prefix + "update_doc", | |||
args: { doc: doc }, | |||
freeze: true | |||
}).then(function(r) { | |||
var updated_doc = r.message; | |||
var updated_card = prepare_card(card, state, updated_doc); | |||
fluxify.doAction('update_card', updated_card); | |||
}); | |||
}, | |||
update_order_for_single_card: function(updater, card) { | |||
// cache original order | |||
const _cards = this.cards.slice(); | |||
@@ -10,14 +10,14 @@ | |||
<link href="{{ base_url }}/assets/frappe/css/bootstrap.css" rel="stylesheet"> | |||
<link type="text/css" rel="stylesheet" | |||
href="{{ base_url }}/assets/frappe/css/font-awesome.css"> | |||
<link rel="stylesheet" type="text/css" href="{{ base_url }}/assets/frappe/css/tree.css"> | |||
<link rel="stylesheet" type="text/css" href="{{ base_url }}/assets/frappe/css/tree.css"> | |||
<style> | |||
{{ print_css }} | |||
</style> | |||
<style> | |||
.tree.opened::before, | |||
.tree-node.opened::before, | |||
.tree:last-child::after, | |||
.tree.opened::before, | |||
.tree-node.opened::before, | |||
.tree:last-child::after, | |||
.tree-node:last-child::after { | |||
z-index: 1; | |||
border-left: 1px solid #d1d8dd; | |||
@@ -28,7 +28,7 @@ | |||
text-decoration: none; | |||
cursor: default; | |||
} | |||
.tree.opened > .tree-children > .tree-node > .tree-link::before, | |||
.tree.opened > .tree-children > .tree-node > .tree-link::before, | |||
.tree-node.opened > .tree-children > .tree-node > .tree-link::before { | |||
border-top: 1px solid #d1d8dd; | |||
z-index: 1; | |||
@@ -578,7 +578,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { | |||
args: { | |||
report_name: this.report_name, | |||
filters: filters, | |||
report_settings: this.report_settings | |||
is_tree: this.report_settings.tree, | |||
parent_field: this.report_settings.parent_field | |||
}, | |||
callback: resolve, | |||
always: () => this.page.btn_secondary.prop('disabled', false) | |||
@@ -50,7 +50,7 @@ export default class Block { | |||
document.documentElement.addEventListener('mousemove', do_drag, false); | |||
document.documentElement.addEventListener('mouseup', stop_drag, false); | |||
} | |||
function do_drag(e) { | |||
$(this).css("cursor", "col-resize"); | |||
$('.widget').css("pointer-events", "none"); | |||
@@ -72,7 +72,7 @@ export default class Block { | |||
} else { | |||
window.getSelection().removeAllRanges(); | |||
} | |||
} | |||
} | |||
function stop_drag() { | |||
$(this).css("cursor", "default"); | |||
@@ -221,7 +221,7 @@ export default class Block { | |||
$widget_control.prepend($button); | |||
this.dropdown_list.forEach((item) => { | |||
if ((item.label == 'Expand' || item.label == 'Shrink') && | |||
if ((item.label == 'Expand' || item.label == 'Shrink') && | |||
me.options && !me.options.allow_resize) { | |||
return; | |||
} | |||
@@ -107,7 +107,7 @@ export default class Header extends Block { | |||
if (data.text !== undefined) { | |||
let text = this._data.text || ''; | |||
const contains_html_tag = /<[a-z][\s\S]*>/i.test(text); | |||
this._element.innerHTML = contains_html_tag ? | |||
this._element.innerHTML = contains_html_tag ? | |||
text : `<span class="h${this._settings.default_size}">${text}</span>`; | |||
} | |||
@@ -36,7 +36,7 @@ export default class HeaderSize { | |||
checkState(selection) { | |||
let termWrapper = this.api.selection.findParentTag('SPAN'); | |||
for (const h of ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']) { | |||
if (termWrapper && termWrapper.classList.contains(h)) { | |||
let num = h.match(/\d+/)[0]; | |||
@@ -57,7 +57,7 @@ export default class HeaderSize { | |||
span.innerText = range.toString(); | |||
this.remove_parent_tag(range, range.commonAncestorContainer, span); | |||
range.extractContents(); | |||
range.insertNode(span); | |||
this.api.inlineToolbar.close(); | |||
@@ -90,7 +90,7 @@ export default class HeaderSize { | |||
renderActions() { | |||
this.actions = document.createElement('div'); | |||
this.actions.classList = 'header-level-select'; | |||
this.headerLevels = new Array(6).fill().map((_, idx) => { | |||
const $header_level = document.createElement('div'); | |||
$header_level.classList.add(`h${idx+1}`, 'header-level'); | |||
@@ -116,7 +116,7 @@ export default class Paragraph extends Block { | |||
this.wrapper.appendChild(this._element); | |||
this._element.classList.remove('widget'); | |||
$para_control.appendTo(this.wrapper); | |||
this.wrapper.classList.add('widget', 'paragraph', 'edit-mode'); | |||
this.open_block_list(); | |||
@@ -219,7 +219,7 @@ frappe.views.Workspace = class Workspace { | |||
$sidebar[0].firstElementChild.classList.add("selected"); | |||
if (sidebar_page) sidebar_page.selected = true; | |||
// open child sidebar section if closed | |||
// open child sidebar section if closed | |||
$sidebar.parent().hasClass('hidden') && | |||
$sidebar.parent().removeClass('hidden'); | |||
@@ -244,7 +244,7 @@ frappe.views.Workspace = class Workspace { | |||
this.pages[page.name] = data.message; | |||
if (!this.page_data || Object.keys(this.page_data).length === 0) return; | |||
if (this.page_data.charts && this.page_data.charts.items.length === 0) return; | |||
if (this.page_data.charts && this.page_data.charts.items.length === 0) return; | |||
return frappe.dashboard_utils.get_dashboard_settings().then(settings => { | |||
if (settings) { | |||
@@ -596,9 +596,9 @@ frappe.views.Workspace = class Workspace { | |||
} | |||
update_cached_values(old_item, new_item, duplicate, new_page) { | |||
let [from_pages, to_pages] = old_item.public ? | |||
let [from_pages, to_pages] = old_item.public ? | |||
[this.public_pages, this.private_pages] : [this.private_pages, this.public_pages]; | |||
let old_item_index = from_pages.findIndex(page => page.title == old_item.title); | |||
duplicate && old_item_index++; | |||
@@ -859,7 +859,7 @@ frappe.views.Workspace = class Workspace { | |||
public: page.attributes['item-public'].value | |||
}); | |||
let $drop_icon = $(page).find('.sidebar-item-control .drop-icon').first(); | |||
let $drop_icon = $(page).find('.sidebar-item-control .drop-icon').first(); | |||
if ($(page).find('.sidebar-child-item > *').length != 0) { | |||
$drop_icon.removeClass('hidden'); | |||
} else { | |||
@@ -993,13 +993,13 @@ frappe.views.Workspace = class Workspace { | |||
} | |||
} | |||
}); | |||
this.update_cached_values(new_page, new_page, true, true); | |||
let pre_url = new_page.public ? '' : 'private/'; | |||
let route = pre_url + frappe.router.slug(new_page.title); | |||
frappe.set_route(route); | |||
this.make_sidebar(); | |||
this.show_sidebar_actions(); | |||
}); | |||
@@ -1010,15 +1010,15 @@ frappe.views.Workspace = class Workspace { | |||
validate_page(new_page, old_page) { | |||
let message = ""; | |||
let [from_pages, to_pages] = new_page.is_public ? | |||
let [from_pages, to_pages] = new_page.is_public ? | |||
[this.private_pages, this.public_pages] : [this.public_pages, this.private_pages]; | |||
let section = this.sidebar_categories[new_page.is_public]; | |||
if (to_pages && to_pages.filter(p => p.title == new_page.title)[0]) { | |||
message = `Page with title ${new_page.title} already exist.`; | |||
} | |||
} | |||
if (frappe.router.doctype_route_exist(frappe.router.slug(new_page.title))) { | |||
message = "Doctype with same route already exist. Please choose different title."; | |||
} | |||
@@ -698,12 +698,12 @@ export default class ChartWidget extends Widget { | |||
.get_filters_for_chart_type(this.chart_doc).then(filters => { | |||
chart_saved_filters = this.update_default_date_filters(filters, chart_saved_filters); | |||
this.filters = | |||
frappe.utils.parse_array(user_saved_filters) || frappe.utils.parse_array(this.filters) | |||
frappe.utils.parse_array(user_saved_filters) || frappe.utils.parse_array(this.filters) | |||
|| frappe.utils.parse_array(chart_saved_filters); | |||
}); | |||
} else { | |||
this.filters = | |||
frappe.utils.parse_array(user_saved_filters) || frappe.utils.parse_array(this.filters) | |||
frappe.utils.parse_array(user_saved_filters) || frappe.utils.parse_array(this.filters) | |||
|| frappe.utils.parse_array(chart_saved_filters); | |||
return Promise.resolve(); | |||
} | |||
@@ -993,7 +993,7 @@ jSignatureClass.prototype.resetCanvas = function(data, dontClear){ | |||
ctx.shadowBlur = 0; | |||
} | |||
} | |||
ctx.strokeStyle = settings.color; | |||
// setting up new dataEngine | |||
@@ -12,7 +12,7 @@ | |||
*/ | |||
/* | |||
1. Buttons | |||
*/ | |||
@@ -257,7 +257,7 @@ a.pswp__share--download:hover { | |||
padding: 0 10px; } | |||
/* | |||
4. Caption | |||
*/ | |||
@@ -338,8 +338,8 @@ a.pswp__share--download:hover { | |||
margin: 0; } | |||
.pswp--css_animation .pswp__preloader__cut { | |||
/* | |||
The idea of animating inner circle is based on Polymer ("material") loading indicator | |||
/* | |||
The idea of animating inner circle is based on Polymer ("material") loading indicator | |||
by Keanu Lee https://blog.keanulee.com/2014/10/20/the-tale-of-three-spinners.html | |||
*/ | |||
position: relative; | |||
@@ -409,7 +409,7 @@ a.pswp__share--download:hover { | |||
transform: rotate(0); } } | |||
/* | |||
6. Additional styles | |||
*/ | |||
@@ -5,9 +5,9 @@ | |||
* | |||
* UI on top of main sliding area (caption, arrows, close button, etc.). | |||
* Built just using public methods/properties of PhotoSwipe. | |||
* | |||
* | |||
*/ | |||
(function (root, factory) { | |||
(function (root, factory) { | |||
if (typeof define === 'function' && define.amd) { | |||
define(factory); | |||
} else if (typeof exports === 'object') { | |||
@@ -48,11 +48,11 @@ var PhotoSwipeUI_Default = | |||
_options, | |||
_defaultUIOptions = { | |||
barsSize: {top:44, bottom:'auto'}, | |||
closeElClasses: ['item', 'caption', 'zoom-wrap', 'ui', 'top-bar'], | |||
timeToIdle: 4000, | |||
closeElClasses: ['item', 'caption', 'zoom-wrap', 'ui', 'top-bar'], | |||
timeToIdle: 4000, | |||
timeToIdleOutside: 1000, | |||
loadingIndicatorDelay: 1000, // 2s | |||
addCaptionHTMLFn: function(item, captionEl /*, isFake */) { | |||
if(!item.title) { | |||
captionEl.children[0].innerHTML = ''; | |||
@@ -92,7 +92,7 @@ var PhotoSwipeUI_Default = | |||
getTextForShare: function( /* shareButtonData */ ) { | |||
return pswp.currItem.title || ''; | |||
}, | |||
indexIndicatorSep: ' / ', | |||
fitControlsWidth: 1200 | |||
@@ -136,12 +136,12 @@ var PhotoSwipeUI_Default = | |||
} | |||
_blockControlsTap = true; | |||
// Some versions of Android don't prevent ghost click event | |||
// Some versions of Android don't prevent ghost click event | |||
// when preventDefault() was called on touchstart and/or touchend. | |||
// | |||
// This happens on v4.3, 4.2, 4.1, | |||
// older versions strangely work correctly, | |||
// but just in case we add delay on all of them) | |||
// | |||
// This happens on v4.3, 4.2, 4.1, | |||
// older versions strangely work correctly, | |||
// but just in case we add delay on all of them) | |||
var tapDelay = framework.features.isOldAndroid ? 600 : 30; | |||
_blockControlsTapTimeout = setTimeout(function() { | |||
_blockControlsTap = false; | |||
@@ -172,8 +172,8 @@ var PhotoSwipeUI_Default = | |||
_toggleShareModal = function() { | |||
_shareModalHidden = !_shareModalHidden; | |||
if(!_shareModalHidden) { | |||
_toggleShareModalClass(); | |||
setTimeout(function() { | |||
@@ -189,7 +189,7 @@ var PhotoSwipeUI_Default = | |||
} | |||
}, 300); | |||
} | |||
if(!_shareModalHidden) { | |||
_updateShareURLs(); | |||
} | |||
@@ -211,13 +211,13 @@ var PhotoSwipeUI_Default = | |||
} | |||
window.open(target.href, 'pswp_share', 'scrollbars=yes,resizable=yes,toolbar=no,'+ | |||
'location=yes,width=550,height=420,top=100,left=' + | |||
'location=yes,width=550,height=420,top=100,left=' + | |||
(window.screen ? Math.round(screen.width / 2 - 275) : 100) ); | |||
if(!_shareModalHidden) { | |||
_toggleShareModal(); | |||
} | |||
return false; | |||
}, | |||
_updateShareURLs = function() { | |||
@@ -242,7 +242,7 @@ var PhotoSwipeUI_Default = | |||
shareButtonOut += '<a href="' + shareURL + '" target="_blank" '+ | |||
'class="pswp__share--' + shareButtonData.id + '"' + | |||
(shareButtonData.download ? 'download' : '') + '>' + | |||
(shareButtonData.download ? 'download' : '') + '>' + | |||
shareButtonData.label + '</a>'; | |||
if(_options.parseShareButtonOut) { | |||
@@ -297,7 +297,7 @@ var PhotoSwipeUI_Default = | |||
_setupLoadingIndicator = function() { | |||
// Setup loading indicator | |||
if(_options.preloaderEl) { | |||
_toggleLoadingIndicator(true); | |||
_listen('beforeChange', function() { | |||
@@ -310,18 +310,18 @@ var PhotoSwipeUI_Default = | |||
if(pswp.currItem && pswp.currItem.loading) { | |||
if( !pswp.allowProgressiveImg() || (pswp.currItem.img && !pswp.currItem.img.naturalWidth) ) { | |||
// show preloader if progressive loading is not enabled, | |||
// show preloader if progressive loading is not enabled, | |||
// or image width is not defined yet (because of slow connection) | |||
_toggleLoadingIndicator(false); | |||
_toggleLoadingIndicator(false); | |||
// items-controller.js function allowProgressiveImg | |||
} | |||
} else { | |||
_toggleLoadingIndicator(true); // hide preloader | |||
} | |||
}, _options.loadingIndicatorDelay); | |||
}); | |||
_listen('imageLoadComplete', function(index, item) { | |||
if(pswp.currItem === item) { | |||
@@ -341,8 +341,8 @@ var PhotoSwipeUI_Default = | |||
var gap = item.vGap; | |||
if( _fitControlsInViewport() ) { | |||
var bars = _options.barsSize; | |||
var bars = _options.barsSize; | |||
if(_options.captionEl && bars.bottom === 'auto') { | |||
if(!_fakeCaptionContainer) { | |||
_fakeCaptionContainer = framework.createEl('pswp__caption pswp__caption--fake'); | |||
@@ -360,7 +360,7 @@ var PhotoSwipeUI_Default = | |||
} else { | |||
gap.bottom = bars.bottom === 'auto' ? 0 : bars.bottom; | |||
} | |||
// height of top bar is static, no need to calculate it | |||
gap.top = bars.top; | |||
} else { | |||
@@ -371,7 +371,7 @@ var PhotoSwipeUI_Default = | |||
// Hide controls when mouse is used | |||
if(_options.timeToIdle) { | |||
_listen('mouseUsed', function() { | |||
framework.bind(document, 'mousemove', _onIdleMouseMove); | |||
framework.bind(document, 'mouseout', _onMouseLeaveWindow); | |||
@@ -418,77 +418,77 @@ var PhotoSwipeUI_Default = | |||
var _uiElements = [ | |||
{ | |||
name: 'caption', | |||
{ | |||
name: 'caption', | |||
option: 'captionEl', | |||
onInit: function(el) { | |||
_captionContainer = el; | |||
} | |||
onInit: function(el) { | |||
_captionContainer = el; | |||
} | |||
}, | |||
{ | |||
name: 'share-modal', | |||
{ | |||
name: 'share-modal', | |||
option: 'shareEl', | |||
onInit: function(el) { | |||
onInit: function(el) { | |||
_shareModal = el; | |||
}, | |||
onTap: function() { | |||
_toggleShareModal(); | |||
} | |||
} | |||
}, | |||
{ | |||
name: 'button--share', | |||
{ | |||
name: 'button--share', | |||
option: 'shareEl', | |||
onInit: function(el) { | |||
onInit: function(el) { | |||
_shareButton = el; | |||
}, | |||
onTap: function() { | |||
_toggleShareModal(); | |||
} | |||
} | |||
}, | |||
{ | |||
name: 'button--zoom', | |||
{ | |||
name: 'button--zoom', | |||
option: 'zoomEl', | |||
onTap: pswp.toggleDesktopZoom | |||
}, | |||
{ | |||
name: 'counter', | |||
{ | |||
name: 'counter', | |||
option: 'counterEl', | |||
onInit: function(el) { | |||
onInit: function(el) { | |||
_indexIndicator = el; | |||
} | |||
} | |||
}, | |||
{ | |||
name: 'button--close', | |||
{ | |||
name: 'button--close', | |||
option: 'closeEl', | |||
onTap: pswp.close | |||
}, | |||
{ | |||
name: 'button--arrow--left', | |||
{ | |||
name: 'button--arrow--left', | |||
option: 'arrowEl', | |||
onTap: pswp.prev | |||
}, | |||
{ | |||
name: 'button--arrow--right', | |||
{ | |||
name: 'button--arrow--right', | |||
option: 'arrowEl', | |||
onTap: pswp.next | |||
}, | |||
{ | |||
name: 'button--fs', | |||
{ | |||
name: 'button--fs', | |||
option: 'fullscreenEl', | |||
onTap: function() { | |||
onTap: function() { | |||
if(_fullscrenAPI.isFullscreen()) { | |||
_fullscrenAPI.exit(); | |||
} else { | |||
_fullscrenAPI.enter(); | |||
} | |||
} | |||
} | |||
}, | |||
{ | |||
name: 'preloader', | |||
{ | |||
name: 'preloader', | |||
option: 'preloaderEl', | |||
onInit: function(el) { | |||
onInit: function(el) { | |||
_loadingIndicator = el; | |||
} | |||
} | |||
} | |||
]; | |||
@@ -514,12 +514,12 @@ var PhotoSwipeUI_Default = | |||
if(classAttr.indexOf('pswp__' + uiElement.name) > -1 ) { | |||
if( _options[uiElement.option] ) { // if element is not disabled from options | |||
framework.removeClass(item, 'pswp__element--disabled'); | |||
if(uiElement.onInit) { | |||
uiElement.onInit(item); | |||
} | |||
//item.style.display = 'block'; | |||
} else { | |||
framework.addClass(item, 'pswp__element--disabled'); | |||
@@ -538,7 +538,7 @@ var PhotoSwipeUI_Default = | |||
}; | |||
ui.init = function() { | |||
@@ -574,9 +574,9 @@ var PhotoSwipeUI_Default = | |||
_listen('preventDragEvent', function(e, isDown, preventObj) { | |||
var t = e.target || e.srcElement; | |||
if( | |||
t && | |||
t.getAttribute('class') && e.type.indexOf('mouse') > -1 && | |||
( t.getAttribute('class').indexOf('__caption') > 0 || (/(SMALL|STRONG|EM)/i).test(t.tagName) ) | |||
t && | |||
t.getAttribute('class') && e.type.indexOf('mouse') > -1 && | |||
( t.getAttribute('class').indexOf('__caption') > 0 || (/(SMALL|STRONG|EM)/i).test(t.tagName) ) | |||
) { | |||
preventObj.prevent = false; | |||
} | |||
@@ -634,7 +634,7 @@ var PhotoSwipeUI_Default = | |||
framework.addClass( _controls, 'pswp__ui--hidden'); | |||
ui.setIdle(false); | |||
}); | |||
if(!_options.showAnimationDuration) { | |||
framework.removeClass( _controls, 'pswp__ui--hidden'); | |||
@@ -649,7 +649,7 @@ var PhotoSwipeUI_Default = | |||
}); | |||
_listen('parseVerticalMargin', _applyNavBarGaps); | |||
_setupUIElements(); | |||
if(_options.shareEl && _shareButton && _shareModal) { | |||
@@ -673,7 +673,7 @@ var PhotoSwipeUI_Default = | |||
ui.update = function() { | |||
// Don't update UI if it's hidden | |||
if(_controlsVisible && pswp.currItem) { | |||
ui.updateIndexIndicator(); | |||
if(_options.captionEl) { | |||
@@ -704,19 +704,19 @@ var PhotoSwipeUI_Default = | |||
pswp.setScrollOffset( 0, framework.getScrollY() ); | |||
}, 50); | |||
} | |||
// toogle pswp--fs class on root element | |||
framework[ (_fullscrenAPI.isFullscreen() ? 'add' : 'remove') + 'Class' ](pswp.template, 'pswp--fs'); | |||
}; | |||
ui.updateIndexIndicator = function() { | |||
if(_options.counterEl) { | |||
_indexIndicator.innerHTML = (pswp.getCurrentIndex()+1) + | |||
_options.indexIndicatorSep + | |||
_indexIndicator.innerHTML = (pswp.getCurrentIndex()+1) + | |||
_options.indexIndicatorSep + | |||
_options.getNumItemsFn(); | |||
} | |||
}; | |||
ui.onGlobalTap = function(e) { | |||
e = e || window.event; | |||
var target = e.target || e.srcElement; | |||
@@ -742,7 +742,7 @@ var PhotoSwipeUI_Default = | |||
pswp.toggleDesktopZoom(e.detail.releasePoint); | |||
} | |||
} | |||
} else { | |||
// tap anywhere (except buttons) to toggle visibility of controls | |||
@@ -759,7 +759,7 @@ var PhotoSwipeUI_Default = | |||
pswp.close(); | |||
return; | |||
} | |||
} | |||
}; | |||
ui.onMouseOver = function(e) { | |||
@@ -809,7 +809,7 @@ var PhotoSwipeUI_Default = | |||
eventK: 'moz' + tF | |||
}; | |||
} else if(dE.webkitRequestFullscreen) { | |||
api = { | |||
@@ -829,21 +829,21 @@ var PhotoSwipeUI_Default = | |||
} | |||
if(api) { | |||
api.enter = function() { | |||
api.enter = function() { | |||
// disable close-on-scroll in fullscreen | |||
_initalCloseOnScrollValue = _options.closeOnScroll; | |||
_options.closeOnScroll = false; | |||
_initalCloseOnScrollValue = _options.closeOnScroll; | |||
_options.closeOnScroll = false; | |||
if(this.enterK === 'webkitRequestFullscreen') { | |||
pswp.template[this.enterK]( Element.ALLOW_KEYBOARD_INPUT ); | |||
} else { | |||
return pswp.template[this.enterK](); | |||
return pswp.template[this.enterK](); | |||
} | |||
}; | |||
api.exit = function() { | |||
api.exit = function() { | |||
_options.closeOnScroll = _initalCloseOnScrollValue; | |||
return document[this.exitK](); | |||
return document[this.exitK](); | |||
}; | |||
api.isFullscreen = function() { return document[this.elementK]; }; | |||
@@ -11,10 +11,10 @@ function prettyDate(time){ | |||
var date = new Date((time || "").replace(/-/g,"/").replace(/[TZ]/g," ").replace(/\.[0-9]*/, "")), | |||
diff = (((new Date()).getTime() - date.getTime()) / 1000), | |||
day_diff = Math.floor(diff / 86400); | |||
if ( isNaN(day_diff) || day_diff < 0 ) | |||
return ''; | |||
return day_diff == 0 && ( | |||
diff < 60 && "just now" || | |||
diff < 120 && "1 minute ago" || | |||
@@ -12,6 +12,13 @@ | |||
border-bottom: 1px solid var(--table-border-color); | |||
color: var(--text-muted); | |||
font-size: var(--text-md); | |||
.grid-static-col { | |||
.static-area.reqd:after { | |||
content: ' *'; | |||
color: var(--red-400); | |||
} | |||
} | |||
} | |||
.rows .grid-row .data-row, | |||
@@ -54,7 +61,7 @@ | |||
} | |||
.form-grid .grid-heading-row .template-row { | |||
margin-left: 20px; | |||
margin-left: 8px; | |||
} | |||
.form-grid .template-row { | |||
@@ -88,6 +95,17 @@ | |||
margin-top: 2px; | |||
} | |||
.template-row-index { | |||
float: left; | |||
margin-left: 15px; | |||
margin-top: 8px; | |||
margin-right: -20px; | |||
span { | |||
margin-left: 5px; | |||
} | |||
} | |||
.editable-form .grid-static-col.bold { | |||
font-weight: bold; | |||
} | |||
@@ -1070,11 +1070,11 @@ body { | |||
} | |||
.resizer { | |||
width: 10px; | |||
width: 10px; | |||
height: 100%; | |||
position:absolute; | |||
right: 0; | |||
bottom: 0; | |||
position:absolute; | |||
right: 0; | |||
bottom: 0; | |||
cursor: col-resize; | |||
border-color: transparent; | |||
transition: border-color 0.3s ease-in-out; | |||
@@ -1089,8 +1089,8 @@ body { | |||
margin-bottom: 0 !important; | |||
flex: 1; | |||
&:focus { | |||
outline: none; | |||
&:focus { | |||
outline: none; | |||
} | |||
} | |||
@@ -1124,11 +1124,11 @@ body { | |||
color: var(--text-muted); | |||
border: 1px dashed var(--gray-400); | |||
cursor: pointer; | |||
.widget-control > * { | |||
width: auto; | |||
} | |||
.spacer-left { | |||
min-width: 74px; | |||
} | |||
@@ -1158,7 +1158,7 @@ body { | |||
gap: 5px; | |||
background-color: var(--card-bg); | |||
padding-left: 5px; | |||
.drag-handle { | |||
cursor: all-scroll; | |||
cursor: grabbing; | |||
@@ -1325,7 +1325,7 @@ body { | |||
padding: 6px 10px; | |||
font-size: small; | |||
border-radius: var(--border-radius-sm); | |||
margin: 1px 0px; | |||
margin: 1px 0px; | |||
} | |||
.dropdown-item-icon { | |||
@@ -202,7 +202,7 @@ $level-margin-right: 8px; | |||
box-shadow: none; | |||
margin-left: 0px !important; | |||
border: 1px solid var(--dark-border-color); | |||
&.btn-info { | |||
background-color: var(--gray-400); | |||
border-color: var(--gray-400); | |||
@@ -150,7 +150,7 @@ body { | |||
min-width: 50%; | |||
padding: 0 4px; | |||
margin-bottom: var(--margin-md); | |||
&:last-child { | |||
margin-bottom: 0; | |||
} | |||
@@ -163,18 +163,18 @@ | |||
padding: var(--padding-lg); | |||
box-shadow: var(--card-shadow); | |||
border-radius: var(--border-radius-md); | |||
.new-comment-fields { | |||
flex: 1; | |||
.form-label { | |||
font-weight: var(--text-bold); | |||
} | |||
.comment-text-area textarea { | |||
resize: none; | |||
} | |||
@media (min-width: 576px) { | |||
.comment-by { | |||
padding-right: 0px !important; | |||
@@ -184,7 +184,7 @@ | |||
} | |||
} | |||
} | |||
#comment-list { | |||
position: relative; | |||
@@ -206,7 +206,7 @@ | |||
top: 10px; | |||
left: -17px; | |||
} | |||
.comment-content { | |||
box-shadow: var(--card-shadow); | |||
border-radius: var(--border-radius-md); | |||
@@ -9,7 +9,7 @@ | |||
width: 80% | |||
} | |||
} | |||
.back-to-home { | |||
font-size: var(--text-base); | |||
} | |||
@@ -80,6 +80,8 @@ | |||
.dropdown-menu { | |||
padding: 0.25rem; | |||
box-shadow: var(--shadow-lg); | |||
border-color: var(--gray-200); | |||
} | |||
.dropdown-item { | |||
@@ -308,4 +310,4 @@ h5.modal-title { | |||
.empty-list-icon { | |||
height: 70px; | |||
} | |||
} |
@@ -46,7 +46,7 @@ | |||
.navbar-toggler { | |||
border-color: rgba(255,255,255, 0.1); | |||
.icon { | |||
stroke: none; | |||
} | |||
@@ -1,6 +1,6 @@ | |||
.portal-row { | |||
padding: 1rem 0; | |||
a { | |||
color: $body-color; | |||
} |
@@ -3,7 +3,7 @@ | |||
[data-doctype="Web Form"] { | |||
.page-content-wrapper { | |||
.breadcrumb-container.container { | |||
@include media-breakpoint-up(sm) { | |||
padding-left: 0; | |||
@@ -50,6 +50,10 @@ | |||
&:last-child { | |||
padding-right: 0; | |||
} | |||
@include media-breakpoint-down(sm) { | |||
padding: 0; | |||
} | |||
} | |||
} | |||
@@ -164,6 +164,7 @@ def get_alert_dict(doc): | |||
return alert_dict | |||
def create_energy_points_log(ref_doctype, ref_name, doc, apply_only_once=False): | |||
doc = frappe._dict(doc) | |||
@@ -171,7 +172,7 @@ def create_energy_points_log(ref_doctype, ref_name, doc, apply_only_once=False): | |||
ref_name, doc.rule, None if apply_only_once else doc.user) | |||
if log_exists: | |||
return | |||
return frappe.get_doc('Energy Point Log', log_exists) | |||
new_log = frappe.new_doc('Energy Point Log') | |||
new_log.reference_doctype = ref_doctype | |||
@@ -2,12 +2,14 @@ | |||
# Copyright (c) 2019, Frappe Technologies and Contributors | |||
# License: MIT. See LICENSE | |||
import frappe | |||
import unittest | |||
from frappe.tests.utils import FrappeTestCase | |||
from .energy_point_log import get_energy_points as _get_energy_points, create_review_points_log, review | |||
from frappe.utils.testutils import add_custom_field, clear_custom_fields | |||
from frappe.desk.form.assign_to import add as assign_to | |||
class TestEnergyPointLog(unittest.TestCase): | |||
class TestEnergyPointLog(FrappeTestCase): | |||
@classmethod | |||
def setUpClass(cls): | |||
settings = frappe.get_single('Energy Point Settings') | |||
@@ -140,9 +142,10 @@ class TestEnergyPointLog(unittest.TestCase): | |||
# for criticism | |||
criticism_points = 2 | |||
todo = create_a_todo(description='Bad patch') | |||
energy_points_before_review = energy_points_after_review | |||
review_points_before_review = review_points_after_review | |||
review(created_todo, criticism_points, 'test@example.com', 'You could have done better.', 'Criticism') | |||
review(todo, criticism_points, 'test@example.com', 'You could have done better.', 'Criticism') | |||
energy_points_after_review = get_points('test@example.com') | |||
review_points_after_review = get_points('test2@example.com', 'review_points') | |||
self.assertEqual(energy_points_after_review, energy_points_before_review - criticism_points) | |||
@@ -332,11 +335,14 @@ def create_energy_point_rule_for_todo(multiplier_field=None, for_doc_event='Cust | |||
'apply_only_once': apply_once | |||
}).insert(ignore_permissions=1) | |||
def create_a_todo(): | |||
def create_a_todo(description=None): | |||
if not description: | |||
description = 'Fix a bug' | |||
return frappe.get_doc({ | |||
'doctype': 'ToDo', | |||
'description': 'Fix a bug', | |||
}).insert() | |||
'description': description, | |||
}).insert(ignore_permissions=True) | |||
def get_points(user, point_type='energy_points'): | |||
@@ -38,6 +38,6 @@ | |||
like | |||
} | |||
}); | |||
} | |||
} | |||
}); | |||
</script> |
@@ -12,7 +12,11 @@ | |||
{# powered #} | |||
<div class="footer-col-right col-sm-6 col-12 footer-powered"> | |||
{% block powered %} | |||
{% include "templates/includes/footer/footer_powered.html" %} | |||
{%- if footer_powered -%} | |||
{{ footer_powered }} | |||
{%- else -%} | |||
{% include "templates/includes/footer/footer_powered.html" %} | |||
{%- endif -%} | |||
{% endblock %} | |||
</div> | |||
</div> | |||
@@ -95,8 +95,6 @@ | |||
min-width: 200px; | |||
padding: 0px; | |||
font-size: 85%; | |||
// only rounded bottoms | |||
border-radius: 0px 0px 4px 4px; | |||
} | |||
@@ -11,7 +11,7 @@ | |||
<p>{{ payment_message or _("Your payment was successfully accepted") }}</p> | |||
{% if not payment_message %} | |||
<div> | |||
<a | |||
<a | |||
href='{{ frappe.form_dict.redirect_to or "/" }}' | |||
class='btn btn-primary btn-sm'> | |||
{{ _("Continue") }} | |||
@@ -97,6 +97,12 @@ class TestReportview(unittest.TestCase): | |||
self.assertFalse(result | |||
in DatabaseQuery("DocType").execute(filters={"name": ["not in", 'DocType,DocField']})) | |||
def test_none_filter(self): | |||
query = frappe.db.query.get_sql("DocType", fields="name", filters={"restrict_to_domain": None}) | |||
sql = str(query).replace('`', '').replace('"', '') | |||
condition = 'restrict_to_domain IS NULL' | |||
self.assertIn(condition, sql) | |||
def test_or_filters(self): | |||
data = DatabaseQuery("DocField").execute( | |||
filters={"parent": "DocType"}, fields=["fieldname", "fieldtype"], | |||
@@ -149,7 +155,6 @@ class TestReportview(unittest.TestCase): | |||
filters={"creation": ["between", ["2016-07-06", "2016-07-07"]]}, | |||
fields=["name"]) | |||
def test_ignore_permissions_for_get_filters_cond(self): | |||
frappe.set_user('test2@example.com') | |||
self.assertRaises(frappe.PermissionError, get_filters_cond, 'DocType', dict(istable=1), []) | |||
@@ -351,7 +356,6 @@ class TestReportview(unittest.TestCase): | |||
self.assertTrue(len(data) == 0) | |||
self.assertTrue(len(frappe.get_all('Nested DocType', {'name': ('not ancestors of', 'Root')})) == len(frappe.get_all('Nested DocType'))) | |||
def test_is_set_is_not_set(self): | |||
res = DatabaseQuery('DocType').execute(filters={'autoname': ['is', 'not set']}) | |||
self.assertTrue({'name': 'Integration Request'} in res) | |||
@@ -319,3 +319,21 @@ class TestDocument(unittest.TestCase): | |||
self.assertIsInstance(doc, Note) | |||
self.assertIsInstance(doc.as_dict().get("age"), timedelta) | |||
self.assertIsInstance(doc.get_valid_dict().get("age"), timedelta) | |||
def test_run_method(self): | |||
doc = frappe.get_last_doc("User") | |||
# Case 1: Override with a string | |||
doc.as_dict = "" | |||
# run_method should throw TypeError | |||
self.assertRaisesRegex(TypeError, "not callable", doc.run_method, "as_dict") | |||
# Case 2: Override with a function | |||
def my_as_dict(*args, **kwargs): | |||
return "success" | |||
doc.as_dict = my_as_dict | |||
# run_method should get overridden | |||
self.assertEqual(doc.run_method("as_dict"), "success") |
@@ -4,7 +4,6 @@ | |||
import frappe | |||
import frappe.defaults | |||
import unittest | |||
import frappe.model.meta | |||
from frappe.permissions import (add_user_permission, remove_user_permission, | |||
clear_user_permissions_for_doctype, get_doc_permissions, add_permission, update_permission_property) | |||
@@ -14,9 +13,12 @@ from frappe.core.doctype.user_permission.user_permission import clear_user_permi | |||
from frappe.desk.form.load import getdoc | |||
from frappe.utils.data import now_datetime | |||
from frappe.tests.utils import FrappeTestCase | |||
test_dependencies = ['Blogger', 'Blog Post', "User", "Contact", "Salutation"] | |||
class TestPermissions(unittest.TestCase): | |||
class TestPermissions(FrappeTestCase): | |||
def setUp(self): | |||
frappe.clear_cache(doctype="Blog Post") | |||
@@ -221,7 +223,7 @@ class TestPermissions(unittest.TestCase): | |||
# check that Document.owner cannot be changed | |||
user.reload() | |||
user.owner = frappe.db.get_value("User", {"name": ("!=", user.name)}) | |||
user.owner = "Guest" | |||
self.assertRaises(frappe.CannotChangeConstantError, user.save) | |||
def test_set_only_once(self): | |||
@@ -557,7 +559,6 @@ class TestPermissions(unittest.TestCase): | |||
# Remove delete perm | |||
update('Blog Post', 'Website Manager', 0, 'delete', 0) | |||
frappe.clear_cache(doctype="Blog Post") | |||
frappe.set_user("test2@example.com") | |||
@@ -510,5 +510,3 @@ class TestLinkTitle(unittest.TestCase): | |||
todo.delete() | |||
user.delete() | |||
prop_setter.delete() | |||
@@ -0,0 +1,76 @@ | |||
import copy | |||
import signal | |||
import unittest | |||
from contextlib import contextmanager | |||
import frappe | |||
class FrappeTestCase(unittest.TestCase): | |||
"""Base test class for Frappe tests.""" | |||
@classmethod | |||
def setUpClass(cls) -> None: | |||
frappe.db.commit() | |||
return super().setUpClass() | |||
@classmethod | |||
def tearDownClass(cls) -> None: | |||
frappe.db.rollback() | |||
return super().tearDownClass() | |||
@contextmanager | |||
def change_settings(doctype, settings_dict): | |||
""" A context manager to ensure that settings are changed before running | |||
function and restored after running it regardless of exceptions occured. | |||
This is useful in tests where you want to make changes in a function but | |||
don't retain those changes. | |||
import and use as decorator to cover full function or using `with` statement. | |||
example: | |||
@change_settings("Print Settings", {"send_print_as_pdf": 1}) | |||
def test_case(self): | |||
... | |||
""" | |||
try: | |||
settings = frappe.get_doc(doctype) | |||
# remember setting | |||
previous_settings = copy.deepcopy(settings_dict) | |||
for key in previous_settings: | |||
previous_settings[key] = getattr(settings, key) | |||
# change setting | |||
for key, value in settings_dict.items(): | |||
setattr(settings, key, value) | |||
settings.save() | |||
# singles are cached by default, clear to avoid flake | |||
frappe.db.value_cache[settings] = {} | |||
yield # yield control to calling function | |||
finally: | |||
# restore settings | |||
settings = frappe.get_doc(doctype) | |||
for key, value in previous_settings.items(): | |||
setattr(settings, key, value) | |||
settings.save() | |||
def timeout(seconds=30, error_message="Test timed out."): | |||
""" Timeout decorator to ensure a test doesn't run for too long. | |||
adapted from https://stackoverflow.com/a/2282656""" | |||
def decorator(func): | |||
def _handle_timeout(signum, frame): | |||
raise Exception(error_message) | |||
def wrapper(*args, **kwargs): | |||
signal.signal(signal.SIGALRM, _handle_timeout) | |||
signal.alarm(seconds) | |||
try: | |||
result = func(*args, **kwargs) | |||
finally: | |||
signal.alarm(0) | |||
return result | |||
return wrapper | |||
return decorator |
@@ -35,7 +35,7 @@ def get_random(doctype, filters=None, doc=False): | |||
condition = " where " + " and ".join(condition) | |||
else: | |||
condition = "" | |||
out = frappe.db.multisql({ | |||
'mariadb': """select name from `tab%s` %s | |||
order by RAND() limit 1 offset 0""" % (doctype, condition), | |||
@@ -42,6 +42,7 @@ | |||
"copyright", | |||
"address", | |||
"footer_items", | |||
"footer_powered", | |||
"footer_template", | |||
"footer_template_values", | |||
"edit_footer_template_values", | |||
@@ -142,7 +143,6 @@ | |||
}, | |||
{ | |||
"collapsible": 1, | |||
"collapsible_depends_on": "top_bar_items", | |||
"fieldname": "top_bar", | |||
"fieldtype": "Section Break", | |||
"label": "Navbar" | |||
@@ -189,7 +189,8 @@ | |||
"description": "Address and other legal information you may want to put in the footer.", | |||
"fieldname": "address", | |||
"fieldtype": "Text Editor", | |||
"label": "Address" | |||
"label": "Address", | |||
"max_height": "8rem" | |||
}, | |||
{ | |||
"fieldname": "footer_items", | |||
@@ -391,6 +392,7 @@ | |||
"label": "App Logo" | |||
}, | |||
{ | |||
"collapsible": 1, | |||
"fieldname": "account_deletion_settings_section", | |||
"fieldtype": "Section Break", | |||
"label": "Account Deletion Settings" | |||
@@ -406,6 +408,11 @@ | |||
"fieldname": "auto_account_deletion", | |||
"fieldtype": "Int", | |||
"label": "Auto Account Deletion within (Days)" | |||
}, | |||
{ | |||
"fieldname": "footer_powered", | |||
"fieldtype": "Small Text", | |||
"label": "Footer \"Powered By\"" | |||
} | |||
], | |||
"icon": "fa fa-cog", | |||
@@ -414,7 +421,7 @@ | |||
"issingle": 1, | |||
"links": [], | |||
"max_attachments": 10, | |||
"modified": "2021-12-15 17:28:59.255184", | |||
"modified": "2022-02-28 23:05:42.493192", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Website Settings", | |||
@@ -437,5 +444,6 @@ | |||
], | |||
"sort_field": "modified", | |||
"sort_order": "ASC", | |||
"states": [], | |||
"track_changes": 1 | |||
} |
@@ -120,7 +120,8 @@ def get_website_settings(context=None): | |||
"facebook_share", "google_plus_one", "twitter_share", "linked_in_share", | |||
"disable_signup", "hide_footer_signup", "head_html", "title_prefix", | |||
"navbar_template", "footer_template", "navbar_search", "enable_view_tracking", | |||
"footer_logo", "call_to_action", "call_to_action_url", "show_language_picker"]: | |||
"footer_logo", "call_to_action", "call_to_action_url", "show_language_picker", | |||
"footer_powered"]: | |||
if hasattr(settings, k): | |||
context[k] = settings.get(k) | |||
@@ -22,7 +22,7 @@ | |||
add_top_padding=1, | |||
add_bottom_padding=1, | |||
) }} | |||
{% if doc.get({"doctype":"Company History"}) %} | |||
<section class="section section-padding-bottom"> | |||
@@ -87,7 +87,7 @@ | |||
{% if item.target %}target="{{ item.target }}"{% endif %}> | |||
{{ _(item.title or item.label) }} | |||
</a> | |||
{%- endfor %} | |||
{%- endfor %} | |||
</ul> | |||
</div> | |||
</div> |
@@ -10,6 +10,6 @@ no_cache = 1 | |||
def get_context(context): | |||
if frappe.session.user=='Guest': | |||
frappe.throw(_("You need to be logged in to access this page"), frappe.PermissionError) | |||
context.current_user = frappe.get_doc("User", frappe.session.user) | |||
context.show_sidebar=True |
@@ -53,8 +53,8 @@ | |||
{% endfor %} | |||
{% else %} | |||
<div class="empty-apps-state"> | |||
<img src="/assets/frappe/images/ui-states/empty-app-state.svg"/> | |||
<div class="font-weight-bold mt-4"> | |||
<img src="/assets/frappe/images/ui-states/empty-app-state.svg"/> | |||
<div class="font-weight-bold mt-4"> | |||
{{ _("No Active Sessions")}} | |||
</div> | |||
<div class="text-muted mt-2"> | |||