Merge branch 'develop' into map-read-only
This commit is contained in:
commit
64d1e6492b
42 changed files with 439 additions and 93 deletions
|
|
@ -34,3 +34,6 @@ c0c5b2ebdddbe8898ce2d5e5365f4931ff73b6bf
|
|||
|
||||
# db.get_all -> get_all
|
||||
2eec621e95564c359ad22da79501a855c1f32b03
|
||||
|
||||
# minor formatting fix in `user.py`
|
||||
f223bc02490902dfcc32892058f13f343d51fbaf
|
||||
|
|
|
|||
|
|
@ -1049,18 +1049,26 @@ def reset_metadata_version():
|
|||
|
||||
def new_doc(
|
||||
doctype: str,
|
||||
*,
|
||||
parent_doc: Optional["Document"] = None,
|
||||
parentfield: str | None = None,
|
||||
as_dict: bool = False,
|
||||
**kwargs,
|
||||
) -> "Document":
|
||||
"""Returns a new document of the given DocType with defaults set.
|
||||
|
||||
:param doctype: DocType of the new document.
|
||||
:param parent_doc: [optional] add to parent document.
|
||||
:param parentfield: [optional] add against this `parentfield`."""
|
||||
:param parentfield: [optional] add against this `parentfield`.
|
||||
:param as_dict: [optional] return as dictionary instead of Document.
|
||||
:param kwargs: [optional] You can specify fields as field=value pairs in function call.
|
||||
"""
|
||||
|
||||
from frappe.model.create_new import get_new_doc
|
||||
|
||||
return get_new_doc(doctype, parent_doc, parentfield, as_dict=as_dict)
|
||||
new_doc = get_new_doc(doctype, parent_doc, parentfield, as_dict=as_dict)
|
||||
|
||||
return new_doc.update(kwargs)
|
||||
|
||||
|
||||
def set_value(doctype, docname, fieldname, value=None):
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import frappe.recorder
|
|||
import frappe.utils.response
|
||||
from frappe import _
|
||||
from frappe.auth import SAFE_HTTP_METHODS, UNSAFE_HTTP_METHODS, HTTPRequest
|
||||
from frappe.core.doctype.comment.comment import update_comments_in_parent_after_request
|
||||
from frappe.middlewares import StaticDataMiddleware
|
||||
from frappe.utils import cint, get_site_name, sanitize_html
|
||||
from frappe.utils.error import make_error_snapshot
|
||||
|
|
@ -351,8 +350,6 @@ def sync_database(rollback: bool) -> bool:
|
|||
frappe.db.commit()
|
||||
rollback = False
|
||||
|
||||
update_comments_in_parent_after_request()
|
||||
|
||||
return rollback
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -152,14 +152,9 @@ def update_comments_in_parent(reference_doctype, reference_name, _comments):
|
|||
|
||||
except Exception as e:
|
||||
if frappe.db.is_column_missing(e) and getattr(frappe.local, "request", None):
|
||||
# missing column and in request, add column and update after commit
|
||||
frappe.local._comments = getattr(frappe.local, "_comments", []) + [
|
||||
(reference_doctype, reference_name, _comments)
|
||||
]
|
||||
|
||||
pass
|
||||
elif frappe.db.is_data_too_long(e):
|
||||
raise frappe.DataTooLongException
|
||||
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
|
|
@ -169,13 +164,3 @@ def update_comments_in_parent(reference_doctype, reference_name, _comments):
|
|||
# Clear route cache
|
||||
if route := frappe.get_cached_value(reference_doctype, reference_name, "route"):
|
||||
clear_cache(route)
|
||||
|
||||
|
||||
def update_comments_in_parent_after_request():
|
||||
"""update _comments in parent if _comments column is missing"""
|
||||
if hasattr(frappe.local, "_comments"):
|
||||
for (reference_doctype, reference_name, _comments) in frappe.local._comments:
|
||||
add_column(reference_doctype, "_comments", "Text")
|
||||
update_comments_in_parent(reference_doctype, reference_name, _comments)
|
||||
|
||||
frappe.db.commit()
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ from frappe.model.meta import Meta
|
|||
from frappe.modules import get_doc_path, make_boilerplate
|
||||
from frappe.modules.import_file import get_file_path
|
||||
from frappe.query_builder.functions import Concat
|
||||
from frappe.utils import cint, random_string
|
||||
from frappe.utils import cint, flt, random_string
|
||||
from frappe.website.utils import clear_cache
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -1751,3 +1751,14 @@ def get_field(doc, fieldname):
|
|||
for field in doc.fields:
|
||||
if field.fieldname == fieldname:
|
||||
return field
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_row_size_utilization(doctype: str) -> float:
|
||||
"""Get row size utilization in percentage"""
|
||||
|
||||
frappe.has_permission("DocType", throw=True)
|
||||
try:
|
||||
return flt(frappe.db.get_row_size(doctype) / frappe.db.MAX_ROW_SIZE_LIMIT * 100, 2)
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
|
|
|||
|
|
@ -4,5 +4,9 @@
|
|||
frappe.ui.form.on("Patch Log", {
|
||||
refresh: function (frm) {
|
||||
frm.disable_save();
|
||||
|
||||
frm.add_custom_button(__("Re-Run Patch"), () => {
|
||||
frm.call("rerun_patch");
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,11 +4,20 @@
|
|||
# License: MIT. See LICENSE
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class PatchLog(Document):
|
||||
pass
|
||||
@frappe.whitelist()
|
||||
def rerun_patch(self):
|
||||
from frappe.modules.patch_handler import run_single
|
||||
|
||||
if not frappe.conf.developer_mode:
|
||||
frappe.throw(_("Re-running patch is only allowed in developer mode."))
|
||||
|
||||
run_single(self.patch, force=True)
|
||||
frappe.msgprint(_("Successfully re-ran patch: {0}").format(self.patch), alert=True)
|
||||
|
||||
|
||||
def before_migrate():
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@
|
|||
"disable_standard_email_footer",
|
||||
"hide_footer_in_auto_email_reports",
|
||||
"attach_view_link",
|
||||
"welcome_email_template",
|
||||
"reset_password_template",
|
||||
"prepared_report_section",
|
||||
"max_auto_email_report_per_user",
|
||||
"system_updates_section",
|
||||
|
|
@ -549,12 +551,24 @@
|
|||
"fieldname": "enable_telemetry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Sending Usage Data for Improving Applications"
|
||||
},
|
||||
{
|
||||
"fieldname": "welcome_email_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Welcome Email Template",
|
||||
"options": "Email Template"
|
||||
},
|
||||
{
|
||||
"fieldname": "reset_password_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reset Password Template",
|
||||
"options": "Email Template"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-23 11:14:59.302851",
|
||||
"modified": "2023-05-25 13:02:54.808773",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "System Settings",
|
||||
|
|
|
|||
|
|
@ -329,7 +329,16 @@ class User(Document):
|
|||
return (self.first_name or "") + (self.first_name and " " or "") + (self.last_name or "")
|
||||
|
||||
def password_reset_mail(self, link):
|
||||
self.send_login_mail(_("Password Reset"), "password_reset", {"link": link}, now=True)
|
||||
|
||||
reset_password_template = frappe.db.get_system_setting("reset_password_template")
|
||||
|
||||
self.send_login_mail(
|
||||
_("Password Reset"),
|
||||
"password_reset",
|
||||
{"link": link},
|
||||
now=True,
|
||||
custom_template=reset_password_template,
|
||||
)
|
||||
|
||||
def send_welcome_mail_to_user(self):
|
||||
from frappe.utils import get_url
|
||||
|
|
@ -346,6 +355,8 @@ class User(Document):
|
|||
else:
|
||||
subject = _("Complete Registration")
|
||||
|
||||
welcome_email_template = frappe.db.get_system_setting("welcome_email_template")
|
||||
|
||||
self.send_login_mail(
|
||||
subject,
|
||||
"new_user",
|
||||
|
|
@ -353,9 +364,10 @@ class User(Document):
|
|||
link=link,
|
||||
site_url=get_url(),
|
||||
),
|
||||
custom_template=welcome_email_template,
|
||||
)
|
||||
|
||||
def send_login_mail(self, subject, template, add_args, now=None):
|
||||
def send_login_mail(self, subject, template, add_args, now=None, custom_template=None):
|
||||
"""send mail with login details"""
|
||||
from frappe.utils import get_url
|
||||
from frappe.utils.user import get_user_fullname
|
||||
|
|
@ -378,11 +390,19 @@ class User(Document):
|
|||
frappe.session.user not in STANDARD_USERS and get_formatted_email(frappe.session.user) or None
|
||||
)
|
||||
|
||||
if custom_template:
|
||||
from frappe.email.doctype.email_template.email_template import get_email_template
|
||||
|
||||
email_template = get_email_template(custom_template, args)
|
||||
subject = email_template.get("subject")
|
||||
content = email_template.get("message")
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=self.email,
|
||||
sender=sender,
|
||||
subject=subject,
|
||||
template=template,
|
||||
template=template if not custom_template else None,
|
||||
content=content if custom_template else None,
|
||||
args=args,
|
||||
header=[subject, "green"],
|
||||
delayed=(not now) if now is not None else self.flags.delay_emails,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
# Copyright (c) 2018, Frappe Technologies and contributors
|
||||
# License: MIT. See LICENSE
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class ViewLog(Document):
|
||||
pass
|
||||
@staticmethod
|
||||
def clear_old_logs(days=180):
|
||||
from frappe.query_builder import Interval
|
||||
from frappe.query_builder.functions import Now
|
||||
|
||||
table = frappe.qb.DocType("View Log")
|
||||
frappe.db.delete(table, filters=(table.modified < (Now() - Interval(days=days))))
|
||||
|
|
|
|||
|
|
@ -172,7 +172,18 @@ class CustomizeForm(Document):
|
|||
check_email_append_to(self)
|
||||
|
||||
if self.flags.update_db:
|
||||
frappe.db.updatedb(self.doc_type)
|
||||
try:
|
||||
frappe.db.updatedb(self.doc_type)
|
||||
except Exception as e:
|
||||
if frappe.db.is_db_table_size_limit(e):
|
||||
frappe.throw(
|
||||
_("You have hit the row size limit on database table: {0}").format(
|
||||
"<a href='https://docs.erpnext.com/docs/v14/user/manual/en/customize-erpnext/articles/maximum-number-of-fields-in-a-form'>"
|
||||
"Maximum Number of Fields in a Form</a>"
|
||||
),
|
||||
title=_("Database Table Row Size Limit"),
|
||||
)
|
||||
raise
|
||||
|
||||
if not hasattr(self, "hide_success") or not self.hide_success:
|
||||
frappe.msgprint(_("{0} updated").format(_(self.doc_type)), alert=True)
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ class Database:
|
|||
|
||||
self.password = password or frappe.conf.db_password
|
||||
self.value_cache = {}
|
||||
self.logger = frappe.logger("database")
|
||||
self.logger.setLevel("WARNING")
|
||||
# self.db_type: str
|
||||
# self.last_query (lazy) attribute of last sql query executed
|
||||
|
||||
|
|
@ -122,7 +124,7 @@ class Database:
|
|||
if execution_timeout := get_query_execution_timeout():
|
||||
self.set_execution_timeout(execution_timeout)
|
||||
except Exception as e:
|
||||
frappe.logger("database").warning(f"Couldn't set execution timeout {e}")
|
||||
self.logger.warning(f"Couldn't set execution timeout {e}")
|
||||
|
||||
def set_execution_timeout(self, seconds: int):
|
||||
"""Set session speicifc timeout on exeuction of statements.
|
||||
|
|
@ -285,7 +287,13 @@ class Database:
|
|||
return self.convert_to_lists(self.last_result)
|
||||
return self.last_result
|
||||
|
||||
def _log_query(self, mogrified_query: str, debug: bool = False, explain: bool = False) -> None:
|
||||
def _log_query(
|
||||
self,
|
||||
mogrified_query: str,
|
||||
debug: bool = False,
|
||||
explain: bool = False,
|
||||
unmogrified_query: str = "",
|
||||
) -> None:
|
||||
"""Takes the query and logs it to various interfaces according to the settings."""
|
||||
_query = None
|
||||
|
||||
|
|
@ -303,6 +311,12 @@ class Database:
|
|||
_query = _query or str(mogrified_query)
|
||||
frappe.log(f"<<<< query\n{_query}\n>>>>")
|
||||
|
||||
if unmogrified_query and is_query_type(
|
||||
unmogrified_query, ("alter", "drop", "create", "truncate", "rename")
|
||||
):
|
||||
_query = _query or str(mogrified_query)
|
||||
self.logger.warning("DDL Query made to DB:\n" + _query)
|
||||
|
||||
if frappe.flags.in_migrate:
|
||||
_query = _query or str(mogrified_query)
|
||||
self.log_touched_tables(_query)
|
||||
|
|
@ -314,7 +328,7 @@ class Database:
|
|||
# like cursor._transformed_statement from the cursor object. We can also avoid setting
|
||||
# mogrified_query if we don't need to log it.
|
||||
mogrified_query = self.lazy_mogrify(query, values)
|
||||
self._log_query(mogrified_query, debug, explain)
|
||||
self._log_query(mogrified_query, debug, explain, unmogrified_query=query)
|
||||
return mogrified_query
|
||||
|
||||
def mogrify(self, query: Query, values: QueryValues):
|
||||
|
|
@ -812,6 +826,7 @@ class Database:
|
|||
fields=fields,
|
||||
distinct=distinct,
|
||||
limit=limit,
|
||||
validate_filters=True,
|
||||
)
|
||||
if isinstance(fields, str) and fields == "*":
|
||||
as_dict = True
|
||||
|
|
@ -840,6 +855,7 @@ class Database:
|
|||
order_by=order_by,
|
||||
distinct=distinct,
|
||||
limit=limit,
|
||||
validate_filters=True,
|
||||
).run(debug=debug, run=run, as_dict=as_dict, pluck=pluck)
|
||||
return {}
|
||||
|
||||
|
|
@ -889,7 +905,12 @@ class Database:
|
|||
field, val, modified=modified, modified_by=modified_by, update_modified=update_modified
|
||||
)
|
||||
|
||||
query = frappe.qb.get_query(table=dt, filters=dn, update=True)
|
||||
query = frappe.qb.get_query(
|
||||
table=dt,
|
||||
filters=dn,
|
||||
update=True,
|
||||
validate_filters=True,
|
||||
)
|
||||
|
||||
if isinstance(dn, str):
|
||||
frappe.clear_document_cache(dt, dn)
|
||||
|
|
@ -1057,9 +1078,13 @@ class Database:
|
|||
cache_count = frappe.cache().get_value(f"doctype:count:{dt}")
|
||||
if cache_count is not None:
|
||||
return cache_count
|
||||
count = frappe.qb.get_query(table=dt, filters=filters, fields=Count("*"), distinct=distinct).run(
|
||||
debug=debug
|
||||
)[0][0]
|
||||
count = frappe.qb.get_query(
|
||||
table=dt,
|
||||
filters=filters,
|
||||
fields=Count("*"),
|
||||
distinct=distinct,
|
||||
validate_filters=True,
|
||||
).run(debug=debug)[0][0]
|
||||
if not filters and cache:
|
||||
frappe.cache().set_value(f"doctype:count:{dt}", count, expires_in_sec=86400)
|
||||
return count
|
||||
|
|
@ -1179,7 +1204,12 @@ class Database:
|
|||
Doctype name can be passed directly, it will be pre-pended with `tab`.
|
||||
"""
|
||||
filters = filters or kwargs.get("conditions")
|
||||
query = frappe.qb.get_query(table=doctype, filters=filters, delete=True)
|
||||
query = frappe.qb.get_query(
|
||||
table=doctype,
|
||||
filters=filters,
|
||||
delete=True,
|
||||
validate_filters=True,
|
||||
)
|
||||
if "debug" not in kwargs:
|
||||
kwargs["debug"] = debug
|
||||
return query.run(**kwargs)
|
||||
|
|
@ -1269,6 +1299,10 @@ class Database:
|
|||
|
||||
return get_next_val(*args, **kwargs)
|
||||
|
||||
def get_row_size(self, doctype: str) -> int:
|
||||
"""Get estimated max row size of any table in bytes."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def enqueue_jobs_after_commit():
|
||||
from frappe.utils.background_jobs import (
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ class MariaDBExceptionUtil:
|
|||
def is_data_too_long(e: pymysql.Error) -> bool:
|
||||
return e.args[0] == ER.DATA_TOO_LONG
|
||||
|
||||
@staticmethod
|
||||
def is_db_table_size_limit(e: pymysql.Error) -> bool:
|
||||
return e.args[0] == ER.TOO_BIG_ROWSIZE
|
||||
|
||||
@staticmethod
|
||||
def is_primary_key_violation(e: pymysql.Error) -> bool:
|
||||
return (
|
||||
|
|
@ -145,6 +149,7 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
|||
UnicodeWithAttrs: escape_string,
|
||||
}
|
||||
default_port = "3306"
|
||||
MAX_ROW_SIZE_LIMIT = 65_535 # bytes
|
||||
|
||||
def setup_type_map(self):
|
||||
self.db_type = "mariadb"
|
||||
|
|
@ -200,8 +205,8 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
|||
return db_size[0].get("database_size")
|
||||
|
||||
def log_query(self, query, values, debug, explain):
|
||||
self.last_query = query = self._cursor._executed
|
||||
self._log_query(query, debug, explain)
|
||||
self.last_query = self._cursor._executed
|
||||
self._log_query(self.last_query, debug, explain, query)
|
||||
return self.last_query
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -445,3 +450,56 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
|||
frappe.cache().set_value("db_tables", tables)
|
||||
|
||||
return tables
|
||||
|
||||
def get_row_size(self, doctype: str) -> int:
|
||||
"""Get estimated max row size of any table in bytes."""
|
||||
|
||||
# Query reused from this answer: https://dba.stackexchange.com/a/313889/274503
|
||||
# Modification: get values for particular table instead of full summary.
|
||||
# Reference: https://mariadb.com/kb/en/data-type-storage-requirements/
|
||||
|
||||
est_row_size = frappe.db.sql(
|
||||
"""
|
||||
SELECT SUM(col_sizes.col_size) AS EST_MAX_ROW_SIZE
|
||||
FROM (
|
||||
SELECT
|
||||
cols.COLUMN_NAME,
|
||||
CASE cols.DATA_TYPE
|
||||
WHEN 'tinyint' THEN 1
|
||||
WHEN 'smallint' THEN 2
|
||||
WHEN 'mediumint' THEN 3
|
||||
WHEN 'int' THEN 4
|
||||
WHEN 'bigint' THEN 8
|
||||
WHEN 'float' THEN IF(cols.NUMERIC_PRECISION > 24, 8, 4)
|
||||
WHEN 'double' THEN 8
|
||||
WHEN 'decimal' THEN ((cols.NUMERIC_PRECISION - cols.NUMERIC_SCALE) DIV 9)*4 + (cols.NUMERIC_SCALE DIV 9)*4 + CEIL(MOD(cols.NUMERIC_PRECISION - cols.NUMERIC_SCALE,9)/2) + CEIL(MOD(cols.NUMERIC_SCALE,9)/2)
|
||||
WHEN 'bit' THEN (cols.NUMERIC_PRECISION + 7) DIV 8
|
||||
WHEN 'year' THEN 1
|
||||
WHEN 'date' THEN 3
|
||||
WHEN 'time' THEN 3 + CEIL(cols.DATETIME_PRECISION /2)
|
||||
WHEN 'datetime' THEN 5 + CEIL(cols.DATETIME_PRECISION /2)
|
||||
WHEN 'timestamp' THEN 4 + CEIL(cols.DATETIME_PRECISION /2)
|
||||
WHEN 'char' THEN cols.CHARACTER_OCTET_LENGTH
|
||||
WHEN 'binary' THEN cols.CHARACTER_OCTET_LENGTH
|
||||
WHEN 'varchar' THEN IF(cols.CHARACTER_OCTET_LENGTH > 255, 2, 1) + cols.CHARACTER_OCTET_LENGTH
|
||||
WHEN 'varbinary' THEN IF(cols.CHARACTER_OCTET_LENGTH > 255, 2, 1) + cols.CHARACTER_OCTET_LENGTH
|
||||
WHEN 'tinyblob' THEN 9
|
||||
WHEN 'tinytext' THEN 9
|
||||
WHEN 'blob' THEN 10
|
||||
WHEN 'text' THEN 10
|
||||
WHEN 'mediumblob' THEN 11
|
||||
WHEN 'mediumtext' THEN 11
|
||||
WHEN 'longblob' THEN 12
|
||||
WHEN 'longtext' THEN 12
|
||||
WHEN 'enum' THEN 2
|
||||
WHEN 'set' THEN 8
|
||||
ELSE 0
|
||||
END AS col_size
|
||||
FROM INFORMATION_SCHEMA.COLUMNS cols
|
||||
WHERE cols.TABLE_NAME = %s
|
||||
) AS col_sizes;""",
|
||||
(get_table_name(doctype),),
|
||||
)
|
||||
|
||||
if est_row_size:
|
||||
return int(est_row_size[0][0])
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ class PostgresExceptionUtil:
|
|||
def is_data_too_long(e):
|
||||
return getattr(e, "pgcode", None) == STRING_DATA_RIGHT_TRUNCATION
|
||||
|
||||
@staticmethod
|
||||
def is_db_table_size_limit(e) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class PostgresDatabase(PostgresExceptionUtil, Database):
|
||||
REGEX_CHARACTER = "~"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from pypika.queries import QueryBuilder, Table
|
|||
import frappe
|
||||
from frappe import _
|
||||
from frappe.database.operator_map import OPERATOR_MAP
|
||||
from frappe.database.schema import SPECIAL_CHAR_PATTERN
|
||||
from frappe.database.utils import DefaultOrderBy, get_doctype_name
|
||||
from frappe.query_builder import Criterion, Field, Order, functions
|
||||
from frappe.query_builder.functions import Function, SqlFunctions
|
||||
|
|
@ -44,9 +45,12 @@ class Engine:
|
|||
update: bool = False,
|
||||
into: bool = False,
|
||||
delete: bool = False,
|
||||
*,
|
||||
validate_filters: bool = False,
|
||||
) -> QueryBuilder:
|
||||
self.is_mariadb = frappe.db.db_type == "mariadb"
|
||||
self.is_postgres = frappe.db.db_type == "postgres"
|
||||
self.validate_filters = validate_filters
|
||||
|
||||
if isinstance(table, Table):
|
||||
self.table = table
|
||||
|
|
@ -157,14 +161,16 @@ class Engine:
|
|||
_value = value
|
||||
_operator = operator
|
||||
|
||||
if isinstance(_field, Field):
|
||||
if not isinstance(_field, str):
|
||||
pass
|
||||
elif dynamic_field := DynamicTableField.parse(field, self.doctype):
|
||||
elif not self.validate_filters and (
|
||||
dynamic_field := DynamicTableField.parse(field, self.doctype)
|
||||
):
|
||||
# apply implicit join if link field's field is referenced
|
||||
self.query = dynamic_field.apply_join(self.query)
|
||||
_field = dynamic_field.field
|
||||
elif has_function(field):
|
||||
_field = self.get_function_object(field)
|
||||
elif self.validate_filters and SPECIAL_CHAR_PATTERN.search(_field):
|
||||
frappe.throw(_("Invalid filter: {0}").format(_field))
|
||||
elif not doctype or doctype == self.doctype:
|
||||
_field = self.table[field]
|
||||
elif doctype:
|
||||
|
|
|
|||
|
|
@ -202,7 +202,11 @@ def get_cards_for_user(doctype, txt, searchfield, start, page_len, filters):
|
|||
if txt:
|
||||
search_conditions = [numberCard[field].like(f"%{txt}%") for field in searchfields]
|
||||
|
||||
condition_query = frappe.qb.get_query(doctype, filters=filters)
|
||||
condition_query = frappe.qb.get_query(
|
||||
doctype,
|
||||
filters=filters,
|
||||
validate_filters=True,
|
||||
)
|
||||
|
||||
return (
|
||||
condition_query.select(numberCard.name, numberCard.label, numberCard.document_type)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,12 @@ def get_group_by_count(doctype: str, current_filters: str, field: str) -> list[d
|
|||
ToDo = DocType("ToDo")
|
||||
User = DocType("User")
|
||||
count = Count("*").as_("count")
|
||||
filtered_records = frappe.qb.get_query(doctype, filters=current_filters, fields=["name"])
|
||||
filtered_records = frappe.qb.get_query(
|
||||
doctype,
|
||||
filters=current_filters,
|
||||
fields=["name"],
|
||||
validate_filters=True,
|
||||
)
|
||||
|
||||
return (
|
||||
frappe.qb.from_(ToDo)
|
||||
|
|
|
|||
|
|
@ -206,3 +206,59 @@ class TestWebhook(FrappeTestCase):
|
|||
enqueue_webhook(doc, wh)
|
||||
log = frappe.get_last_doc("Webhook Request Log")
|
||||
self.assertEqual(len(json.loads(log.response)["json"]), 3)
|
||||
|
||||
def test_webhook_with_dynamic_url_enabled(self):
|
||||
wh_config = {
|
||||
"doctype": "Webhook",
|
||||
"webhook_doctype": "Note",
|
||||
"webhook_docevent": "after_insert",
|
||||
"enabled": 1,
|
||||
"request_url": "https://httpbin.org/anything/{{ doc.doctype }}",
|
||||
"is_dynamic_url": 1,
|
||||
"request_method": "POST",
|
||||
"request_structure": "JSON",
|
||||
"webhook_json": "{}",
|
||||
"meets_condition": "Yes",
|
||||
"webhook_headers": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
with get_test_webhook(wh_config) as wh:
|
||||
doc = frappe.new_doc("Note")
|
||||
doc.title = "Test Webhook Note"
|
||||
enqueue_webhook(doc, wh)
|
||||
log = frappe.get_last_doc("Webhook Request Log")
|
||||
self.assertEqual(json.loads(log.response)["url"], "https://httpbin.org/anything/Note")
|
||||
|
||||
def test_webhook_with_dynamic_url_disabled(self):
|
||||
wh_config = {
|
||||
"doctype": "Webhook",
|
||||
"webhook_doctype": "Note",
|
||||
"webhook_docevent": "after_insert",
|
||||
"enabled": 1,
|
||||
"request_url": "https://httpbin.org/anything/{{doc.doctype}}",
|
||||
"is_dynamic_url": 0,
|
||||
"request_method": "POST",
|
||||
"request_structure": "JSON",
|
||||
"webhook_json": "{}",
|
||||
"meets_condition": "Yes",
|
||||
"webhook_headers": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
with get_test_webhook(wh_config) as wh:
|
||||
doc = frappe.new_doc("Note")
|
||||
doc.title = "Test Webhook Note"
|
||||
enqueue_webhook(doc, wh)
|
||||
log = frappe.get_last_doc("Webhook Request Log")
|
||||
self.assertEqual(
|
||||
json.loads(log.response)["url"], "https://httpbin.org/anything/{{doc.doctype}}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@
|
|||
"html_condition",
|
||||
"sb_webhook",
|
||||
"request_url",
|
||||
"request_method",
|
||||
"is_dynamic_url",
|
||||
"cb_webhook",
|
||||
"request_method",
|
||||
"request_structure",
|
||||
"sb_security",
|
||||
"enable_security",
|
||||
|
|
@ -202,6 +203,13 @@
|
|||
{
|
||||
"fieldname": "section_break_28",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "On checking this option, URL will be treated like a jinja template string",
|
||||
"fieldname": "is_dynamic_url",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Dynamic URL?"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
|
|
@ -210,7 +218,7 @@
|
|||
"link_fieldname": "webhook"
|
||||
}
|
||||
],
|
||||
"modified": "2023-05-21 15:42:58.844826",
|
||||
"modified": "2023-05-22 16:30:10.740512",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Webhook",
|
||||
|
|
|
|||
|
|
@ -115,29 +115,34 @@ def enqueue_webhook(doc, webhook) -> None:
|
|||
webhook: Webhook = frappe.get_doc("Webhook", webhook.get("name"))
|
||||
headers = get_webhook_headers(doc, webhook)
|
||||
data = get_webhook_data(doc, webhook)
|
||||
r = None
|
||||
|
||||
if webhook.is_dynamic_url:
|
||||
request_url = frappe.render_template(webhook.request_url, get_context(doc))
|
||||
else:
|
||||
request_url = webhook.request_url
|
||||
|
||||
r = None
|
||||
for i in range(3):
|
||||
try:
|
||||
r = requests.request(
|
||||
method=webhook.request_method,
|
||||
url=webhook.request_url,
|
||||
url=request_url,
|
||||
data=json.dumps(data, default=str),
|
||||
headers=headers,
|
||||
timeout=5,
|
||||
)
|
||||
r.raise_for_status()
|
||||
frappe.logger().debug({"webhook_success": r.text})
|
||||
log_request(webhook.name, doc.name, webhook.request_url, headers, data, r)
|
||||
log_request(webhook.name, doc.name, request_url, headers, data, r)
|
||||
break
|
||||
|
||||
except requests.exceptions.ReadTimeout as e:
|
||||
frappe.logger().debug({"webhook_error": e, "try": i + 1})
|
||||
log_request(webhook.name, doc.name, webhook.request_url, headers, data)
|
||||
log_request(webhook.name, doc.name, request_url, headers, data)
|
||||
|
||||
except Exception as e:
|
||||
frappe.logger().debug({"webhook_error": e, "try": i + 1})
|
||||
log_request(webhook.name, doc.name, webhook.request_url, headers, data, r)
|
||||
log_request(webhook.name, doc.name, request_url, headers, data, r)
|
||||
sleep(3 * i + 1)
|
||||
if i != 2:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -530,7 +530,7 @@ class BaseDocument:
|
|||
|
||||
if not ignore_if_duplicate:
|
||||
frappe.msgprint(
|
||||
_("{0} {1} already exists").format(self.doctype, frappe.bold(self.name)),
|
||||
_("{0} {1} already exists").format(_(self.doctype), frappe.bold(self.name)),
|
||||
title=_("Duplicate Name"),
|
||||
indicator="red",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ def get_mapped_doc(
|
|||
postprocess=None,
|
||||
ignore_permissions=False,
|
||||
ignore_child_tables=False,
|
||||
cached=False,
|
||||
):
|
||||
|
||||
apply_strict_user_permissions = frappe.get_system_settings("apply_strict_user_permissions")
|
||||
|
|
@ -79,7 +80,10 @@ def get_mapped_doc(
|
|||
):
|
||||
target_doc.raise_no_permission_to("create")
|
||||
|
||||
source_doc = frappe.get_doc(from_doctype, from_docname)
|
||||
if cached:
|
||||
source_doc = frappe.get_cached_doc(from_doctype, from_docname)
|
||||
else:
|
||||
source_doc = frappe.get_doc(from_doctype, from_docname)
|
||||
|
||||
if not ignore_permissions:
|
||||
if not source_doc.has_permission("read"):
|
||||
|
|
@ -255,7 +259,9 @@ def map_fetch_fields(target_doc, df, no_copy_fields):
|
|||
def map_child_doc(source_d, target_parent, table_map, source_parent=None):
|
||||
target_child_doctype = table_map["doctype"]
|
||||
target_parentfield = target_parent.get_parentfield_of_doctype(target_child_doctype)
|
||||
target_d = frappe.new_doc(target_child_doctype, target_parent, target_parentfield)
|
||||
target_d = frappe.new_doc(
|
||||
target_child_doctype, parent_doc=target_parent, parentfield=target_parentfield
|
||||
)
|
||||
|
||||
map_doc(source_d, target_d, table_map, source_parent)
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ def execute():
|
|||
if field:
|
||||
field.update(cf)
|
||||
else:
|
||||
df = frappe.new_doc("DocField", meta, "fields")
|
||||
df = frappe.new_doc("DocField", parent_doc=meta, parentfield="fields")
|
||||
df.update(cf)
|
||||
meta.fields.append(df)
|
||||
frappe.db.delete("Custom Field", {"name": cf.name})
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@
|
|||
"default": "14",
|
||||
"fieldname": "font_size",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 1,
|
||||
"label": "Font Size"
|
||||
},
|
||||
{
|
||||
|
|
@ -258,7 +259,7 @@
|
|||
"icon": "fa fa-print",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-09 15:29:46.709305",
|
||||
"modified": "2023-05-31 15:40:52.919029",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Printing",
|
||||
"name": "Print Format",
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
"default": "1",
|
||||
"fieldname": "repeat_header_footer",
|
||||
"fieldtype": "Check",
|
||||
"label": "Repeat Header and Footer in PDF"
|
||||
"label": "Repeat Header and Footer"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
|
|
@ -176,7 +176,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-17 12:59:14.783694",
|
||||
"modified": "2023-05-30 14:55:25.740691",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Printing",
|
||||
"name": "Print Settings",
|
||||
|
|
@ -193,5 +193,6 @@
|
|||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ frappe.ui.form.PrintView = class {
|
|||
fieldtype: "Link",
|
||||
fieldname: "print_format",
|
||||
options: "Print Format",
|
||||
placeholder: __("Print Format"),
|
||||
label: __("Print Format"),
|
||||
get_query: () => {
|
||||
return { filters: { doc_type: this.frm.doctype } };
|
||||
},
|
||||
|
|
@ -101,7 +101,7 @@ frappe.ui.form.PrintView = class {
|
|||
this.language_selector = this.add_sidebar_item({
|
||||
fieldtype: "Link",
|
||||
fieldname: "language",
|
||||
placeholder: __("Language"),
|
||||
label: __("Language"),
|
||||
options: "Language",
|
||||
change: () => {
|
||||
this.set_user_lang();
|
||||
|
|
@ -109,12 +109,27 @@ frappe.ui.form.PrintView = class {
|
|||
},
|
||||
}).$input;
|
||||
|
||||
let description = "";
|
||||
if (!cint(this.print_settings.repeat_header_footer)) {
|
||||
description =
|
||||
"<div class='form-message yellow p-3 mt-3'>" +
|
||||
__("Footer might not be visible as {0} option is disabled</div>", [
|
||||
`<a href="/app/print-settings/Print Settings">${__(
|
||||
"Repeat Header and Footer"
|
||||
)}</a>`,
|
||||
]);
|
||||
}
|
||||
const print_view = this;
|
||||
this.letterhead_selector = this.add_sidebar_item({
|
||||
fieldtype: "Link",
|
||||
fieldname: "letterhead",
|
||||
options: "Letter Head",
|
||||
placeholder: __("Letter Head"),
|
||||
change: () => this.preview(),
|
||||
label: __("Letter Head"),
|
||||
description: description,
|
||||
change: function () {
|
||||
this.set_description(this.get_value() ? description : "");
|
||||
print_view.preview();
|
||||
},
|
||||
}).$input;
|
||||
this.sidebar_dynamic_section = $(`<div class="dynamic-settings"></div>`).appendTo(
|
||||
this.sidebar
|
||||
|
|
|
|||
|
|
@ -22,6 +22,29 @@ frappe.model.DocTypeController = class DocTypeController extends frappe.ui.form.
|
|||
};
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.show_db_utilization();
|
||||
}
|
||||
|
||||
show_db_utilization() {
|
||||
const doctype = this.frm.doc.doc_type || this.frm.doc.name;
|
||||
frappe
|
||||
.xcall("frappe.core.doctype.doctype.doctype.get_row_size_utilization", {
|
||||
doctype,
|
||||
})
|
||||
.then((r) => {
|
||||
if (r < 50.0) return;
|
||||
this.frm.dashboard.show_progress(
|
||||
__("Database Row Size Utilization"),
|
||||
r,
|
||||
__(
|
||||
"Database Table Row Size Utilization: {0}%, this limits number of fields you can add.",
|
||||
[r]
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
max_attachments() {
|
||||
if (!this.frm.doc.max_attachments) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -18,13 +18,11 @@ frappe.ui.form.ControlMultiCheck = class ControlMultiCheck extends frappe.ui.for
|
|||
this.$checkbox_area = $(`<div class="checkbox-options ${row}"></div>`).appendTo(
|
||||
this.wrapper
|
||||
);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.set_options();
|
||||
this.bind_checkboxes();
|
||||
this.refresh_input();
|
||||
super.refresh();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -365,8 +365,14 @@ frappe.form.formatters = {
|
|||
</div>`
|
||||
: "";
|
||||
},
|
||||
Attach: format_attachment_url,
|
||||
AttachImage: format_attachment_url,
|
||||
};
|
||||
|
||||
function format_attachment_url(url) {
|
||||
return url ? `<a href="${url}" target="_blank">${url}</a>` : "";
|
||||
}
|
||||
|
||||
frappe.form.get_formatter = function (fieldtype) {
|
||||
if (!fieldtype) fieldtype = "Data";
|
||||
return frappe.form.formatters[fieldtype.replace(/ /g, "")] || frappe.form.formatters.Data;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
{% } %}
|
||||
<div class="col-md-4">
|
||||
<div class="form-link-title">
|
||||
<span>{{ __(transactions[i].label) }}<span>
|
||||
<span>{{ __(transactions[i].label) }}</span>
|
||||
</div>
|
||||
{% for (let j=0; j < transactions[i].items.length; j++) { %}
|
||||
{% let doctype = transactions[i].items[j]; %}
|
||||
|
|
|
|||
|
|
@ -1029,7 +1029,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
return `<span class="indicator-pill ${indicator[1]} filterable ellipsis"
|
||||
data-filter='${indicator[2]}' title='${title}'>
|
||||
<span class="ellipsis"> ${__(indicator[0])}</span>
|
||||
<span>`;
|
||||
</span>`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -747,7 +747,10 @@ frappe.views.CommunicationComposer = class {
|
|||
this.content_set = true;
|
||||
}
|
||||
|
||||
message += await this.get_signature(sender_email || null);
|
||||
const signature = await this.get_signature(sender_email || "");
|
||||
if (!this.content_set || !strip_html(message).includes(strip_html(signature))) {
|
||||
message += signature;
|
||||
}
|
||||
|
||||
if (this.is_a_reply && !this.reply_set) {
|
||||
message += this.get_earlier_reply();
|
||||
|
|
|
|||
|
|
@ -353,7 +353,7 @@ frappe.views.Workspace = class Workspace {
|
|||
let current_page = pages.filter((p) => p.title == page.name)[0];
|
||||
this.content = current_page && JSON.parse(current_page.content);
|
||||
|
||||
this.add_custom_cards_in_content();
|
||||
this.content && this.add_custom_cards_in_content();
|
||||
|
||||
$(".item-anchor").addClass("disable-click");
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,13 @@ frappe.ui.OnboardingTour = class OnboardingTour {
|
|||
step.popover.node.offsetTop + step.options.step_info.offset_y
|
||||
}px`;
|
||||
}
|
||||
if (step.popover.node.offsetLeft < 0) {
|
||||
step.popover.node.style.minWidth = "200px";
|
||||
step.popover.node.style.maxWidth = `${
|
||||
350 + step.popover.node.offsetLeft
|
||||
}px`;
|
||||
step.popover.node.style.left = "0px";
|
||||
}
|
||||
if (step.popover.closeBtnNode) {
|
||||
step.popover.closeBtnNode.onclick = () => {
|
||||
this.on_finish && this.on_finish();
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ class TelemetryManager {
|
|||
}
|
||||
}
|
||||
|
||||
capture(event, app) {
|
||||
capture(event, app, props) {
|
||||
if (!this.enabled) return;
|
||||
posthog.capture(`${app}_${event}`);
|
||||
posthog.capture(`${app}_${event}`, props);
|
||||
}
|
||||
|
||||
disable() {
|
||||
|
|
@ -49,7 +49,7 @@ class TelemetryManager {
|
|||
|
||||
if (!last || moment(now).diff(moment(last), "hours") > 12) {
|
||||
localStorage.setItem(KEY, now.toISOString());
|
||||
this.capture("heartbeat", "frappe");
|
||||
this.capture("heartbeat", "frappe", { frappe_version: frappe.boot?.versions?.frappe });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,9 +45,6 @@
|
|||
.layout-side-section.print-preview-sidebar {
|
||||
padding-right: var(--padding-md);
|
||||
|
||||
.clearfix {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.label-area {
|
||||
white-space: nowrap;
|
||||
|
|
|
|||
|
|
@ -169,6 +169,10 @@ class TestDocument(FrappeTestCase):
|
|||
with self.assertQueryCount(0):
|
||||
user.db_set("user_type", "Magical Wizard")
|
||||
|
||||
def test_new_doc_with_fields(self):
|
||||
user = frappe.new_doc("User", first_name="wizard")
|
||||
self.assertEqual(user.first_name, "wizard")
|
||||
|
||||
def test_update_after_submit(self):
|
||||
d = self.test_insert()
|
||||
d.starts_on = "2014-09-09"
|
||||
|
|
|
|||
|
|
@ -218,13 +218,6 @@ class TestQuery(FrappeTestCase):
|
|||
|
||||
@run_only_if(db_type_is.MARIADB)
|
||||
def test_filters(self):
|
||||
self.assertEqual(
|
||||
frappe.qb.get_query(
|
||||
"User", filters={"IfNull(name, " ")": ("<", Now())}, fields=["Max(name)"]
|
||||
).run(),
|
||||
frappe.qb.from_("User").select(Max(Field("name"))).where(Ifnull("name", "") < Now()).run(),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
frappe.qb.get_query(
|
||||
"DocType",
|
||||
|
|
@ -258,6 +251,17 @@ class TestQuery(FrappeTestCase):
|
|||
),
|
||||
)
|
||||
|
||||
self.assertRaisesRegex(
|
||||
frappe.ValidationError,
|
||||
"Invalid filter",
|
||||
lambda: frappe.qb.get_query(
|
||||
"DocType",
|
||||
fields=["name"],
|
||||
filters={"permissions.role": "System Manager"},
|
||||
validate_filters=True,
|
||||
),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
frappe.qb.get_query(
|
||||
"DocType",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ def get_monthly_results(
|
|||
Function(aggregation, goal_field),
|
||||
],
|
||||
filters=filters,
|
||||
validate_filters=True,
|
||||
)
|
||||
.groupby("month_year")
|
||||
.run()
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@
|
|||
"allow_incomplete",
|
||||
"section_break_2",
|
||||
"max_attachment_size",
|
||||
"section_break_xzqr",
|
||||
"condition",
|
||||
"column_break_tjgl",
|
||||
"condition_description",
|
||||
"section_break_3",
|
||||
"list_setting_message",
|
||||
"show_list",
|
||||
|
|
@ -279,10 +283,6 @@
|
|||
"fieldtype": "Tab Break",
|
||||
"label": "Form"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_1",
|
||||
"fieldtype": "Section Break"
|
||||
|
|
@ -297,7 +297,6 @@
|
|||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
|
|
@ -374,13 +373,33 @@
|
|||
"fieldname": "anonymous",
|
||||
"fieldtype": "Check",
|
||||
"label": "Anonymous"
|
||||
},
|
||||
{
|
||||
"fieldname": "condition",
|
||||
"fieldtype": "Code",
|
||||
"label": "Condition",
|
||||
"max_height": "150px"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_xzqr",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_tjgl",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "condition_description",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Condition Description",
|
||||
"options": "<p>Multiple webforms can be created for a single doctype. Write a condition specific to this webform to display correct record after submission.</p><p>For Example:</p>\n<p>If you create a separate webform every year to capture feedback from employees add a \n field named year in doctype and add a condition <b>doc.year==\"2023\"</b></p>\n"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
"icon": "icon-edit",
|
||||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"modified": "2023-04-20 17:24:42.657731",
|
||||
"modified": "2023-06-03 19:18:56.760479",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Form",
|
||||
|
|
|
|||
|
|
@ -153,10 +153,16 @@ def get_context(context):
|
|||
and not frappe.form_dict.name
|
||||
and not frappe.form_dict.is_list
|
||||
):
|
||||
name = frappe.db.get_value(self.doc_type, {"owner": frappe.session.user}, "name")
|
||||
if name:
|
||||
context.in_view_mode = True
|
||||
frappe.redirect(f"/{self.route}/{name}")
|
||||
names = frappe.db.get_values(self.doc_type, {"owner": frappe.session.user}, pluck="name")
|
||||
for name in names:
|
||||
if self.condition:
|
||||
doc = frappe.get_doc(self.doc_type, name)
|
||||
if frappe.safe_eval(self.condition, None, {"doc": doc.as_dict()}):
|
||||
context.in_view_mode = True
|
||||
frappe.redirect(f"/{self.route}/{name}")
|
||||
else:
|
||||
context.in_view_mode = True
|
||||
frappe.redirect(f"/{self.route}/{name}")
|
||||
|
||||
# Show new form when
|
||||
# - User is Guest
|
||||
|
|
|
|||
|
|
@ -9,7 +9,13 @@ from frappe.model.document import Document
|
|||
|
||||
|
||||
class WebPageView(Document):
|
||||
pass
|
||||
@staticmethod
|
||||
def clear_old_logs(days=180):
|
||||
from frappe.query_builder import Interval
|
||||
from frappe.query_builder.functions import Now
|
||||
|
||||
table = frappe.qb.DocType("Web Page View")
|
||||
frappe.db.delete(table, filters=(table.modified < (Now() - Interval(days=days))))
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue