diff --git a/frappe/__init__.py b/frappe/__init__.py index c906a69918..defa6e3336 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -28,7 +28,11 @@ from .exceptions import * from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader) from .utils.lazy_loader import lazy_import -from frappe.query_builder import get_query_builder, patch_query_execute +from frappe.query_builder import ( + get_query_builder, + patch_query_execute, + patch_query_aggregation, +) __version__ = '14.0.0-dev' @@ -211,6 +215,7 @@ def init(site, sites_path=None, new_site=False): setup_module_map() patch_query_execute() + patch_query_aggregation() local.initialised = True diff --git a/frappe/database/database.py b/frappe/database/database.py index 411587aa7e..f489cea7de 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -836,18 +836,6 @@ class Database(object): except Exception: return None - def min(self, dt, fieldname, filters=None, **kwargs): - return self.query.build_conditions(dt, filters=filters).select(Min(Column(fieldname))).run(**kwargs)[0][0] or 0 - - def max(self, dt, fieldname, filters=None, **kwargs): - return self.query.build_conditions(dt, filters=filters).select(Max(Column(fieldname))).run(**kwargs)[0][0] or 0 - - def avg(self, dt, fieldname, filters=None, **kwargs): - return self.query.build_conditions(dt, filters=filters).select(Avg(Column(fieldname))).run(**kwargs)[0][0] or 0 - - def sum(self, dt, fieldname, filters=None, **kwargs): - return self.query.build_conditions(dt, filters=filters).select(Sum(Column(fieldname))).run(**kwargs)[0][0] or 0 - def count(self, dt, filters=None, debug=False, cache=False): """Returns `COUNT(*)` for given DocType and filters.""" if cache and not filters: diff --git a/frappe/deferred_insert.py b/frappe/deferred_insert.py index 499fc5e41b..b1338a73b0 100644 --- a/frappe/deferred_insert.py +++ b/frappe/deferred_insert.py @@ -5,7 +5,6 @@ from frappe.utils import cstr queue_prefix = 'insert_queue_for_' -@frappe.whitelist() def deferred_insert(doctype, records): frappe.cache().rpush(queue_prefix + doctype, records) diff --git a/frappe/desk/doctype/route_history/route_history.py b/frappe/desk/doctype/route_history/route_history.py index 01184fcc3a..fc87312950 100644 --- a/frappe/desk/doctype/route_history/route_history.py +++ b/frappe/desk/doctype/route_history/route_history.py @@ -1,9 +1,13 @@ # Copyright (c) 2021, Frappe Technologies and contributors # License: MIT. See LICENSE +import json + import frappe +from frappe.deferred_insert import deferred_insert as _deferred_insert from frappe.model.document import Document + class RouteHistory(Document): pass @@ -35,3 +39,19 @@ def flush_old_route_records(): "modified": ("<=", last_record_to_keep[0].modified), "user": user }) + +@frappe.whitelist() +def deferred_insert(routes): + routes_record = [] + + if isinstance(routes, str): + routes = json.loads(routes) + + for route_doc in routes: + routes_record.append({ + "user": frappe.session.user, + "route": route_doc.get("route"), + "creation": route_doc.get("creation") + }) + + _deferred_insert("Route History", json.dumps(routes_record)) diff --git a/frappe/patches.txt b/frappe/patches.txt index b230c336b4..3078159c3d 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -186,3 +186,4 @@ frappe.patches.v14_0.rename_cancelled_documents frappe.patches.v14_0.copy_mail_data #08.03.21 frappe.patches.v14_0.update_workspace2 # 20.09.2021 frappe.patches.v14_0.update_github_endpoints #08-11-2021 +frappe.patches.v14_0.remove_db_aggregation diff --git a/frappe/patches/v14_0/remove_db_aggregation.py b/frappe/patches/v14_0/remove_db_aggregation.py new file mode 100644 index 0000000000..25a170f362 --- /dev/null +++ b/frappe/patches/v14_0/remove_db_aggregation.py @@ -0,0 +1,32 @@ +import re + +import frappe +from frappe.query_builder import DocType + + +def execute(): + """Replace temporarily available Database Aggregate APIs on frappe (develop) + + APIs changed: + * frappe.db.max => frappe.qb.max + * frappe.db.min => frappe.qb.min + * frappe.db.sum => frappe.qb.sum + * frappe.db.avg => frappe.qb.avg + """ + ServerScript = DocType("Server Script") + server_scripts = frappe.qb.from_(ServerScript).where( + ServerScript.script.like("%frappe.db.max(%") + | ServerScript.script.like("%frappe.db.min(%") + | ServerScript.script.like("%frappe.db.sum(%") + | ServerScript.script.like("%frappe.db.avg(%") + ).select( + "name", "script" + ).run(as_dict=True) + + for server_script in server_scripts: + name, script = server_script["name"], server_script["script"] + + for agg in ["avg", "max", "min", "sum"]: + script = re.sub(f"frappe.db.{agg}(", f"frappe.qb.{agg}(", script) + + frappe.db.update("Server Script", name, "script", script) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index 37b7e08a80..161e4196b0 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -325,7 +325,9 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { let parent_names = this.child_datatable.rowmanager.checkMap.reduce((parent_names, checked, index) => { if (checked == 1) { const parent_name = this.child_results[index].parent; - parent_names.push(parent_name); + if (!parent_names.includes(parent_name)) { + parent_names.push(parent_name); + } } return parent_names; }, []); diff --git a/frappe/public/js/frappe/router_history.js b/frappe/public/js/frappe/router_history.js index c64c3fc9f2..fb2d5790da 100644 --- a/frappe/public/js/frappe/router_history.js +++ b/frappe/public/js/frappe/router_history.js @@ -5,13 +5,14 @@ const save_routes = frappe.utils.debounce(() => { if (frappe.session.user === 'Guest') return; const routes = frappe.route_history_queue; frappe.route_history_queue = []; - - frappe.xcall('frappe.deferred_insert.deferred_insert', { - 'doctype': 'Route History', - 'records': routes + + if (!routes.length) return; + + frappe.xcall('frappe.desk.doctype.route_history.route_history.deferred_insert', { + 'routes': routes }).catch(() => { frappe.route_history_queue.concat(routes); - }); + }); }, 10000); @@ -19,7 +20,6 @@ frappe.router.on('change', () => { const route = frappe.get_route(); if (is_route_useful(route)) { frappe.route_history_queue.push({ - 'user': frappe.session.user, 'creation': frappe.datetime.now_datetime(), 'route': frappe.get_route_str() }); diff --git a/frappe/query_builder/__init__.py b/frappe/query_builder/__init__.py index 4a1fe8fb84..9c7432142f 100644 --- a/frappe/query_builder/__init__.py +++ b/frappe/query_builder/__init__.py @@ -1,2 +1,2 @@ from pypika import * -from frappe.query_builder.utils import Column, DocType, get_query_builder, patch_query_execute +from frappe.query_builder.utils import Column, DocType, get_query_builder, patch_query_execute, patch_query_aggregation diff --git a/frappe/query_builder/functions.py b/frappe/query_builder/functions.py index 39c67178c2..c98df775b7 100644 --- a/frappe/query_builder/functions.py +++ b/frappe/query_builder/functions.py @@ -2,6 +2,8 @@ from pypika.functions import * from pypika.terms import Function from frappe.query_builder.utils import ImportMapper, db_type_is from frappe.query_builder.custom import GROUP_CONCAT, STRING_AGG, MATCH, TO_TSVECTOR +from frappe.database.query import Query +from .utils import Column class Concat_ws(Function): @@ -22,3 +24,26 @@ Match = ImportMapper( db_type_is.POSTGRES: TO_TSVECTOR } ) + + +def _aggregate(function, dt, fieldname, filters, **kwargs): + return ( + Query() + .build_conditions(dt, filters) + .select(function(Column(fieldname))) + .run(**kwargs)[0][0] + or 0 + ) + + +def _max(dt, fieldname, filters=None, **kwargs): + return _aggregate(Max, dt, fieldname, filters, **kwargs) + +def _min(dt, fieldname, filters=None, **kwargs): + return _aggregate(Min, dt, fieldname, filters, **kwargs) + +def _avg(dt, fieldname, filters=None, **kwargs): + return _aggregate(Avg, dt, fieldname, filters, **kwargs) + +def _sum(dt, fieldname, filters=None, **kwargs): + return _aggregate(Sum, dt, fieldname, filters, **kwargs) \ No newline at end of file diff --git a/frappe/query_builder/utils.py b/frappe/query_builder/utils.py index b7d22fc354..a7f52df012 100644 --- a/frappe/query_builder/utils.py +++ b/frappe/query_builder/utils.py @@ -67,3 +67,14 @@ def patch_query_execute(): raise BuilderIdentificationFailed builder_class.run = execute_query + + +def patch_query_aggregation(): + """Patch aggregation functions to frappe.qb + """ + from frappe.query_builder.functions import _max, _min, _avg, _sum + + frappe.qb.max = _max + frappe.qb.min = _min + frappe.qb.avg = _avg + frappe.qb.sum = _sum \ No newline at end of file diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 00b7822104..6f317855a0 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -165,10 +165,6 @@ def get_safe_globals(): get_default=frappe.db.get_default, exists=frappe.db.exists, count=frappe.db.count, - min=frappe.db.min, - max=frappe.db.max, - avg=frappe.db.avg, - sum=frappe.db.sum, escape=frappe.db.escape, sql=read_sql, commit=frappe.db.commit,