diff --git a/cypress/integration/form_tour.js b/cypress/integration/form_tour.js
index 0a9a5e8020..507a07ab1a 100644
--- a/cypress/integration/form_tour.js
+++ b/cypress/integration/form_tour.js
@@ -1,5 +1,5 @@
-context('Form Tour', () => {
- before.skip(() => {
+context.skip('Form Tour', () => {
+ before(() => {
cy.login();
cy.visit('/app');
return cy.window().its('frappe').then(frappe => {
diff --git a/frappe/__init__.py b/frappe/__init__.py
index d54686304f..8bd7783283 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -232,7 +232,6 @@ def init(site, sites_path=None, new_site=False):
local.cache = {}
local.document_cache = {}
local.meta_cache = {}
- local.autoincremented_status_map = {site: -1}
local.form_dict = _dict()
local.session = _dict()
local.dev_server = _dev_server
@@ -1926,7 +1925,7 @@ def attach_print(
if not file_name:
file_name = name
- file_name = file_name.replace(" ", "").replace("/", "-")
+ file_name = cstr(file_name).replace(" ", "").replace("/", "-")
print_settings = db.get_singles_dict("Print Settings")
diff --git a/frappe/core/doctype/access_log/access_log.json b/frappe/core/doctype/access_log/access_log.json
index 6a3028303b..c5f1030266 100644
--- a/frappe/core/doctype/access_log/access_log.json
+++ b/frappe/core/doctype/access_log/access_log.json
@@ -1,5 +1,6 @@
{
- "autoname": "format:AL-{#####}",
+ "actions": [],
+ "autoname": "hash",
"creation": "2019-07-25 15:44:44.955496",
"doctype": "DocType",
"editable_grid": 1,
@@ -127,10 +128,12 @@
"read_only": 1
}
],
- "modified": "2019-08-05 19:00:13.839471",
+ "links": [],
+ "modified": "2022-05-03 09:34:19.337551",
"modified_by": "Administrator",
"module": "Core",
"name": "Access Log",
+ "naming_rule": "Random",
"owner": "Administrator",
"permissions": [
{
@@ -146,5 +149,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_seen": 1
}
\ No newline at end of file
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index 2bb971bac8..6bd7f2306f 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -92,10 +92,10 @@ class DocType(Document):
self.check_developer_mode()
- self.validate_autoname()
self.validate_name()
self.set_defaults_for_single_and_table()
+ self.set_defaults_for_autoincremented()
self.scrub_field_names()
self.set_default_in_list_view()
self.set_default_translatable()
@@ -124,6 +124,12 @@ class DocType(Document):
if self.default_print_format and not self.custom:
frappe.throw(_("Standard DocType cannot have default print format, use Customize Form"))
+ if check_if_can_change_name_type(self):
+ change_name_column_type(
+ self.name,
+ "bigint" if self.autoname == "autoincrement" else f"varchar({frappe.db.VARCHAR_LEN})",
+ )
+
def validate_field_name_conflicts(self):
"""Check if field names dont conflict with controller properties and methods"""
core_doctypes = [
@@ -184,6 +190,10 @@ class DocType(Document):
self.allow_import = 0
self.permissions = []
+ def set_defaults_for_autoincremented(self):
+ if self.autoname and self.autoname == "autoincrement":
+ self.allow_rename = 0
+
def set_default_in_list_view(self):
"""Set default in-list-view for first 4 mandatory fields"""
if not [d.fieldname for d in self.fields if d.in_list_view]:
@@ -809,17 +819,6 @@ class DocType(Document):
max_idx = frappe.db.sql("""select max(idx) from `tabDocField` where parent = %s""", self.name)
return max_idx and max_idx[0][0] or 0
- def validate_autoname(self):
- if not self.is_new():
- doc_before_save = self.get_doc_before_save()
- if doc_before_save:
- if (self.autoname == "autoincrement" and doc_before_save.autoname != "autoincrement") or (
- self.autoname != "autoincrement" and doc_before_save.autoname == "autoincrement"
- ):
- frappe.throw(_("Cannot change to/from Autoincrement naming rule"))
- if self.autoname == "autoincrement":
- self.allow_rename = 0
-
def validate_name(self, name=None):
if not name:
name = self.name
@@ -887,7 +886,7 @@ def validate_series(dt, autoname=None, name=None):
autoname
and (not autoname.startswith("field:"))
and (not autoname.startswith("eval:"))
- and (not autoname.lower() in ("prompt", "hash"))
+ and (autoname.lower() not in ("prompt", "hash"))
and (not autoname.startswith("naming_series:"))
and (not autoname.startswith("format:"))
):
@@ -904,6 +903,51 @@ def validate_series(dt, autoname=None, name=None):
frappe.throw(_("Series {0} already used in {1}").format(prefix, used_in[0][0]))
+def check_if_can_change_name_type(dt: DocType, raise_err: bool = True) -> bool:
+ def get_autoname_before_save(doctype: str, to_be_customized_dt: str) -> str:
+ if doctype == "Customize Form":
+ property_value = frappe.db.get_value(
+ "Property Setter", {"doc_type": to_be_customized_dt, "property": "autoname"}, "value"
+ )
+
+ # initially no property setter is set,
+ # hence getting autoname value from the doctype itself
+ if not property_value:
+ return frappe.db.get_value("DocType", to_be_customized_dt, "autoname") or ""
+
+ return property_value
+
+ return getattr(dt.get_doc_before_save(), "autoname", "")
+
+ doctype_name = dt.doc_type if dt.doctype == "Customize Form" else dt.name
+
+ if not dt.is_new():
+ autoname_before_save = get_autoname_before_save(dt.doctype, doctype_name)
+ is_autoname_autoincrement = dt.autoname == "autoincrement"
+
+ if (
+ is_autoname_autoincrement
+ and autoname_before_save != "autoincrement"
+ or (not is_autoname_autoincrement and autoname_before_save == "autoincrement")
+ ):
+ if not frappe.get_all(doctype_name, limit=1):
+ # allow changing the column type if there is no data
+ return True
+
+ if raise_err:
+ frappe.throw(
+ _("Can only change to/from Autoincrement naming rule when there is no data in the doctype")
+ )
+
+ return False
+
+
+def change_name_column_type(doctype_name: str, type: str) -> None:
+ return frappe.db.change_column_type(
+ doctype_name, "name", type, True if frappe.db.db_type == "mariadb" else False
+ )
+
+
def validate_links_table_fieldnames(meta):
"""Validate fieldnames in Links table"""
if not meta.links or frappe.flags.in_patch or frappe.flags.in_fixtures:
@@ -974,12 +1018,13 @@ def validate_fields(meta):
validate_column_name(fieldname)
def check_invalid_fieldnames(docname, fieldname):
- invalid_fields = ("doctype",)
- if fieldname in invalid_fields:
+ if fieldname in Document._reserved_keywords:
frappe.throw(
- _("{0}: Fieldname cannot be one of {1}").format(
- docname, ", ".join(frappe.bold(d) for d in invalid_fields)
- )
+ _("{0}: fieldname cannot be set to reserved keyword {1}").format(
+ frappe.bold(docname),
+ frappe.bold(fieldname),
+ ),
+ title=_("Invalid Fieldname"),
)
def check_unique_fieldname(docname, fieldname):
diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py
index 7b4806da59..59475a95a7 100644
--- a/frappe/core/doctype/doctype/test_doctype.py
+++ b/frappe/core/doctype/doctype/test_doctype.py
@@ -524,18 +524,33 @@ class TestDocType(unittest.TestCase):
dt.delete()
def test_autoincremented_doctype_transition(self):
- frappe.delete_doc("testy_autoinc_dt")
+ frappe.delete_doc_if_exists("DocType", "testy_autoinc_dt")
dt = new_doctype("testy_autoinc_dt", autoname="autoincrement").insert(ignore_permissions=True)
dt.autoname = "hash"
+ dt.save(ignore_permissions=True)
+
+ dt_data = frappe.get_doc({"doctype": dt.name, "some_fieldname": "test data"}).insert(
+ ignore_permissions=True
+ )
+
+ dt.autoname = "autoincrement"
+
try:
dt.save(ignore_permissions=True)
except frappe.ValidationError as e:
- self.assertEqual(e.args[0], "Cannot change to/from Autoincrement naming rule")
+ self.assertEqual(
+ e.args[0],
+ "Can only change to/from Autoincrement naming rule when there is no data in the doctype",
+ )
else:
- self.fail("Shouldnt be possible to transition autoincremented doctype to any other naming rule")
+ self.fail(
+ """Shouldn't be possible to transition to/from autoincremented doctype
+ when data is present in doctype"""
+ )
finally:
# cleanup
+ dt_data.delete(ignore_permissions=True)
dt.delete(ignore_permissions=True)
def test_json_field(self):
diff --git a/frappe/core/doctype/user/test_records.json b/frappe/core/doctype/user/test_records.json
index 21fe3ff69d..9d1bf0efd4 100644
--- a/frappe/core/doctype/user/test_records.json
+++ b/frappe/core/doctype/user/test_records.json
@@ -45,6 +45,13 @@
"new_password": "Eastern_43A1W",
"enabled": 1
},
+ {
+ "doctype": "User",
+ "email": "test'5@example.com",
+ "first_name": "_Test'5",
+ "new_password": "Eastern_43A1W",
+ "enabled": 1
+ },
{
"doctype": "User",
"email": "testperm@example.com",
diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json
index 6563cae94c..a0bc994c45 100644
--- a/frappe/custom/doctype/customize_form/customize_form.json
+++ b/frappe/custom/doctype/customize_form/customize_form.json
@@ -287,7 +287,7 @@
"label": "Naming"
},
{
- "description": "Naming Options:\n
field:[fieldname] - By Field
naming_series: - By Naming Series (field called naming_series must be present)
Prompt - Prompt user for a name
[series] - Series by prefix (separated by a dot); for example PRE.#####
\n
format:EXAMPLE-{MM}morewords{fieldname1}-{fieldname2}-{#####} - Replace all braced words (fieldnames, date words (DD, MM, YY), series) with their value. Outside braces, any characters can be used.
",
+ "description": "Naming Options:\n
field:[fieldname] - By Field
\n
autoincrement - Uses Databases' Auto Increment feature
naming_series: - By Naming Series (field called naming_series must be present
Prompt - Prompt user for a name
[series] - Series by prefix (separated by a dot); for example PRE.#####
\n
format:EXAMPLE-{MM}morewords{fieldname1}-{fieldname2}-{#####} - Replace all braced words (fieldnames, date words (DD, MM, YY), series) with their value. Outside braces, any characters can be used.
",
"fieldname": "autoname",
"fieldtype": "Data",
"label": "Auto Name"
@@ -319,7 +319,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2022-01-07 16:07:06.196534",
+ "modified": "2022-04-21 15:36:16.772277",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form",
diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py
index e7187db0d6..262542fd4b 100644
--- a/frappe/custom/doctype/customize_form/customize_form.py
+++ b/frappe/custom/doctype/customize_form/customize_form.py
@@ -11,7 +11,9 @@ import frappe
import frappe.translate
from frappe import _
from frappe.core.doctype.doctype.doctype import (
+ change_name_column_type,
check_email_append_to,
+ check_if_can_change_name_type,
validate_fields_for_doctype,
validate_series,
)
@@ -159,7 +161,9 @@ class CustomizeForm(Document):
def save_customization(self):
if not self.doc_type:
return
+
validate_series(self, self.autoname, self.doc_type)
+ can_change_name_type = check_if_can_change_name_type(self)
self.flags.update_db = False
self.flags.rebuild_doctype_for_global_search = False
self.set_property_setters()
@@ -168,6 +172,12 @@ class CustomizeForm(Document):
validate_fields_for_doctype(self.doc_type)
check_email_append_to(self)
+ if can_change_name_type:
+ change_name_column_type(
+ self.doc_type,
+ "bigint" if self.autoname == "autoincrement" else f"varchar({frappe.db.VARCHAR_LEN})",
+ )
+
if self.flags.update_db:
frappe.db.updatedb(self.doc_type)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index fb631951fa..ca4b5a5310 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -1246,6 +1246,21 @@ class Database(object):
values_to_insert = values[start_index : start_index + chunk_size]
query.columns(fields).insert(*values_to_insert).run()
+ def create_sequence(self, *args, **kwargs):
+ from frappe.database.sequence import create_sequence
+
+ return create_sequence(*args, **kwargs)
+
+ def set_next_sequence_val(self, *args, **kwargs):
+ from frappe.database.sequence import set_next_val
+
+ set_next_val(*args, **kwargs)
+
+ def get_next_sequence_val(self, *args, **kwargs):
+ from frappe.database.sequence import get_next_val
+
+ return get_next_val(*args, **kwargs)
+
def enqueue_jobs_after_commit():
from frappe.utils.background_jobs import execute_job, get_queue
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 0f5410a403..28d78471d2 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -149,7 +149,7 @@ class MariaDBDatabase(Database):
) -> Union[List, Tuple]:
table_name = get_table_name(doctype)
null_constraint = "NOT NULL" if not nullable else ""
- return self.sql(f"ALTER TABLE `{table_name}` MODIFY `{column}` {type} {null_constraint}")
+ return self.sql_ddl(f"ALTER TABLE `{table_name}` MODIFY `{column}` {type} {null_constraint}")
# exception types
@staticmethod
diff --git a/frappe/database/mariadb/schema.py b/frappe/database/mariadb/schema.py
index 7c95e9ffcb..784fa23c13 100644
--- a/frappe/database/mariadb/schema.py
+++ b/frappe/database/mariadb/schema.py
@@ -1,7 +1,6 @@
import frappe
from frappe import _
from frappe.database.schema import DBTable
-from frappe.database.sequence import create_sequence
from frappe.model import log_types
@@ -48,7 +47,7 @@ class MariaDBTable(DBTable):
# By default the cache is 1000 which will mess up the sequence when
# using the system after a restore.
# issue link: https://jira.mariadb.org/browse/MDEV-21786
- create_sequence(self.doctype, check_not_exists=True, cache=50)
+ frappe.db.create_sequence(self.doctype, check_not_exists=True, cache=50)
# NOTE: not used nextval func as default as the ability to restore
# database with sequences has bugs in mariadb and gives a scary error.
diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py
index 4cd6ab9873..d69e0bea94 100644
--- a/frappe/database/postgres/database.py
+++ b/frappe/database/postgres/database.py
@@ -213,7 +213,11 @@ class PostgresDatabase(Database):
) -> Union[List, Tuple]:
table_name = get_table_name(doctype)
null_constraint = "SET NOT NULL" if not nullable else "DROP NOT NULL"
- return self.sql(
+
+ # postgres allows ddl in transactions but since we've currently made
+ # things same as mariadb (raising exception on ddl commands if the transaction has any writes),
+ # hence using sql_ddl here for committing and then moving forward.
+ return self.sql_ddl(
f"""ALTER TABLE "{table_name}"
ALTER COLUMN "{column}" TYPE {type},
ALTER COLUMN "{column}" {null_constraint}"""
@@ -382,12 +386,10 @@ def modify_query(query):
# drop .0 from decimals and add quotes around them
#
# >>> query = "c='abcd' , a >= 45, b = -45.0, c = 40, d=4500.0, e=3500.53, f=40psdfsd, g=9092094312, h=12.00023"
- # >>> re.sub(r"([=><]+)\s*(?!\d+[a-zA-Z])(?![+-]?\d+\.\d\d+)([+-]?\d+)(\.0)?", r"\1 '\2'", query)
+ # >>> re.sub(r"([=><]+)\s*([+-]?\d+)(\.0)?(?![a-zA-Z\.\d])", r"\1 '\2'", query)
# "c='abcd' , a >= '45', b = '-45', c = '40', d= '4500', e=3500.53, f=40psdfsd, g= '9092094312', h=12.00023
- query = re.sub(
- r"([=><]+)\s*(?!\d+[a-zA-Z])(?![+-]?\d+\.\d\d+)([+-]?\d+)(\.0)?", r"\1 '\2'", query
- )
+ query = re.sub(r"([=><]+)\s*([+-]?\d+)(\.0)?(?![a-zA-Z\.\d])", r"\1 '\2'", query)
return query
diff --git a/frappe/database/postgres/schema.py b/frappe/database/postgres/schema.py
index 3432c8b548..2abd5f37c7 100644
--- a/frappe/database/postgres/schema.py
+++ b/frappe/database/postgres/schema.py
@@ -1,7 +1,6 @@
import frappe
from frappe import _
from frappe.database.schema import DBTable, get_definition
-from frappe.database.sequence import create_sequence
from frappe.model import log_types
from frappe.utils import cint, flt
@@ -39,7 +38,7 @@ class PostgresTable(DBTable):
# Since we're opening and closing connections for every transaction this results in skipping the cache
# to the next non-cached value hence not using cache in postgres.
# ref: https://stackoverflow.com/questions/21356375/postgres-9-0-4-sequence-skipping-numbers
- create_sequence(self.doctype, check_not_exists=True)
+ frappe.db.create_sequence(self.doctype, check_not_exists=True)
name_column = "name bigint primary key"
# TODO: set docstatus length
diff --git a/frappe/database/sequence.py b/frappe/database/sequence.py
index c4789dbdaf..ede4689485 100644
--- a/frappe/database/sequence.py
+++ b/frappe/database/sequence.py
@@ -5,6 +5,7 @@ def create_sequence(
doctype_name: str,
*,
slug: str = "_id_seq",
+ temporary=False,
check_not_exists: bool = False,
cycle: bool = False,
cache: int = 0,
@@ -14,7 +15,7 @@ def create_sequence(
max_value: int = 0,
) -> str:
- query = "create sequence"
+ query = "create sequence" if not temporary else "create temporary sequence"
sequence_name = scrub(doctype_name + slug)
if check_not_exists:
@@ -22,29 +23,29 @@ def create_sequence(
query += f" {sequence_name}"
- if cache:
- query += f" cache {cache}"
- else:
- # in postgres, the default is cache 1
- if db.db_type == "mariadb":
- query += " nocache"
-
- if start_value:
- # default is 1
- query += f" start with {start_value}"
-
if increment_by:
# default is 1
query += f" increment by {increment_by}"
if min_value:
# default is 1
- query += f" min value {min_value}"
+ query += f" minvalue {min_value}"
if max_value:
- query += f" max value {max_value}"
+ query += f" maxvalue {max_value}"
+
+ if start_value:
+ # default is 1
+ query += f" start {start_value}"
+
+ # in postgres, the default is cache 1 / no cache
+ if cache:
+ query += f" cache {cache}"
+ elif db.db_type == "mariadb":
+ query += " nocache"
if not cycle:
+ # in postgres, default is no cycle
if db.db_type == "mariadb":
query += " nocycle"
else:
@@ -56,21 +57,23 @@ def create_sequence(
def get_next_val(doctype_name: str, slug: str = "_id_seq") -> int:
- if db.db_type == "postgres":
- return db.sql(f"select nextval('\"{scrub(doctype_name + slug)}\"')")[0][0]
- return db.sql(f"select nextval(`{scrub(doctype_name + slug)}`)")[0][0]
+ return db.multisql(
+ {
+ "postgres": f"select nextval('\"{scrub(doctype_name + slug)}\"')",
+ "mariadb": f"select nextval(`{scrub(doctype_name + slug)}`)",
+ }
+ )[0][0]
def set_next_val(
doctype_name: str, next_val: int, *, slug: str = "_id_seq", is_val_used: bool = False
) -> None:
- if not is_val_used:
- is_val_used = 0 if db.db_type == "mariadb" else "f"
- else:
- is_val_used = 1 if db.db_type == "mariadb" else "t"
+ is_val_used = "false" if not is_val_used else "true"
- if db.db_type == "postgres":
- db.sql(f"SELECT SETVAL('\"{scrub(doctype_name + slug)}\"', {next_val}, '{is_val_used}')")
- else:
- db.sql(f"SELECT SETVAL(`{scrub(doctype_name + slug)}`, {next_val}, {is_val_used})")
+ db.multisql(
+ {
+ "postgres": f"SELECT SETVAL('\"{scrub(doctype_name + slug)}\"', {next_val}, {is_val_used})",
+ "mariadb": f"SELECT SETVAL(`{scrub(doctype_name + slug)}`, {next_val}, {is_val_used})",
+ }
+ )
diff --git a/frappe/deferred_insert.py b/frappe/deferred_insert.py
index 28c77002f8..3b47d46cdf 100644
--- a/frappe/deferred_insert.py
+++ b/frappe/deferred_insert.py
@@ -42,12 +42,10 @@ def save_to_db():
record_count += 1
insert_record(record, doctype)
- frappe.db.commit()
-
def insert_record(record: Union[Dict, "Document"], doctype: str):
- setattr(record, "doctype", doctype)
try:
+ record.update({"doctype": doctype})
frappe.get_doc(record).insert()
except Exception as e:
frappe.logger().error(f"Error while inserting deferred {doctype} record: {e}")
diff --git a/frappe/desk/doctype/dashboard_settings/dashboard_settings.py b/frappe/desk/doctype/dashboard_settings/dashboard_settings.py
index d43476ad7d..01c3a87f20 100644
--- a/frappe/desk/doctype/dashboard_settings/dashboard_settings.py
+++ b/frappe/desk/doctype/dashboard_settings/dashboard_settings.py
@@ -28,7 +28,7 @@ def get_permission_query_conditions(user):
if not user:
user = frappe.session.user
- return """(`tabDashboard Settings`.name = '{user}')""".format(user=user)
+ return """(`tabDashboard Settings`.name = {user})""".format(user=frappe.db.escape(user))
@frappe.whitelist()
diff --git a/frappe/desk/doctype/kanban_board/kanban_board.py b/frappe/desk/doctype/kanban_board/kanban_board.py
index 381f71438c..bc47bbbaf4 100644
--- a/frappe/desk/doctype/kanban_board/kanban_board.py
+++ b/frappe/desk/doctype/kanban_board/kanban_board.py
@@ -34,7 +34,9 @@ def get_permission_query_conditions(user):
if user == "Administrator":
return ""
- return """(`tabKanban Board`.private=0 or `tabKanban Board`.owner='{user}')""".format(user=user)
+ return """(`tabKanban Board`.private=0 or `tabKanban Board`.owner={user})""".format(
+ user=frappe.db.escape(user)
+ )
def has_permission(doc, ptype, user):
diff --git a/frappe/desk/doctype/note/note.py b/frappe/desk/doctype/note/note.py
index de019d9898..d67ecda594 100644
--- a/frappe/desk/doctype/note/note.py
+++ b/frappe/desk/doctype/note/note.py
@@ -38,7 +38,7 @@ def get_permission_query_conditions(user):
if user == "Administrator":
return ""
- return """(`tabNote`.public=1 or `tabNote`.owner="{user}")""".format(user=user)
+ return """(`tabNote`.public=1 or `tabNote`.owner={user})""".format(user=frappe.db.escape(user))
def has_permission(doc, ptype, user):
diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py
index 011f3e22ff..9e7ed7d9fe 100644
--- a/frappe/desk/doctype/notification_log/notification_log.py
+++ b/frappe/desk/doctype/notification_log/notification_log.py
@@ -30,7 +30,7 @@ def get_permission_query_conditions(for_user):
if for_user == "Administrator":
return
- return """(`tabNotification Log`.for_user = '{user}')""".format(user=for_user)
+ return """(`tabNotification Log`.for_user = {user})""".format(user=frappe.db.escape(for_user))
def get_title(doctype, docname, title_field=None):
diff --git a/frappe/desk/doctype/notification_settings/notification_settings.py b/frappe/desk/doctype/notification_settings/notification_settings.py
index bbb4a62154..ee425e154b 100644
--- a/frappe/desk/doctype/notification_settings/notification_settings.py
+++ b/frappe/desk/doctype/notification_settings/notification_settings.py
@@ -81,7 +81,7 @@ def get_permission_query_conditions(user):
if "System Manager" in roles:
return """(`tabNotification Settings`.name != 'Administrator')"""
- return """(`tabNotification Settings`.name = '{user}')""".format(user=user)
+ return """(`tabNotification Settings`.name = {user})""".format(user=frappe.db.escape(user))
@frappe.whitelist()
diff --git a/frappe/desk/doctype/number_card/number_card.js b/frappe/desk/doctype/number_card/number_card.js
index f548388a99..79ddb71187 100644
--- a/frappe/desk/doctype/number_card/number_card.js
+++ b/frappe/desk/doctype/number_card/number_card.js
@@ -27,8 +27,11 @@ frappe.ui.form.on('Number Card', {
frm.trigger('set_method_description');
frm.trigger('render_filters_table');
}
- frm.trigger('create_add_to_dashboard_button');
frm.trigger('set_parent_document_type');
+
+ if (!frm.is_new()) {
+ frm.trigger('create_add_to_dashboard_button');
+ }
},
create_add_to_dashboard_button: function(frm) {
diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py
index 370b187ffe..d6d4f00b69 100644
--- a/frappe/desk/doctype/number_card/number_card.py
+++ b/frappe/desk/doctype/number_card/number_card.py
@@ -8,6 +8,8 @@ from frappe.config import get_modules_from_all_apps_for_user
from frappe.model.document import Document
from frappe.model.naming import append_number_if_name_exists
from frappe.modules.export_file import export_to_files
+from frappe.query_builder import Criterion
+from frappe.query_builder.utils import DocType
from frappe.utils import cint
@@ -20,15 +22,24 @@ class NumberCard(Document):
self.name = append_number_if_name_exists("Number Card", self.name)
def validate(self):
- if not self.document_type:
- frappe.throw(_("Document type is required to create a number card"))
+ if self.type == "Document Type":
+ if not (self.document_type and self.function):
+ frappe.throw(_("Document Type and Function are required to create a number card"))
- if (
- self.document_type
- and frappe.get_meta(self.document_type).istable
- and not self.parent_document_type
- ):
- frappe.throw(_("Parent document type is required to create a number card"))
+ if (
+ self.document_type
+ and frappe.get_meta(self.document_type).istable
+ and not self.parent_document_type
+ ):
+ frappe.throw(_("Parent Document Type is required to create a number card"))
+
+ elif self.type == "Report":
+ if not (self.report_name and self.report_field and self.function):
+ frappe.throw(_("Report Name, Report Field and Fucntion are required to create a number card"))
+
+ elif self.type == "Custom":
+ if not self.method:
+ frappe.throw(_("Method is required to create a number card"))
def on_update(self):
if frappe.conf.developer_mode and self.is_standard:
@@ -181,36 +192,18 @@ def get_cards_for_user(doctype, txt, searchfield, start, page_len, filters):
if not frappe.db.exists("DocType", doctype):
return
+ numberCard = DocType("Number Card")
+
if txt:
- for field in searchfields:
- search_conditions.append(
- "`tab{doctype}`.`{field}` like %(txt)s".format(field=field, doctype=doctype, txt=txt)
- )
+ search_conditions = [numberCard[field].like("%{txt}%".format(txt=txt)) for field in searchfields]
- search_conditions = " or ".join(search_conditions)
+ condition_query = frappe.db.query.build_conditions(doctype, filters)
- search_conditions = "and (" + search_conditions + ")" if search_conditions else ""
- conditions, values = frappe.db.build_conditions(filters)
- values["txt"] = "%" + txt + "%"
-
- return frappe.db.sql(
- """select
- `tabNumber Card`.name, `tabNumber Card`.label, `tabNumber Card`.document_type
- from
- `tabNumber Card`
- where
- {conditions} and
- (`tabNumber Card`.owner = '{user}' or
- `tabNumber Card`.is_public = 1)
- {search_conditions}
- """.format(
- filters=filters,
- user=frappe.session.user,
- search_conditions=search_conditions,
- conditions=conditions,
- ),
- values,
- )
+ return (
+ condition_query.select(numberCard.name, numberCard.label, numberCard.document_type)
+ .where((numberCard.owner == frappe.session.user) | (numberCard.is_public == 1))
+ .where(Criterion.any(search_conditions))
+ ).run()
@frappe.whitelist()
diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py
index f5edd3fcc6..dc26dfe5b0 100644
--- a/frappe/desk/form/meta.py
+++ b/frappe/desk/form/meta.py
@@ -55,12 +55,6 @@ class FormMeta(Meta):
super(FormMeta, self).__init__(doctype)
self.load_assets()
- def set(self, key, value, *args, **kwargs):
- if key in ASSET_KEYS:
- self.__dict__[key] = value
- else:
- super(FormMeta, self).set(key, value, *args, **kwargs)
-
def load_assets(self):
if self.get("__assets_loaded", False):
return
diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js
index cc91a16345..c683655dd5 100644
--- a/frappe/desk/page/setup_wizard/setup_wizard.js
+++ b/frappe/desk/page/setup_wizard/setup_wizard.js
@@ -49,7 +49,6 @@ frappe.pages['setup-wizard'].on_page_load = function (wrapper) {
}
frappe.wizard = new frappe.setup.SetupWizard(wizard_settings);
frappe.setup.run_event("after_load");
- // frappe.wizard.values = test_values_edu;
let route = frappe.get_route();
if (route) {
frappe.wizard.show_slide(route[1]);
@@ -145,7 +144,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
refresh_slides() {
// For Translations, etc.
- if (this.in_refresh_slides || !this.current_slide.set_values()) {
+ if (this.in_refresh_slides || !this.current_slide.set_values(true)) {
return;
}
this.in_refresh_slides = true;
@@ -344,63 +343,40 @@ frappe.setup.slides_settings = [
// Welcome (language) slide
name: "welcome",
title: __("Hello!"),
- icon: "fa fa-world",
- // help: __("Let's prepare the system for first use."),
fields: [
{
- fieldname: "language", label: __("Your Language"),
- fieldtype: "Select", reqd: 1
- }
- ],
-
- onload: function (slide) {
- this.setup_fields(slide);
- let browser_language = frappe.setup.utils.get_language_name_from_code(navigator.language);
- let language_field = slide.get_field("language");
-
- language_field.set_input(browser_language || "English");
-
- if (!frappe.setup._from_load_messages) {
- language_field.$input.trigger("change");
- }
- delete frappe.setup._from_load_messages;
- moment.locale("en");
- },
-
- setup_fields: function (slide) {
- frappe.setup.utils.setup_language_field(slide);
- frappe.setup.utils.bind_language_events(slide);
- },
- },
-
- {
- // Region slide
- name: 'region',
- title: __("Select Your Region"),
- icon: "fa fa-flag",
- // help: __("Select your Country, Time Zone and Currency"),
- fields: [
- {
- fieldname: "country", label: __("Your Country"), reqd: 1,
+ fieldname: "language",
+ label: __("Your Language"),
fieldtype: "Autocomplete",
- placeholder: __('Select Country')
+ placeholder: __('Select Language'),
+ default: "English",
+ reqd: 1,
+ },
+ {
+ fieldname: "country",
+ label: __("Your Country"),
+ fieldtype: "Autocomplete",
+ placeholder: __('Select Country'),
+ reqd: 1,
+ },
+ {
+ fieldtype: "Section Break"
},
- { fieldtype: "Section Break" },
{
fieldname: "timezone",
label: __("Time Zone"),
placeholder: __('Select Time Zone'),
- reqd: 1,
fieldtype: "Select",
+ reqd: 1,
},
{ fieldtype: "Column Break" },
{
fieldname: "currency",
label: __("Currency"),
placeholder: __('Select Currency'),
- reqd: 1,
fieldtype: "Select",
+ reqd: 1,
}
],
@@ -410,14 +386,26 @@ frappe.setup.slides_settings = [
} else {
frappe.setup.utils.load_regional_data(slide, this.setup_fields);
}
+ if (!slide.get_value("language")) {
+ let session_language = frappe.setup.utils.get_language_name_from_code(frappe.boot.lang || navigator.language) || "English";
+ let language_field = slide.get_field("language");
+
+ language_field.set_input(session_language);
+ if (!frappe.setup._from_load_messages) {
+ language_field.$input.trigger("change");
+ }
+ delete frappe.setup._from_load_messages;
+ moment.locale("en");
+ }
+ frappe.setup.utils.bind_region_events(slide);
+ frappe.setup.utils.bind_language_events(slide);
},
setup_fields: function (slide) {
frappe.setup.utils.setup_region_fields(slide);
- frappe.setup.utils.bind_region_events(slide);
- }
+ frappe.setup.utils.setup_language_field(slide);
+ },
},
-
{
// Profile slide
name: 'user',
@@ -438,7 +426,7 @@ frappe.setup.slides_settings = [
},
{ "fieldname": "password", "label": __("Password"), "fieldtype": "Password" }
],
- // help: __('The first user will become the System Manager (you can change this later).'),
+
onload: function (slide) {
if (frappe.session.user !== "Administrator") {
slide.form.fields_dict.email.$wrapper.toggle(false);
@@ -516,7 +504,7 @@ frappe.setup.utils = {
setup_language_field: function (slide) {
var language_field = slide.get_field("language");
language_field.df.options = frappe.setup.data.lang.languages;
- language_field.refresh();
+ language_field.set_options();
},
setup_region_fields: function (slide) {
@@ -555,9 +543,7 @@ frappe.setup.utils = {
}
slide.get_field("currency").set_input(frappe.wizard.values.currency);
-
slide.get_field("timezone").set_input(frappe.wizard.values.timezone);
-
},
bind_language_events: function (slide) {
diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py
index 894e82d117..a51fd8b1e3 100644
--- a/frappe/desk/query_report.py
+++ b/frappe/desk/query_report.py
@@ -58,6 +58,8 @@ def get_report_doc(report_name):
def get_report_result(report, filters):
+ res = None
+
if report.report_type == "Query Report":
res = report.execute_query_report(filters)
@@ -84,7 +86,7 @@ def generate_report_result(
res = get_report_result(report, filters) or []
columns, result, message, chart, report_summary, skip_total_row = ljust_list(res, 6)
- columns = [get_column_as_dict(col) for col in columns]
+ columns = [get_column_as_dict(col) for col in (columns or [])]
report_column_names = [col["fieldname"] for col in columns]
# convert to list of dicts
diff --git a/frappe/email/doctype/email_account/email_account.js b/frappe/email/doctype/email_account/email_account.js
index 54f0d2372d..a357126a48 100644
--- a/frappe/email/doctype/email_account/email_account.js
+++ b/frappe/email/doctype/email_account/email_account.js
@@ -134,10 +134,11 @@ frappe.ui.form.on("Email Account", {
show_gmail_message_for_less_secure_apps: function(frm) {
frm.dashboard.clear_headline();
+ let msg = __("GMail will only work if you enable 2-step authentication and use app-specific password.");
+ let cta = __("Read the step by step guide here.");
+ msg += ` ${cta}`;
if (frm.doc.service==="GMail") {
- frm.dashboard.set_headline_alert('Gmail will only work if you allow access for less secure \
- apps in Gmail settings. Read this for details');
+ frm.dashboard.set_headline_alert(msg);
}
},
diff --git a/frappe/integrations/doctype/braintree_settings/braintree_settings.py b/frappe/integrations/doctype/braintree_settings/braintree_settings.py
index ca7ab0cfdf..17330d7c84 100644
--- a/frappe/integrations/doctype/braintree_settings/braintree_settings.py
+++ b/frappe/integrations/doctype/braintree_settings/braintree_settings.py
@@ -190,7 +190,7 @@ class BraintreeSettings(Document):
self.data = frappe._dict(data)
try:
- self.integration_request = create_request_log(self.data, "Host", "Braintree")
+ self.integration_request = create_request_log(self.data, service_name="Braintree")
return self.create_charge_on_braintree()
except Exception:
diff --git a/frappe/integrations/doctype/integration_request/integration_request.json b/frappe/integrations/doctype/integration_request/integration_request.json
index 8a3fbc41ba..98db8ea748 100644
--- a/frappe/integrations/doctype/integration_request/integration_request.json
+++ b/frappe/integrations/doctype/integration_request/integration_request.json
@@ -1,334 +1,154 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-08-04 04:58:40.457416",
- "custom": 0,
- "docstatus": 0,
+ "actions": [],
+ "creation": "2022-03-28 12:25:29.929952",
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "request_id",
+ "integration_request_service",
+ "is_remote_request",
+ "column_break_5",
+ "request_description",
+ "status",
+ "section_break_8",
+ "url",
+ "request_headers",
+ "data",
+ "response_section",
+ "output",
+ "error",
+ "reference_section",
+ "reference_doctype",
+ "column_break_16",
+ "reference_docname"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "integration_type",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Integration Type",
- "length": 0,
- "no_copy": 0,
- "options": "\nHost\nRemote\nSubscription Notification",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "integration_request_service",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Integration Request Service",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Service",
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "Queued",
- "fetch_if_empty": 0,
"fieldname": "status",
"fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
- "length": 0,
- "no_copy": 0,
"options": "\nQueued\nAuthorized\nCompleted\nCancelled\nFailed",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "data",
"fieldtype": "Code",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Data",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "label": "Request Data",
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "output",
"fieldtype": "Code",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Output",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "error",
"fieldtype": "Code",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Error",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "reference_doctype",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
"label": "Reference Document Type",
- "length": 0,
- "no_copy": 0,
"options": "DocType",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
"fieldname": "reference_docname",
"fieldtype": "Dynamic Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Reference Docname",
- "length": 0,
- "no_copy": 0,
+ "label": "Reference Document Name",
"options": "reference_doctype",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "is_remote_request",
+ "fieldtype": "Check",
+ "label": "Is Remote Request?",
+ "read_only": 1
+ },
+ {
+ "fieldname": "request_description",
+ "fieldtype": "Data",
+ "label": "Request Description",
+ "read_only": 1
+ },
+ {
+ "fieldname": "request_id",
+ "fieldtype": "Data",
+ "label": "Request ID",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "url",
+ "fieldtype": "Data",
+ "label": "URL",
+ "read_only": 1
+ },
+ {
+ "fieldname": "response_section",
+ "fieldtype": "Section Break",
+ "label": "Response"
+ },
+ {
+ "depends_on": "eval:doc.reference_doctype",
+ "fieldname": "reference_section",
+ "fieldtype": "Section Break",
+ "label": "Reference"
+ },
+ {
+ "fieldname": "column_break_16",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "request_headers",
+ "fieldtype": "Code",
+ "label": "Request Headers",
+ "read_only": 1
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
"in_create": 1,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-09-05 14:22:27.664645",
+ "links": [],
+ "modified": "2022-04-07 11:32:27.557548",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Integration Request",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
+ "share": 1
}
],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "integration_request_service",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/paypal_settings/paypal_settings.py b/frappe/integrations/doctype/paypal_settings/paypal_settings.py
index ab7512f403..3568b77baf 100644
--- a/frappe/integrations/doctype/paypal_settings/paypal_settings.py
+++ b/frappe/integrations/doctype/paypal_settings/paypal_settings.py
@@ -182,9 +182,8 @@ class PayPalSettings(Document):
kwargs.update(
{"token": response.get("TOKEN")[0], "correlation_id": response.get("CORRELATIONID")[0]}
)
- self.integration_request = create_request_log(
- kwargs, "Remote", "PayPal", response.get("TOKEN")[0]
- )
+
+ create_request_log(kwargs, service_name="PayPal", name=kwargs["token"])
return return_url.format(kwargs["token"])
@@ -463,7 +462,8 @@ def ipn_handler():
{
"data": json.dumps(frappe.local.form_dict),
"doctype": "Integration Request",
- "integration_type": "Subscription Notification",
+ "request_description": "Subscription Notification",
+ "is_remote_request": 1,
"status": "Queued",
}
).insert(ignore_permissions=True)
diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py
index 0888fd35b7..d8c9159303 100644
--- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py
+++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py
@@ -34,7 +34,7 @@ class PaytmSettings(Document):
def get_payment_url(self, **kwargs):
"""Return payment url with several params"""
# create unique order id by making it equal to the integration request
- integration_request = create_request_log(kwargs, "Host", "Paytm")
+ integration_request = create_request_log(kwargs, service_name="Paytm")
kwargs.update(dict(order_id=integration_request.name))
return get_url("./integrations/paytm_checkout?{0}".format(urlencode(kwargs)))
diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py
index c4ffb74325..b48d21187c 100644
--- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py
+++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py
@@ -140,7 +140,7 @@ class RazorpaySettings(Document):
headers={"content-type": "application/json"},
)
if not resp.get("id"):
- frappe.log_error(str(resp), "Razorpay Failed while creating subscription")
+ frappe.log_error(message=str(resp), title="Razorpay Failed while creating subscription")
except:
frappe.log_error(frappe.get_traceback())
# failed
@@ -179,7 +179,7 @@ class RazorpaySettings(Document):
frappe.flags.status = "created"
return kwargs
else:
- frappe.log_error(str(resp), "Razorpay Failed while creating subscription")
+ frappe.log_error(message=str(resp), title="Razorpay Failed while creating subscription")
except:
frappe.log_error(frappe.get_traceback())
@@ -196,7 +196,7 @@ class RazorpaySettings(Document):
return kwargs
def get_payment_url(self, **kwargs):
- integration_request = create_request_log(kwargs, "Host", "Razorpay")
+ integration_request = create_request_log(kwargs, service_name="Razorpay")
return get_url("./integrations/razorpay_checkout?token={0}".format(integration_request.name))
def create_order(self, **kwargs):
@@ -206,7 +206,7 @@ class RazorpaySettings(Document):
kwargs["amount"] *= 100
# Create integration log
- integration_request = create_request_log(kwargs, "Host", "Razorpay")
+ integration_request = create_request_log(kwargs, service_name="Razorpay")
# Setup payment options
payment_options = {
@@ -281,7 +281,7 @@ class RazorpaySettings(Document):
self.flags.status_changed_to = "Verified"
else:
- frappe.log_error(str(resp), "Razorpay Payment not authorized")
+ frappe.log_error(message=str(resp), title="Razorpay Payment not authorized")
except:
frappe.log_error(frappe.get_traceback())
@@ -490,7 +490,8 @@ def razorpay_subscription_callback():
{
"data": json.dumps(frappe.local.form_dict),
"doctype": "Integration Request",
- "integration_type": "Subscription Notification",
+ "request_description": "Subscription Notification",
+ "is_remote_request": 1,
"status": "Queued",
}
).insert(ignore_permissions=True)
diff --git a/frappe/integrations/doctype/stripe_settings/stripe_settings.py b/frappe/integrations/doctype/stripe_settings/stripe_settings.py
index 4ebf902e84..bc8b0dfa17 100644
--- a/frappe/integrations/doctype/stripe_settings/stripe_settings.py
+++ b/frappe/integrations/doctype/stripe_settings/stripe_settings.py
@@ -200,7 +200,7 @@ class StripeSettings(Document):
stripe.default_http_client = stripe.http_client.RequestsClient()
try:
- self.integration_request = create_request_log(self.data, "Host", "Stripe")
+ self.integration_request = create_request_log(self.data, service_name="Stripe")
return self.create_charge_on_stripe()
except Exception:
diff --git a/frappe/integrations/doctype/webhook_request_log/webhook_request_log.json b/frappe/integrations/doctype/webhook_request_log/webhook_request_log.json
index 96690f6e8c..d9410a2f82 100644
--- a/frappe/integrations/doctype/webhook_request_log/webhook_request_log.json
+++ b/frappe/integrations/doctype/webhook_request_log/webhook_request_log.json
@@ -1,6 +1,6 @@
{
"actions": [],
- "autoname": "WEBHOOK-REQ-.#####",
+ "autoname": "hash",
"creation": "2021-05-24 21:35:59.104776",
"doctype": "DocType",
"editable_grid": 1,
@@ -56,10 +56,11 @@
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-05-26 23:57:58.495261",
+ "modified": "2022-05-03 09:33:49.240777",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Webhook Request Log",
+ "naming_rule": "Random",
"owner": "Administrator",
"permissions": [
{
@@ -77,5 +78,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/integrations/utils.py b/frappe/integrations/utils.py
index 191cd1f23b..1440c80399 100644
--- a/frappe/integrations/utils.py
+++ b/frappe/integrations/utils.py
@@ -42,22 +42,45 @@ def make_put_request(url, **kwargs):
return make_request("PUT", url, **kwargs)
-def create_request_log(data, integration_type, service_name, name=None, error=None):
- if isinstance(data, str):
- data = json.loads(data)
+def create_request_log(
+ data,
+ integration_type=None,
+ service_name=None,
+ name=None,
+ error=None,
+ request_headers=None,
+ output=None,
+ **kwargs
+):
+ """
+ DEPRECATED: The parameter integration_type will be removed in the next major release.
+ Use is_remote_request instead.
+ """
+ if integration_type == "Remote":
+ kwargs["is_remote_request"] = 1
- if isinstance(error, str):
- error = json.loads(error)
+ elif integration_type == "Subscription Notification":
+ kwargs["request_description"] = integration_type
+
+ reference_doctype = reference_docname = None
+ if "reference_doctype" not in kwargs:
+ if isinstance(data, str):
+ data = json.loads(data)
+
+ reference_doctype = data.get("reference_doctype")
+ reference_docname = data.get("reference_docname")
integration_request = frappe.get_doc(
{
"doctype": "Integration Request",
- "integration_type": integration_type,
"integration_request_service": service_name,
- "reference_doctype": data.get("reference_doctype"),
- "reference_docname": data.get("reference_docname"),
- "error": json.dumps(error, default=json_handler),
- "data": json.dumps(data, default=json_handler),
+ "request_headers": get_json(request_headers),
+ "data": get_json(data),
+ "output": get_json(output),
+ "error": get_json(error),
+ "reference_doctype": reference_doctype,
+ "reference_docname": reference_docname,
+ **kwargs,
}
)
@@ -70,6 +93,10 @@ def create_request_log(data, integration_type, service_name, name=None, error=No
return integration_request
+def get_json(obj):
+ return obj if isinstance(obj, str) else frappe.as_json(obj, indent=1)
+
+
def get_payment_gateway_controller(payment_gateway):
"""Return payment gateway controller"""
gateway = frappe.get_doc("Payment Gateway", payment_gateway)
diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py
index a272dedd02..22f8cc15df 100644
--- a/frappe/model/base_document.py
+++ b/frappe/model/base_document.py
@@ -5,7 +5,7 @@ import json
from typing import Dict, List
import frappe
-from frappe import _
+from frappe import _, _dict
from frappe.model import (
child_table_fields,
datetime_fields,
@@ -23,7 +23,16 @@ from frappe.utils.html_utils import unescape_html
max_positive_value = {"smallint": 2**15, "int": 2**31, "bigint": 2**63}
-DOCTYPES_FOR_DOCTYPE = ("DocType", "DocField", "DocPerm", "DocType Action", "DocType Link")
+DOCTYPE_TABLE_FIELDS = [
+ _dict(fieldname="fields", options="DocField"),
+ _dict(fieldname="permissions", options="DocPerm"),
+ _dict(fieldname="actions", options="DocType Action"),
+ _dict(fieldname="links", options="DocType Link"),
+ _dict(fieldname="states", options="DocType State"),
+]
+
+TABLE_DOCTYPES_FOR_DOCTYPE = {df["fieldname"]: df["options"] for df in DOCTYPE_TABLE_FIELDS}
+DOCTYPES_FOR_DOCTYPE = {"DocType", *TABLE_DOCTYPES_FOR_DOCTYPE.values()}
def get_controller(doctype):
@@ -78,12 +87,28 @@ def get_controller(doctype):
class BaseDocument(object):
- ignore_in_setter = ("doctype", "_meta", "meta", "_table_fields", "_valid_columns")
+ _reserved_keywords = {
+ "doctype",
+ "meta",
+ "_meta",
+ "flags",
+ "_table_fields",
+ "_valid_columns",
+ "_table_fieldnames",
+ "_reserved_keywords",
+ "dont_update_if_missing",
+ }
def __init__(self, d):
if d.get("doctype"):
self.doctype = d["doctype"]
+ self._table_fieldnames = (
+ d["_table_fieldnames"] # from cache
+ if "_table_fieldnames" in d
+ else {df.fieldname for df in self._get_table_fields()}
+ )
+
self.update(d)
self.dont_update_if_missing = []
@@ -92,10 +117,10 @@ class BaseDocument(object):
@property
def meta(self):
- if not getattr(self, "_meta", None):
- self._meta = frappe.get_meta(self.doctype)
+ if not (meta := getattr(self, "_meta", None)):
+ self._meta = meta = frappe.get_meta(self.doctype)
- return self._meta
+ return meta
def __getstate__(self):
self._meta = None
@@ -144,17 +169,12 @@ class BaseDocument(object):
if filters:
if isinstance(filters, dict):
- value = _filter(self.__dict__.get(key, []), filters, limit=limit)
- else:
- default = filters
- filters = None
- value = self.__dict__.get(key, default)
- else:
- value = self.__dict__.get(key, default)
+ return _filter(self.__dict__.get(key, []), filters, limit=limit)
- if value is None and key in (d.fieldname for d in self.meta.get_table_fields()):
- value = []
- self.set(key, value)
+ # perhaps you wanted to set a default instead
+ default = filters
+
+ value = self.__dict__.get(key, default)
if limit and isinstance(value, (list, tuple)) and len(value) > limit:
value = value[:limit]
@@ -165,14 +185,19 @@ class BaseDocument(object):
return self.get(key, filters=filters, limit=1)[0]
def set(self, key, value, as_value=False):
- if key in self.ignore_in_setter:
+ if key in self._reserved_keywords:
return
- if isinstance(value, list) and not as_value:
+ if not as_value and key in self._table_fieldnames:
self.__dict__[key] = []
- self.extend(key, value)
- else:
- self.__dict__[key] = value
+
+ # if value is falsy, just init to an empty list
+ if value:
+ self.extend(key, value)
+
+ return
+
+ self.__dict__[key] = value
def delete_key(self, key):
if key in self.__dict__:
@@ -190,41 +215,27 @@ class BaseDocument(object):
"""
if value is None:
value = {}
- if isinstance(value, (dict, BaseDocument)):
- if not self.__dict__.get(key):
- self.__dict__[key] = []
- value = self._init_child(value, key)
- self.__dict__[key].append(value)
+ if (table := self.__dict__.get(key)) is None:
+ self.__dict__[key] = table = []
- # reference parent document
- value.parent_doc = self
+ value = self._init_child(value, key)
+ table.append(value)
- return value
- else:
+ # reference parent document
+ value.parent_doc = self
- # metaclasses may have arbitrary lists
- # which we can ignore
- if getattr(self, "_metaclass", None) or self.__class__.__name__ in (
- "Meta",
- "FormMeta",
- "DocField",
- ):
- return value
-
- raise ValueError(
- 'Document for field "{0}" attached to child table of "{1}" must be a dict or BaseDocument, not {2} ({3})'.format(
- key, self.name, str(type(value))[1:-1], value
- )
- )
+ return value
def extend(self, key, value):
- if isinstance(value, list):
- for v in value:
- self.append(key, v)
- else:
+ try:
+ value = iter(value)
+ except TypeError:
raise ValueError
+ for v in value:
+ self.append(key, v)
+
def remove(self, doc):
# Usage: from the parent doc, pass the child table doc
# to remove that child doc from the child table, thus removing it from the parent doc
@@ -232,15 +243,12 @@ class BaseDocument(object):
self.get(doc.parentfield).remove(doc)
def _init_child(self, value, key):
- if not self.doctype:
- return value
-
if not isinstance(value, BaseDocument):
- value["doctype"] = self.get_table_field_doctype(key)
- if not value["doctype"]:
+ if not (doctype := self.get_table_field_doctype(key)):
raise AttributeError(key)
- value = get_controller(value["doctype"])(value)
+ value["doctype"] = doctype
+ value = get_controller(doctype)(value)
value.parent = self.name
value.parenttype = self.doctype
@@ -250,17 +258,35 @@ class BaseDocument(object):
value.docstatus = DocStatus.draft()
if not getattr(value, "idx", None):
- value.idx = len(self.get(key) or []) + 1
+ if table := getattr(self, key, None):
+ value.idx = len(table) + 1
+ else:
+ value.idx = 1
if not getattr(value, "name", None):
value.__dict__["__islocal"] = 1
return value
+ def _get_table_fields(self):
+ """
+ To get table fields during Document init
+ Meta.get_table_fields goes into recursion for special doctypes
+ """
+
+ if self.doctype == "DocType":
+ return DOCTYPE_TABLE_FIELDS
+
+ # child tables don't have child tables
+ if self.doctype in DOCTYPES_FOR_DOCTYPE or getattr(self, "parentfield", None):
+ return ()
+
+ return self.meta.get_table_fields()
+
def get_valid_dict(
self, sanitize=True, convert_dates_to_str=False, ignore_nulls=False, ignore_virtual=False
) -> Dict:
- d = frappe._dict()
+ d = _dict()
for fieldname in self.meta.get_valid_columns():
# column is valid, we can use getattr
d[fieldname] = getattr(self, fieldname, None)
@@ -316,6 +342,16 @@ class BaseDocument(object):
return d
+ def init_child_tables(self):
+ """
+ This is needed so that one can loop over child table properties
+ without worrying about whether or not they have values
+ """
+
+ for fieldname in self._table_fieldnames:
+ if self.__dict__.get(fieldname) is None:
+ self.__dict__[fieldname] = []
+
def init_valid_columns(self):
for key in default_fields:
if key not in self.__dict__:
@@ -365,9 +401,9 @@ class BaseDocument(object):
doc = self.get_valid_dict(convert_dates_to_str=convert_dates_to_str, ignore_nulls=no_nulls)
doc["doctype"] = self.doctype
- for df in self.meta.get_table_fields():
- children = self.get(df.fieldname) or []
- doc[df.fieldname] = [
+ for fieldname in self._table_fieldnames:
+ children = self.get(fieldname) or []
+ doc[fieldname] = [
d.as_dict(
convert_dates_to_str=convert_dates_to_str,
no_nulls=no_nulls,
@@ -407,10 +443,9 @@ class BaseDocument(object):
try:
return self.meta.get_field(fieldname).options
except AttributeError:
- if self.doctype == "DocType":
- return dict(links="DocType Link", actions="DocType Action", states="DocType State").get(
- fieldname
- )
+ if self.doctype == "DocType" and (table_doctype := TABLE_DOCTYPES_FOR_DOCTYPE.get(fieldname)):
+ return table_doctype
+
raise
def get_parentfield_of_doctype(self, doctype):
@@ -519,8 +554,8 @@ class BaseDocument(object):
"""Raw update parent + children
DOES NOT VALIDATE AND CALL TRIGGERS"""
self.db_update()
- for df in self.meta.get_table_fields():
- for doc in self.get(df.fieldname):
+ for fieldname in self._table_fieldnames:
+ for doc in self.get(fieldname):
doc.db_update()
def show_unique_validation_message(self, e):
@@ -632,7 +667,7 @@ class BaseDocument(object):
if self.meta.istable:
for fieldname in ("parent", "parenttype"):
if not self.get(fieldname):
- missing.append((fieldname, get_msg(frappe._dict(label=fieldname))))
+ missing.append((fieldname, get_msg(_dict(label=fieldname))))
return missing
@@ -679,7 +714,7 @@ class BaseDocument(object):
if not frappe.get_meta(doctype).get("is_virtual"):
if not fields_to_fetch:
# cache a single value type
- values = frappe._dict(name=frappe.db.get_value(doctype, docname, "name", cache=True))
+ values = _dict(name=frappe.db.get_value(doctype, docname, "name", cache=True))
else:
values_to_fetch = ["name"] + [_df.fetch_from.split(".")[-1] for _df in fields_to_fetch]
@@ -1009,10 +1044,10 @@ class BaseDocument(object):
cache_key = parentfield or "main"
if not hasattr(self, "_precision"):
- self._precision = frappe._dict()
+ self._precision = _dict()
if cache_key not in self._precision:
- self._precision[cache_key] = frappe._dict()
+ self._precision[cache_key] = _dict()
if fieldname not in self._precision[cache_key]:
self._precision[cache_key][fieldname] = None
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index a7d9536ebc..acb63b5bfa 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -213,7 +213,7 @@ class DatabaseQuery(object):
# left join parent, child tables
for child in self.tables[1:]:
- parent_name = self.cast_name(f"{self.tables[0]}.name")
+ parent_name = cast_name(f"{self.tables[0]}.name")
args.tables += f" {self.join} {child} on ({child}.parent = {parent_name})"
if self.grouped_or_conditions:
@@ -225,6 +225,7 @@ class DatabaseQuery(object):
args.conditions += (" or " if args.conditions else "") + " or ".join(self.or_conditions)
self.set_field_tables()
+ self.cast_name_fields()
fields = []
@@ -385,16 +386,8 @@ class DatabaseQuery(object):
]
# add tables from fields
if self.fields:
- for i, field in enumerate(self.fields):
- # add cast in locate/strpos
- func_found = False
- for func in sql_functions:
- if func in field.lower():
- self.fields[i] = self.cast_name(field, func)
- func_found = True
- break
-
- if func_found or not ("tab" in field and "." in field):
+ for field in self.fields:
+ if not ("tab" in field and "." in field) or any(x for x in sql_functions if x in field):
continue
table_name = field.split(".")[0]
@@ -406,38 +399,6 @@ class DatabaseQuery(object):
if table_name not in self.tables:
self.append_table(table_name)
- def cast_name(
- self,
- column: str,
- sql_function: str = "",
- ) -> str:
- if frappe.db.db_type == "postgres":
- if "name" in column.lower():
- if "cast(" not in column.lower() or "::" not in column:
- if not sql_function:
- return f"cast({column} as varchar)"
-
- elif sql_function == "locate(":
- return re.sub(
- r"locate\(([^,]+),([^)]+)\)",
- r"locate(\1, cast(\2 as varchar))",
- column,
- flags=re.IGNORECASE,
- )
-
- elif sql_function == "strpos(":
- return re.sub(
- r"strpos\(([^,]+),([^)]+)\)",
- r"strpos(cast(\1 as varchar), \2)",
- column,
- flags=re.IGNORECASE,
- )
-
- elif sql_function == "ifnull(":
- return re.sub(r"ifnull\(([^,]+)", r"ifnull(cast(\1 as varchar)", column, flags=re.IGNORECASE)
-
- return column
-
def append_table(self, table_name):
self.tables.append(table_name)
doctype = table_name[4:-1]
@@ -462,6 +423,10 @@ class DatabaseQuery(object):
if "." not in field and not _in_standard_sql_methods(field):
self.fields[idx] = f"{self.tables[0]}.{field}"
+ def cast_name_fields(self):
+ for i, field in enumerate(self.fields):
+ self.fields[i] = cast_name(field)
+
def get_table_columns(self):
try:
return get_table_columns(self.doctype)
@@ -541,10 +506,7 @@ class DatabaseQuery(object):
if tname not in self.tables:
self.append_table(tname)
- if "ifnull(" in f.fieldname:
- column_name = self.cast_name(f.fieldname, "ifnull(")
- else:
- column_name = self.cast_name(f"{tname}.`{f.fieldname}`")
+ column_name = cast_name(f.fieldname if "ifnull(" in f.fieldname else f"{tname}.`{f.fieldname}`")
if f.operator.lower() in additional_filters_config:
f.update(get_additional_filter_field(additional_filters_config, f, f.value))
@@ -766,7 +728,10 @@ class DatabaseQuery(object):
return self.match_filters
def get_share_condition(self):
- return f"`tab{self.doctype}`.name in ({', '.join(frappe.db.escape(s, percent=False) for s in self.shared)})"
+ return (
+ cast_name(f"`tab{self.doctype}`.name")
+ + f" in ({', '.join(frappe.db.escape(s, percent=False) for s in self.shared)})"
+ )
def add_user_permissions(self, user_permissions):
meta = frappe.get_meta(self.doctype)
@@ -794,7 +759,9 @@ class DatabaseQuery(object):
if frappe.get_system_settings("apply_strict_user_permissions"):
condition = ""
else:
- empty_value_condition = f"ifnull(`tab{self.doctype}`.`{df.get('fieldname')}`, '')=''"
+ empty_value_condition = cast_name(
+ f"ifnull(`tab{self.doctype}`.`{df.get('fieldname')}`, '')=''"
+ )
condition = empty_value_condition + " or "
for permission in user_permission_values:
@@ -815,7 +782,7 @@ class DatabaseQuery(object):
if docs:
values = ", ".join(frappe.db.escape(doc, percent=False) for doc in docs)
- condition += f"`tab{self.doctype}`.`{df.get('fieldname')}` in ({values})"
+ condition += cast_name(f"`tab{self.doctype}`.`{df.get('fieldname')}`") + f" in ({values})"
match_conditions.append(f"({condition})")
match_filters[df.get("options")] = docs
@@ -933,6 +900,40 @@ class DatabaseQuery(object):
update_user_settings(self.doctype, user_settings)
+def cast_name(column: str) -> str:
+ """Casts name field to varchar for postgres
+
+ Handles majorly 4 cases:
+ 1. locate
+ 2. strpos
+ 3. ifnull
+ 4. coalesce
+
+ Uses regex substitution.
+
+ Example:
+ input - "ifnull(`tabBlog Post`.`name`, '')=''"
+ output - "ifnull(cast(`tabBlog Post`.`name` as varchar), '')=''" """
+
+ if frappe.db.db_type == "mariadb":
+ return column
+
+ kwargs = {"string": column, "flags": re.IGNORECASE}
+ if "cast(" not in column.lower() and "::" not in column:
+ if re.search(r"locate\(([^,]+),\s*([`\"]?name[`\"]?)\s*\)", **kwargs):
+ return re.sub(
+ r"locate\(([^,]+),\s*([`\"]?name[`\"]?)\)", r"locate(\1, cast(\2 as varchar))", **kwargs
+ )
+
+ elif match := re.search(r"(strpos|ifnull|coalesce)\(\s*([`\"]?name[`\"]?)\s*,", **kwargs):
+ func = match.groups()[0]
+ return re.sub(rf"{func}\(\s*([`\"]?name[`\"]?)\s*,", rf"{func}(cast(\1 as varchar),", **kwargs)
+
+ return re.sub(r"([`\"]?tab[\w`\" -]+\.[`\"]?name[`\"]?)(?!\w)", r"cast(\1 as varchar)", **kwargs)
+
+ return column
+
+
def check_parent_permission(parent, child_doctype):
if parent:
# User may pass fake parent and get the information from the child table
diff --git a/frappe/model/document.py b/frappe/model/document.py
index c5e61563f8..f59fb81350 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -113,6 +113,7 @@ class Document(BaseDocument):
if kwargs:
# init base document
super(Document, self).__init__(kwargs)
+ self.init_child_tables()
self.init_valid_columns()
else:
@@ -150,25 +151,19 @@ class Document(BaseDocument):
super(Document, self).__init__(d)
- if self.name == "DocType" and self.doctype == "DocType":
- from frappe.model.meta import DOCTYPE_TABLE_FIELDS
-
- table_fields = DOCTYPE_TABLE_FIELDS
- else:
- table_fields = self.meta.get_table_fields()
-
- for df in table_fields:
- children = frappe.db.get_values(
- df.options,
- {"parent": self.name, "parenttype": self.doctype, "parentfield": df.fieldname},
- "*",
- as_dict=True,
- order_by="idx asc",
+ for df in self._get_table_fields():
+ children = (
+ frappe.db.get_values(
+ df.options,
+ {"parent": self.name, "parenttype": self.doctype, "parentfield": df.fieldname},
+ "*",
+ as_dict=True,
+ order_by="idx asc",
+ )
+ or []
)
- if children:
- self.set(df.fieldname, children)
- else:
- self.set(df.fieldname, [])
+
+ self.set(df.fieldname, children)
# sometimes __setup__ can depend on child values, hence calling again at the end
if hasattr(self, "__setup__"):
@@ -897,8 +892,7 @@ class Document(BaseDocument):
if parenttype and df.options != parenttype:
continue
- value = self.get(df.fieldname)
- if isinstance(value, list):
+ if value := self.get(df.fieldname):
children.extend(value)
return children
diff --git a/frappe/model/meta.py b/frappe/model/meta.py
index 232d0a8d58..aeb12136ef 100644
--- a/frappe/model/meta.py
+++ b/frappe/model/meta.py
@@ -30,7 +30,11 @@ from frappe.model import (
optional_fields,
table_fields,
)
-from frappe.model.base_document import BaseDocument
+from frappe.model.base_document import (
+ DOCTYPE_TABLE_FIELDS,
+ TABLE_DOCTYPES_FOR_DOCTYPE,
+ BaseDocument,
+)
from frappe.model.document import Document
from frappe.model.workflow import get_workflow_name
from frappe.modules import load_doctype_module
@@ -148,9 +152,9 @@ class Meta(Document):
out[key] = value
# set empty lists for unset table fields
- for table_field in DOCTYPE_TABLE_FIELDS:
- if out.get(table_field.fieldname) is None:
- out[table_field.fieldname] = []
+ for fieldname in TABLE_DOCTYPES_FOR_DOCTYPE.keys():
+ if out.get(fieldname) is None:
+ out[fieldname] = []
return out
@@ -225,13 +229,7 @@ class Meta(Document):
return self._valid_columns
def get_table_field_doctype(self, fieldname):
- return {
- "fields": "DocField",
- "permissions": "DocPerm",
- "actions": "DocType Action",
- "links": "DocType Link",
- "states": "DocType State",
- }.get(fieldname)
+ return TABLE_DOCTYPES_FOR_DOCTYPE.get(fieldname)
def get_field(self, fieldname):
"""Return docfield from meta"""
@@ -642,14 +640,6 @@ class Meta(Document):
return self.has_field("lft") and self.has_field("rgt")
-DOCTYPE_TABLE_FIELDS = [
- frappe._dict({"fieldname": "fields", "options": "DocField"}),
- frappe._dict({"fieldname": "permissions", "options": "DocPerm"}),
- frappe._dict({"fieldname": "actions", "options": "DocType Action"}),
- frappe._dict({"fieldname": "links", "options": "DocType Link"}),
- frappe._dict({"fieldname": "states", "options": "DocType State"}),
-]
-
#######
diff --git a/frappe/model/naming.py b/frappe/model/naming.py
index aa502f5a4c..bb93244a66 100644
--- a/frappe/model/naming.py
+++ b/frappe/model/naming.py
@@ -14,6 +14,11 @@ if TYPE_CHECKING:
from frappe.model.meta import Meta
+# NOTE: This is used to keep track of status of sites
+# whether `log_types` have autoincremented naming set for the site or not.
+autoincremented_site_status_map = {}
+
+
def set_new_name(doc):
"""
Sets the `name` property for the document based on various rules.
@@ -35,9 +40,7 @@ def set_new_name(doc):
doc.name = None
if is_autoincremented(doc.doctype, meta):
- from frappe.database.sequence import get_next_val
-
- doc.name = get_next_val(doc.doctype)
+ doc.name = frappe.db.get_next_sequence_val(doc.doctype)
return
if getattr(doc, "amended_from", None):
@@ -72,12 +75,11 @@ def set_new_name(doc):
doc.name = validate_name(doc.doctype, doc.name, meta.get_field("name_case"))
-def is_autoincremented(doctype: str, meta: "Meta" = None):
+def is_autoincremented(doctype: str, meta: Optional["Meta"] = None) -> bool:
+ """Checks if the doctype has autoincrement autoname set"""
+
if doctype in log_types:
- if (
- frappe.local.autoincremented_status_map.get(frappe.local.site) is None
- or frappe.local.autoincremented_status_map[frappe.local.site] == -1
- ):
+ if autoincremented_site_status_map.get(frappe.local.site) is None:
if (
frappe.db.sql(
f"""select data_type FROM information_schema.columns
@@ -85,22 +87,19 @@ def is_autoincremented(doctype: str, meta: "Meta" = None):
)[0][0]
== "bigint"
):
- frappe.local.autoincremented_status_map[frappe.local.site] = 1
+ autoincremented_site_status_map[frappe.local.site] = 1
return True
else:
- frappe.local.autoincremented_status_map[frappe.local.site] = 0
+ autoincremented_site_status_map[frappe.local.site] = 0
- elif frappe.local.autoincremented_status_map[frappe.local.site]:
+ elif autoincremented_site_status_map[frappe.local.site]:
return True
else:
if not meta:
meta = frappe.get_meta(doctype)
- if getattr(meta, "issingle", False):
- return False
-
- if meta.autoname == "autoincrement":
+ if not getattr(meta, "issingle", False) and meta.autoname == "autoincrement":
return True
return False
@@ -329,11 +328,9 @@ def validate_name(doctype: str, name: Union[int, str], case: Optional[str] = Non
if isinstance(name, int):
if is_autoincremented(doctype):
- from frappe.database.sequence import set_next_val
-
- # this will set the sequence val to be the provided name and set it to be used
- # so that the sequence will start from the next val of the setted val(name)
- set_next_val(doctype, name, is_val_used=True)
+ # this will set the sequence value to be the provided name/value and set it to be used
+ # so that the sequence will start from the next value
+ frappe.db.set_next_sequence_val(doctype, name, is_val_used=True)
return name
frappe.throw(_("Invalid name type (integer) for varchar name column"), frappe.NameError)
diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py
index a0cd10f967..25e471d4b0 100644
--- a/frappe/model/rename_doc.py
+++ b/frappe/model/rename_doc.py
@@ -9,7 +9,6 @@ from frappe.model.dynamic_links import get_dynamic_link_map
from frappe.model.naming import validate_name
from frappe.model.utils.user_settings import sync_user_settings, update_user_settings_data
from frappe.query_builder import Field
-from frappe.query_builder.utils import DocType, Table
from frappe.utils.data import sbool
from frappe.utils.password import rename_password
from frappe.utils.scheduler import is_scheduler_inactive
@@ -198,7 +197,7 @@ def rename_doc(
rename_password(doctype, old, new)
# update user_permissions
- DefaultValue = DocType("DefaultValue")
+ DefaultValue = frappe.qb.DocType("DefaultValue")
frappe.qb.update(DefaultValue).set(DefaultValue.defvalue, new).where(
(DefaultValue.parenttype == "User Permission")
& (DefaultValue.defkey == doctype)
@@ -267,7 +266,7 @@ def update_user_settings(old: str, new: str, link_fields: List[Dict]) -> None:
# find the user settings for the linked doctypes
linked_doctypes = {d.parent for d in link_fields if not d.issingle}
- UserSettings = Table("__UserSettings")
+ UserSettings = frappe.qb.Table("__UserSettings")
user_settings_details = (
frappe.qb.from_(UserSettings)
@@ -299,7 +298,7 @@ def update_customizations(old: str, new: str) -> None:
def update_attachments(doctype: str, old: str, new: str) -> None:
if doctype != "DocType":
- File = DocType("File")
+ File = frappe.qb.DocType("File")
frappe.qb.update(File).set(File.attached_to_name, new).where(
(File.attached_to_name == old) & (File.attached_to_doctype == doctype)
@@ -307,7 +306,7 @@ def update_attachments(doctype: str, old: str, new: str) -> None:
def rename_versions(doctype: str, old: str, new: str) -> None:
- Version = DocType("Version")
+ Version = frappe.qb.DocType("Version")
frappe.qb.update(Version).set(Version.docname, new).where(
(Version.docname == old) & (Version.ref_doctype == doctype)
@@ -315,7 +314,7 @@ def rename_versions(doctype: str, old: str, new: str) -> None:
def rename_eps_records(doctype: str, old: str, new: str) -> None:
- EPL = DocType("Energy Point Log")
+ EPL = frappe.qb.DocType("Energy Point Log")
frappe.qb.update(EPL).set(EPL.reference_name, new).where(
(EPL.reference_doctype == doctype) & (EPL.reference_name == old)
@@ -455,10 +454,10 @@ def get_link_fields(doctype: str) -> List[Dict]:
frappe.flags.link_fields = {}
if doctype not in frappe.flags.link_fields:
- dt = DocType("DocType")
- df = DocType("DocField")
- cf = DocType("Custom Field")
- ps = DocType("Property Setter")
+ dt = frappe.qb.DocType("DocType")
+ df = frappe.qb.DocType("DocField")
+ cf = frappe.qb.DocType("Custom Field")
+ ps = frappe.qb.DocType("Property Setter")
st_issingle = frappe.qb.from_(dt).select(dt.issingle).where(dt.name == df.parent).as_("issingle")
standard_fields = (
@@ -492,8 +491,8 @@ def get_link_fields(doctype: str) -> List[Dict]:
def update_options_for_fieldtype(fieldtype: str, old: str, new: str) -> None:
- CustomField = DocType("Custom Field")
- PropertySetter = DocType("Property Setter")
+ CustomField = frappe.qb.DocType("Custom Field")
+ PropertySetter = frappe.qb.DocType("Property Setter")
if frappe.conf.developer_mode:
for name in frappe.get_all("DocField", filters={"options": old}, pluck="parent"):
@@ -506,7 +505,7 @@ def update_options_for_fieldtype(fieldtype: str, old: str, new: str) -> None:
if save:
doctype.save()
else:
- DocField = DocType("DocField")
+ DocField = frappe.qb.DocType("DocField")
frappe.qb.update(DocField).set(DocField.options, new).where(
(DocField.fieldtype == fieldtype) & (DocField.options == old)
).run()
@@ -525,10 +524,10 @@ def get_select_fields(old: str, new: str) -> List[Dict]:
get select type fields where doctype's name is hardcoded as
new line separated list
"""
- df = DocType("DocField")
- dt = DocType("DocType")
- cf = DocType("Custom Field")
- ps = DocType("Property Setter")
+ df = frappe.qb.DocType("DocField")
+ dt = frappe.qb.DocType("DocType")
+ cf = frappe.qb.DocType("Custom Field")
+ ps = frappe.qb.DocType("Property Setter")
# get link fields from tabDocField
st_issingle = frappe.qb.from_(dt).select(dt.issingle).where(dt.name == df.parent).as_("issingle")
@@ -570,9 +569,9 @@ def get_select_fields(old: str, new: str) -> List[Dict]:
def update_select_field_values(old: str, new: str):
from frappe.query_builder.functions import Replace
- DocField = DocType("DocField")
- CustomField = DocType("Custom Field")
- PropertySetter = DocType("Property Setter")
+ DocField = frappe.qb.DocType("DocField")
+ CustomField = frappe.qb.DocType("Custom Field")
+ PropertySetter = frappe.qb.DocType("Property Setter")
frappe.qb.update(DocField).set(DocField.options, Replace(DocField.options, old, new)).where(
(DocField.fieldtype == "Select")
@@ -623,12 +622,12 @@ def update_parenttype_values(old: str, new: str):
child_doctypes = set(list(d["options"] for d in child_doctypes) + property_setter_child_doctypes)
for doctype in child_doctypes:
- Table = DocType(doctype)
- frappe.qb.update(Table).set(Table.parenttype, new).where(Table.parenttype == old).run()
+ table = frappe.qb.DocType(doctype)
+ frappe.qb.update(table).set(table.parenttype, new).where(table.parenttype == old).run()
def rename_dynamic_links(doctype: str, old: str, new: str):
- Singles = DocType("Singles")
+ Singles = frappe.qb.DocType("Singles")
for df in get_dynamic_link_map().get(doctype, []):
# dynamic link in single, just one value to check
if frappe.get_meta(df.parent).issingle:
diff --git a/frappe/patches/v14_0/update_integration_request.py b/frappe/patches/v14_0/update_integration_request.py
new file mode 100644
index 0000000000..7d491461e3
--- /dev/null
+++ b/frappe/patches/v14_0/update_integration_request.py
@@ -0,0 +1,17 @@
+import frappe
+
+
+def execute():
+ doctype = "Integration Request"
+ frappe.db.set_value(
+ doctype,
+ {"integration_type": "Remote", "integration_request_service": ("!=", "PayPal")},
+ "is_remote_request",
+ 1,
+ )
+ frappe.db.set_value(
+ doctype,
+ {"integration_type": "Subscription Notification"},
+ "request_description",
+ "Subscription Notification",
+ )
diff --git a/frappe/public/js/frappe/form/controls/base_input.js b/frappe/public/js/frappe/form/controls/base_input.js
index b7fe61b385..5a5af389ee 100644
--- a/frappe/public/js/frappe/form/controls/base_input.js
+++ b/frappe/public/js/frappe/form/controls/base_input.js
@@ -65,11 +65,7 @@ frappe.ui.form.ControlInput = class ControlInput extends frappe.ui.form.Control
};
var update_input = function() {
- if (me.doctype && me.docname) {
- me.set_input(me.value);
- } else {
- me.set_input(me.value || null);
- }
+ me.set_input(me.value);
};
if (me.disp_status != "None") {
diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js
index c56ffc592d..2572f7b2e3 100644
--- a/frappe/public/js/frappe/form/form.js
+++ b/frappe/public/js/frappe/form/form.js
@@ -248,7 +248,7 @@ frappe.ui.form.Form = class FrappeForm {
// on main doc
frappe.model.on(me.doctype, "*", function(fieldname, value, doc, skip_dirty_trigger=false) {
// set input
- if (cstr(doc.name) === me.docname) {
+ if (doc.name == me.docname) {
if (!skip_dirty_trigger) {
me.dirty();
}
@@ -273,7 +273,7 @@ frappe.ui.form.Form = class FrappeForm {
// using $.each to preserve df via closure
$.each(table_fields, function(i, df) {
frappe.model.on(df.options, "*", function(fieldname, value, doc) {
- if(doc.parent===me.docname && doc.parentfield===df.fieldname) {
+ if (doc.parent == me.docname && doc.parentfield === df.fieldname) {
me.dirty();
me.fields_dict[df.fieldname].grid.set_value(fieldname, value, doc);
return me.script_manager.trigger(fieldname, doc.doctype, doc.name);
@@ -356,7 +356,7 @@ frappe.ui.form.Form = class FrappeForm {
// check permissions
if (!this.has_read_permission()) {
- frappe.show_not_permitted(__(this.doctype) + " " + __(this.docname));
+ frappe.show_not_permitted(__(this.doctype) + " " + __(cstr(this.docname)));
return;
}
diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js
index 3b95a4b3f1..ad7a1181f6 100644
--- a/frappe/public/js/frappe/model/model.js
+++ b/frappe/public/js/frappe/model/model.js
@@ -403,7 +403,7 @@ $.extend(frappe.model, {
}
});
} else {
- if(typeof filters==="string" && locals[doctype] && locals[doctype][filters]) {
+ if (["number", "string"].includes(typeof filters) && locals[doctype] && locals[doctype][filters]) {
return locals[doctype][filters][fieldname];
} else {
var l = frappe.get_list(doctype, filters);
diff --git a/frappe/public/js/frappe/ui/slides.js b/frappe/public/js/frappe/ui/slides.js
index f79f54b786..100dd13610 100644
--- a/frappe/public/js/frappe/ui/slides.js
+++ b/frappe/public/js/frappe/ui/slides.js
@@ -105,8 +105,8 @@ frappe.ui.Slide = class Slide {
});
}
- set_values() {
- this.values = this.form.get_values();
+ set_values(ignore_errors) {
+ this.values = this.form.get_values(ignore_errors);
if (this.values === null) {
return false;
}
@@ -309,6 +309,8 @@ frappe.ui.Slides = class Slides {
// Can be called by a slide to update states
this.$slide_progress.empty();
+ if (this.slides.length <= 1) return
+
this.slides.map((slide, id) => {
let $dot = $(`
diff --git a/frappe/public/js/frappe/ui/sort_selector.js b/frappe/public/js/frappe/ui/sort_selector.js
index 879466e8f7..837454ed09 100644
--- a/frappe/public/js/frappe/ui/sort_selector.js
+++ b/frappe/public/js/frappe/ui/sort_selector.js
@@ -132,7 +132,7 @@ frappe.ui.SortSelector = class SortSelector {
// bold, mandatory and fields that are available in list view
meta.fields.forEach(function(df) {
if (
- (df.mandatory || df.bold || df.in_list_view)
+ (df.mandatory || df.bold || df.in_list_view || df.reqd)
&& frappe.model.is_value_type(df.fieldtype)
&& frappe.perm.has_perm(me.doctype, df.permlevel, "read")
) {
diff --git a/frappe/public/js/frappe/utils/dashboard_utils.js b/frappe/public/js/frappe/utils/dashboard_utils.js
index 85a4048a47..c4b094b216 100644
--- a/frappe/public/js/frappe/utils/dashboard_utils.js
+++ b/frappe/public/js/frappe/utils/dashboard_utils.js
@@ -249,7 +249,7 @@ frappe.dashboard_utils = {
{args: values}
).then(()=> {
let dashboard_route_html =
- `${values.dashboard}`;
+ `${values.dashboard}`;
let message =
__("{0} {1} added to Dashboard {2}", [doctype, values.name, dashboard_route_html]);
diff --git a/frappe/templates/signup.html b/frappe/templates/signup.html
index abc1328e25..7d93bdec85 100644
--- a/frappe/templates/signup.html
+++ b/frappe/templates/signup.html
@@ -3,12 +3,12 @@