Merge pull request #19854 from gavindsouza/get_all-virtual-dts

fix: Interface DatabaseQuery to virtual doctypes' get_list
This commit is contained in:
Ankush Menat 2023-02-03 17:30:57 +05:30 committed by GitHub
commit ccaf4c1e7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 188 additions and 79 deletions

View file

@ -526,13 +526,14 @@ class TestDocType(FrappeTestCase):
self.assertRaises(InvalidFieldNameError, validate_links_table_fieldnames, doc)
def test_create_virtual_doctype(self):
"""Test virtual DOcTYpe."""
"""Test virtual DocType."""
virtual_doc = new_doctype("Test Virtual Doctype")
virtual_doc.is_virtual = 1
virtual_doc.insert()
virtual_doc.save()
virtual_doc.insert(ignore_if_duplicate=True)
virtual_doc.reload()
doc = frappe.get_doc("DocType", "Test Virtual Doctype")
self.assertDictEqual(doc.as_dict(), virtual_doc.as_dict())
self.assertEqual(doc.is_virtual, 1)
self.assertFalse(frappe.db.table_exists("Test Virtual Doctype"))

View file

@ -20,6 +20,7 @@ import frappe.defaults
import frappe.model.meta
from frappe import _
from frappe.database.utils import (
DefaultOrderBy,
EmptyQueryValues,
FallBackDateTimeStr,
LazyMogrify,
@ -422,7 +423,7 @@ class Database:
ignore=None,
as_dict=False,
debug=False,
order_by="KEEP_DEFAULT_ORDERING",
order_by=DefaultOrderBy,
cache=False,
for_update=False,
*,
@ -492,7 +493,7 @@ class Database:
ignore=None,
as_dict=False,
debug=False,
order_by="KEEP_DEFAULT_ORDERING",
order_by=DefaultOrderBy,
update=None,
cache=False,
for_update=False,
@ -551,7 +552,7 @@ class Database:
if (filters is not None) and (filters != doctype or doctype == "DocType"):
try:
if order_by:
order_by = "modified" if order_by == "KEEP_DEFAULT_ORDERING" else order_by
order_by = "modified" if order_by == DefaultOrderBy else order_by
out = self._get_values_from_table(
fields=fields,
filters=filters,

View file

@ -10,7 +10,7 @@ from pypika.queries import QueryBuilder, Table
import frappe
from frappe import _
from frappe.database.operator_map import OPERATOR_MAP
from frappe.database.utils import get_doctype_name
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
from frappe.query_builder.utils import PseudoColumnMapper
@ -314,7 +314,7 @@ class Engine:
return _fields
def apply_order_by(self, order_by: str | None):
if not order_by or order_by == "KEEP_DEFAULT_ORDERING":
if not order_by or order_by == DefaultOrderBy:
return
for declaration in order_by.split(","):
if _order_by := declaration.strip():

View file

@ -17,7 +17,7 @@ QueryValues = tuple | list | dict | NoneType
EmptyQueryValues = object()
FallBackDateTimeStr = "0001-01-01 00:00:00.000000"
DefaultOrderBy = "KEEP_DEFAULT_ORDERING"
NestedSetHierarchy = (
"ancestors of",
"descendants of",

View file

@ -13,9 +13,10 @@ import frappe.permissions
import frappe.share
from frappe import _
from frappe.core.doctype.server_script.server_script_utils import get_server_script_map
from frappe.database.utils import FallBackDateTimeStr, NestedSetHierarchy
from frappe.database.utils import DefaultOrderBy, FallBackDateTimeStr, NestedSetHierarchy
from frappe.model import get_permitted_fields, optional_fields
from frappe.model.meta import get_table_columns
from frappe.model.utils import is_virtual_doctype
from frappe.model.utils.user_settings import get_user_settings, update_user_settings
from frappe.query_builder.utils import Column
from frappe.utils import (
@ -80,7 +81,7 @@ class DatabaseQuery:
or_filters=None,
docstatus=None,
group_by=None,
order_by="KEEP_DEFAULT_ORDERING",
order_by=DefaultOrderBy,
limit_start=False,
limit_page_length=None,
as_list=False,
@ -171,6 +172,21 @@ class DatabaseQuery:
if user_settings:
self.user_settings = json.loads(user_settings)
if is_virtual_doctype(self.doctype):
from frappe.model.base_document import get_controller
controller = get_controller(self.doctype)
self.parse_args()
kwargs = {
"as_list": as_list,
"with_comment_count": with_comment_count,
"save_user_settings": save_user_settings,
"save_user_settings_fields": save_user_settings_fields,
"pluck": pluck,
"parent_doctype": parent_doctype,
} | self.__dict__
return controller.get_list(kwargs)
self.columns = self.get_table_columns()
# no table & ignore_ddl, return

View file

@ -129,5 +129,7 @@ def get_fetch_values(doctype, fieldname, value):
@site_cache()
def is_virtual_doctype(doctype):
return frappe.db.get_value("DocType", doctype, "is_virtual")
def is_virtual_doctype(doctype: str):
if frappe.db.has_column("DocType", "is_virtual"):
return frappe.db.get_value("DocType", doctype, "is_virtual")
return False

View file

@ -2,10 +2,13 @@
# License: MIT. See LICENSE
import datetime
from contextlib import contextmanager
from unittest.mock import MagicMock, patch
import frappe
from frappe.core.doctype.doctype.test_doctype import new_doctype
from frappe.core.page.permission_manager.permission_manager import add, reset, update
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.database.utils import DefaultOrderBy
from frappe.desk.reportview import get_filters_cond
from frappe.handler import execute_cmd
from frappe.model.db_query import DatabaseQuery
@ -43,7 +46,7 @@ def setup_patched_blog_post():
yield
class TestReportview(FrappeTestCase):
class TestDBQuery(FrappeTestCase):
def setUp(self):
frappe.set_user("Administrator")
@ -848,68 +851,6 @@ class TestReportview(FrappeTestCase):
fields=["blog_category.description"],
)
def test_reportview_get_permlevel_system_users(self):
with setup_patched_blog_post(), setup_test_user(set_user=True):
frappe.local.request = frappe._dict()
frappe.local.request.method = "POST"
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"],
}
)
# even if * is passed, fields which are not accessible should be filtered out
response = execute_cmd("frappe.desk.reportview.get")
self.assertListEqual(response["keys"], ["title"])
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"fields": ["*"],
}
)
response = execute_cmd("frappe.desk.reportview.get")
self.assertNotIn("published", response["keys"])
def test_reportview_get_admin(self):
# Admin should be able to see access all fields
with setup_patched_blog_post():
frappe.local.request = frappe._dict()
frappe.local.request.method = "POST"
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"],
}
)
response = execute_cmd("frappe.desk.reportview.get")
self.assertListEqual(response["keys"], ["published", "title", "test_field"])
def test_reportview_get_aggregation(self):
# test aggregation based on child table field
frappe.local.request = frappe._dict()
frappe.local.request.method = "POST"
frappe.local.form_dict = frappe._dict(
{
"doctype": "DocType",
"fields": """["`tabDocField`.`label` as field_label","`tabDocField`.`name` as field_name"]""",
"filters": "[]",
"order_by": "_aggregate_column desc",
"start": 0,
"page_length": 20,
"view": "Report",
"with_comment_count": 0,
"group_by": "field_label, field_name",
"aggregate_on_field": "columns",
"aggregate_on_doctype": "DocField",
"aggregate_function": "sum",
}
)
response = execute_cmd("frappe.desk.reportview.get")
self.assertListEqual(response["keys"], ["field_label", "field_name", "_aggregate_column"])
def test_cast_name(self):
from frappe.core.doctype.doctype.test_doctype import new_doctype
@ -1007,6 +948,33 @@ class TestReportview(FrappeTestCase):
self.assertTrue(dashboard_settings)
def test_virtual_doctype(self):
"""Test that virtual doctypes can be queried using get_all"""
virtual_doctype = new_doctype("Virtual DocType")
virtual_doctype.is_virtual = 1
virtual_doctype.insert(ignore_if_duplicate=True)
class VirtualDocType:
@staticmethod
def get_list(args):
...
with patch("frappe.controllers", new={frappe.local.site: {"Virtual DocType": VirtualDocType}}):
VirtualDocType.get_list = MagicMock()
frappe.get_all("Virtual DocType", filters={"name": "test"}, fields=["name"], limit=1)
call_args = VirtualDocType.get_list.call_args[0][0]
VirtualDocType.get_list.assert_called_once()
self.assertIsInstance(call_args, dict)
self.assertEqual(call_args["doctype"], "Virtual DocType")
self.assertEqual(call_args["filters"], [["Virtual DocType", "name", "=", "test"]])
self.assertEqual(call_args["fields"], ["name"])
self.assertEqual(call_args["limit_page_length"], 1)
self.assertEqual(call_args["limit_start"], 0)
self.assertEqual(call_args["order_by"], DefaultOrderBy)
def test_coalesce_with_in_ops(self):
self.assertNotIn("ifnull", frappe.get_all("User", {"name": ("in", ["a", "b"])}, run=0))
self.assertIn("ifnull", frappe.get_all("User", {"name": ("in", ["a", None])}, run=0))
@ -1017,6 +985,129 @@ class TestReportview(FrappeTestCase):
self.assertIn("ifnull", frappe.get_all("User", {"name": ("not in", [""])}, run=0))
class TestReportView(FrappeTestCase):
def test_reportview_get(self):
user = frappe.get_doc("User", "test@example.com")
add_child_table_to_blog_post()
user_roles = frappe.get_roles()
user.remove_roles(*user_roles)
user.add_roles("Blogger")
make_property_setter("Blog Post", "published", "permlevel", 1, "Int")
reset("Blog Post")
add("Blog Post", "Website Manager", 1)
update("Blog Post", "Website Manager", 1, "write", 1)
frappe.set_user(user.name)
frappe.local.request = frappe._dict()
frappe.local.request.method = "POST"
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"],
}
)
# even if * is passed, fields which are not accessible should be filtered out
response = execute_cmd("frappe.desk.reportview.get")
self.assertListEqual(response["keys"], ["title"])
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"fields": ["*"],
}
)
response = execute_cmd("frappe.desk.reportview.get")
self.assertNotIn("published", response["keys"])
frappe.set_user("Administrator")
user.add_roles("Website Manager")
frappe.set_user(user.name)
frappe.set_user("Administrator")
# Admin should be able to see access all fields
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"],
}
)
response = execute_cmd("frappe.desk.reportview.get")
self.assertListEqual(response["keys"], ["published", "title", "test_field"])
# reset user roles
user.remove_roles("Blogger", "Website Manager")
user.add_roles(*user_roles)
def test_reportview_get_aggregation(self):
# test aggregation based on child table field
frappe.local.request = frappe._dict()
frappe.local.request.method = "POST"
frappe.local.form_dict = frappe._dict(
{
"doctype": "DocType",
"fields": """["`tabDocField`.`label` as field_label","`tabDocField`.`name` as field_name"]""",
"filters": "[]",
"order_by": "_aggregate_column desc",
"start": 0,
"page_length": 20,
"view": "Report",
"with_comment_count": 0,
"group_by": "field_label, field_name",
"aggregate_on_field": "columns",
"aggregate_on_doctype": "DocField",
"aggregate_function": "sum",
}
)
response = execute_cmd("frappe.desk.reportview.get")
self.assertListEqual(response["keys"], ["field_label", "field_name", "_aggregate_column"])
def test_reportview_get_permlevel_system_users(self):
with setup_patched_blog_post(), setup_test_user(set_user=True):
frappe.local.request = frappe._dict()
frappe.local.request.method = "POST"
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"],
}
)
# even if * is passed, fields which are not accessible should be filtered out
response = execute_cmd("frappe.desk.reportview.get")
self.assertListEqual(response["keys"], ["title"])
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"fields": ["*"],
}
)
response = execute_cmd("frappe.desk.reportview.get")
self.assertNotIn("published", response["keys"])
def test_reportview_get_admin(self):
# Admin should be able to see access all fields
with setup_patched_blog_post():
frappe.local.request = frappe._dict()
frappe.local.request.method = "POST"
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"],
}
)
response = execute_cmd("frappe.desk.reportview.get")
self.assertListEqual(response["keys"], ["published", "title", "test_field"])
def add_child_table_to_blog_post():
child_table = frappe.get_doc(
{
@ -1040,7 +1131,7 @@ def create_event(subject="_Test Event", starts_on=None):
from frappe.utils import get_datetime
event = frappe.get_doc(
return frappe.get_doc(
{
"doctype": "Event",
"subject": subject,
@ -1049,8 +1140,6 @@ def create_event(subject="_Test Event", starts_on=None):
}
).insert(ignore_permissions=True)
return event
def create_nested_doctype():
if frappe.db.exists("DocType", "Nested DocType"):