@@ -11,7 +11,7 @@ from werkzeug.local import Local, release_local | |||
from werkzeug.exceptions import NotFound | |||
from MySQLdb import ProgrammingError as SQLError | |||
import os, sys, importlib | |||
import os, sys, importlib, inspect | |||
import json | |||
import semantic_version | |||
@@ -459,6 +459,18 @@ def get_attr(method_string): | |||
modulename = '.'.join(method_string.split('.')[:-1]) | |||
methodname = method_string.split('.')[-1] | |||
return getattr(get_module(modulename), methodname) | |||
def call(fn, *args, **kwargs): | |||
if hasattr(fn, 'fnargs'): | |||
fnargs = fn.fnargs | |||
else: | |||
fnargs, varargs, varkw, defaults = inspect.getargspec(fn) | |||
newargs = {} | |||
for a in fnargs: | |||
if a in kwargs: | |||
newargs[a] = kwargs.get(a) | |||
return fn(*args, **newargs) | |||
def make_property_setter(args): | |||
args = _dict(args) | |||
@@ -0,0 +1,97 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
import webnotes | |||
import webnotes.handler | |||
import webnotes.client | |||
import webnotes.widgets.reportview | |||
from webnotes.utils.response import build_response, report_error | |||
def handle(): | |||
""" | |||
/api/method/{methodname} will call a whitelisted method | |||
/api/resource/{doctype} will query a table | |||
examples: | |||
?fields=["name", "owner"] | |||
?filters=[["Task", "name", "like", "%005"]] | |||
?limit_start=0 | |||
?limit_page_length=20 | |||
/api/resource/{doctype}/{name} will point to a resource | |||
GET will return doclist | |||
POST will insert | |||
PUT will update | |||
DELETE will delete | |||
/api/resource/{doctype}/{name}?run_method={method} will run a whitelisted controller method | |||
""" | |||
parts = webnotes.request.path[1:].split("/") | |||
call = doctype = name = None | |||
if len(parts) > 1: | |||
call = parts[1] | |||
if len(parts) > 2: | |||
doctype = parts[2] | |||
if len(parts) > 3: | |||
name = parts[3] | |||
try: | |||
if call=="method": | |||
webnotes.local.form_dict.cmd = doctype | |||
webnotes.handler.handle() | |||
return | |||
elif call=="resource": | |||
if "run_method" in webnotes.local.form_dict: | |||
bean = webnotes.bean(doctype, name) | |||
if webnotes.local.request.method=="GET": | |||
if not bean.has_permission("read"): | |||
webnotes.throw("No Permission", webnotes.PermissionError) | |||
webnotes.local.response.update({"data": bean.run_method(webnotes.local.form_dict.run_method, | |||
**webnotes.local.form_dict)}) | |||
if webnotes.local.request.method=="POST": | |||
if not bean.has_permission("write"): | |||
webnotes.throw("No Permission", webnotes.PermissionError) | |||
webnotes.local.response.update({"data":bean.run_method(webnotes.local.form_dict.run_method, | |||
**webnotes.local.form_dict)}) | |||
webnotes.conn.commit() | |||
else: | |||
if name: | |||
if webnotes.local.request.method=="GET": | |||
webnotes.local.response.update({ | |||
"doclist": webnotes.client.get(doctype, | |||
name)}) | |||
if webnotes.local.request.method=="POST": | |||
webnotes.local.response.update({ | |||
"doclist": webnotes.client.insert(webnotes.local.form_dict.doclist)}) | |||
webnotes.conn.commit() | |||
if webnotes.local.request.method=="PUT": | |||
webnotes.local.response.update({ | |||
"doclist":webnotes.client.save(webnotes.local.form_dict.doclist)}) | |||
webnotes.conn.commit() | |||
if webnotes.local.request.method=="DELETE": | |||
webnotes.client.delete(doctype, name) | |||
webnotes.local.response.message = "ok" | |||
elif doctype: | |||
if webnotes.local.request.method=="GET": | |||
webnotes.local.response.update({ | |||
"data": webnotes.call(webnotes.widgets.reportview.execute, | |||
doctype, **webnotes.local.form_dict)}) | |||
else: | |||
raise Exception("Bad API") | |||
else: | |||
raise Exception("Bad API") | |||
except Exception, e: | |||
report_error(500) | |||
build_response() |
@@ -18,6 +18,7 @@ import mimetypes | |||
import webnotes | |||
import webnotes.handler | |||
import webnotes.auth | |||
import webnotes.api | |||
import webnotes.webutils | |||
from webnotes.utils import get_site_name | |||
@@ -56,8 +57,10 @@ def application(request): | |||
webnotes.local._response = Response() | |||
webnotes.http_request = webnotes.auth.HTTPRequest() | |||
if webnotes.form_dict.cmd: | |||
if webnotes.local.form_dict.cmd: | |||
webnotes.handler.handle() | |||
elif webnotes.request.path.startswith("/api/"): | |||
webnotes.api.handle() | |||
elif webnotes.local.request.method in ('GET', 'HEAD'): | |||
webnotes.webutils.render(webnotes.request.path[1:]) | |||
else: | |||
@@ -87,11 +87,11 @@ class HTTPRequest: | |||
class LoginManager: | |||
def __init__(self): | |||
self.user = None | |||
if webnotes.form_dict.get('cmd')=='login': | |||
if webnotes.local.form_dict.get('cmd')=='login' or webnotes.local.request.path=="/api/method/login": | |||
self.login() | |||
else: | |||
self.make_session(resume=True) | |||
def login(self): | |||
# clear cache | |||
webnotes.clear_cache(user = webnotes.form_dict.get('usr')) | |||
@@ -109,16 +109,16 @@ class LoginManager: | |||
info = webnotes.conn.get_value("Profile", self.user, | |||
["user_type", "first_name", "last_name"], as_dict=1) | |||
if info.user_type=="Website User": | |||
webnotes._response.set_cookie("system_user", "no") | |||
webnotes.response["message"] = "No App" | |||
webnotes.local._response.set_cookie("system_user", "no") | |||
webnotes.local.response["message"] = "No App" | |||
else: | |||
webnotes._response.set_cookie("system_user", "yes") | |||
webnotes.response['message'] = 'Logged In' | |||
webnotes.local._response.set_cookie("system_user", "yes") | |||
webnotes.local.response['message'] = 'Logged In' | |||
full_name = " ".join(filter(None, [info.first_name, info.last_name])) | |||
webnotes.response["full_name"] = full_name | |||
webnotes._response.set_cookie("full_name", full_name) | |||
webnotes._response.set_cookie("user_id", self.user) | |||
webnotes.local.response["full_name"] = full_name | |||
webnotes.local._response.set_cookie("full_name", full_name) | |||
webnotes.local._response.set_cookie("user_id", self.user) | |||
def make_session(self, resume=False): | |||
# start session | |||
@@ -154,7 +154,7 @@ class LoginManager: | |||
return user[0][0] # in correct case | |||
def fail(self, message): | |||
webnotes.response['message'] = message | |||
webnotes.local.response['message'] = message | |||
raise webnotes.AuthenticationError | |||
@@ -213,12 +213,12 @@ class LoginManager: | |||
if user == webnotes.session.user: | |||
webnotes.session.sid = "" | |||
webnotes._response.delete_cookie("full_name") | |||
webnotes._response.delete_cookie("user_id") | |||
webnotes._response.delete_cookie("sid") | |||
webnotes._response.set_cookie("full_name", "") | |||
webnotes._response.set_cookie("user_id", "") | |||
webnotes._response.set_cookie("sid", "") | |||
webnotes.local._response.delete_cookie("full_name") | |||
webnotes.local._response.delete_cookie("user_id") | |||
webnotes.local._response.delete_cookie("sid") | |||
webnotes.local._response.set_cookie("full_name", "") | |||
webnotes.local._response.set_cookie("user_id", "") | |||
webnotes.local._response.set_cookie("sid", "") | |||
class CookieManager: | |||
def __init__(self): | |||
@@ -231,9 +231,9 @@ class CookieManager: | |||
# sid expires in 3 days | |||
expires = datetime.datetime.now() + datetime.timedelta(days=3) | |||
if webnotes.session.sid: | |||
webnotes._response.set_cookie("sid", webnotes.session.sid, expires = expires) | |||
webnotes.local._response.set_cookie("sid", webnotes.session.sid, expires = expires) | |||
if webnotes.session.session_country: | |||
webnotes._response.set_cookie('country', webnotes.session.get("session_country")) | |||
webnotes.local._response.set_cookie('country', webnotes.session.get("session_country")) | |||
def set_remember_me(self): | |||
from webnotes.utils import cint | |||
@@ -247,7 +247,7 @@ class CookieManager: | |||
expires = datetime.datetime.now() + \ | |||
datetime.timedelta(days=remember_days) | |||
webnotes._response.set_cookie["remember_me"] = 1 | |||
webnotes.local._response.set_cookie["remember_me"] = 1 | |||
def _update_password(user, password): | |||
@@ -56,12 +56,12 @@ def delete_notification_count_for(doctype): | |||
def delete_event_notification_count(): | |||
delete_notification_count_for("Event") | |||
def clear_doctype_notifications(controller, method=None): | |||
def clear_doctype_notifications(bean, method=None): | |||
if webnotes.flags.in_import: | |||
return | |||
config = get_notification_config() | |||
doctype = controller.doc.doctype | |||
doctype = bean.doc.doctype | |||
if doctype in config.for_doctype: | |||
delete_notification_count_for(doctype) | |||
@@ -2,23 +2,20 @@ | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import sys, os | |||
import json | |||
import webnotes | |||
import webnotes.utils | |||
import webnotes.sessions | |||
import webnotes.utils.file_manager | |||
import webnotes.widgets.form.run_method | |||
from webnotes.utils.response import build_response, report_error | |||
@webnotes.whitelist(allow_guest=True) | |||
def startup(): | |||
webnotes.response.update(webnotes.sessions.get()) | |||
def cleanup_docs(): | |||
import webnotes.model.utils | |||
if webnotes.response.get('docs') and type(webnotes.response['docs'])!=dict: | |||
webnotes.response['docs'] = webnotes.model.utils.compress(webnotes.response['docs']) | |||
@webnotes.whitelist() | |||
def runserverobj(arg=None): | |||
import webnotes.widgets.form.run_method | |||
webnotes.widgets.form.run_method.runserverobj() | |||
@webnotes.whitelist(allow_guest=True) | |||
@@ -38,16 +35,12 @@ def run_custom_method(doctype, name, custom_method): | |||
bean = webnotes.bean(doctype, name) | |||
controller = bean.get_controller() | |||
if getattr(controller, custom_method, webnotes._dict()).is_whitelisted: | |||
call(getattr(controller, custom_method), webnotes.local.form_dict) | |||
webnotes.call(getattr(controller, custom_method), **webnotes.local.form_dict) | |||
else: | |||
webnotes.throw("Not Allowed") | |||
@webnotes.whitelist() | |||
def uploadfile(): | |||
import webnotes.utils | |||
import webnotes.utils.file_manager | |||
import json | |||
try: | |||
if webnotes.form_dict.get('from_form'): | |||
try: | |||
@@ -67,19 +60,9 @@ def uploadfile(): | |||
def handle(): | |||
"""handle request""" | |||
cmd = webnotes.form_dict['cmd'] | |||
cmd = webnotes.local.form_dict.cmd | |||
def _error(status_code): | |||
webnotes.errprint(webnotes.utils.get_traceback()) | |||
webnotes._response.status_code = status_code | |||
if webnotes.request_method == "POST": | |||
webnotes.conn.rollback() | |||
if cmd!='login': | |||
# login executed in webnotes.auth | |||
if webnotes.request_method == "POST": | |||
webnotes.conn.begin() | |||
if cmd!='login': | |||
status_codes = { | |||
webnotes.PermissionError: 403, | |||
webnotes.AuthenticationError: 401, | |||
@@ -91,12 +74,12 @@ def handle(): | |||
try: | |||
execute_cmd(cmd) | |||
except Exception, e: | |||
_error(status_codes.get(e.__class__, 500)) | |||
report_error(status_codes.get(e.__class__, 500)) | |||
else: | |||
if webnotes.request_method == "POST" and webnotes.conn: | |||
if webnotes.local.request.method in ("POST", "PUT") and webnotes.conn: | |||
webnotes.conn.commit() | |||
print_response() | |||
build_response() | |||
if webnotes.conn: | |||
webnotes.conn.close() | |||
@@ -117,7 +100,7 @@ def execute_cmd(cmd): | |||
webnotes.msgprint('Not Allowed, %s' % str(method)) | |||
raise webnotes.PermissionError('Not Allowed, %s' % str(method)) | |||
ret = call(method, webnotes.form_dict) | |||
ret = webnotes.call(method, **webnotes.form_dict) | |||
# returns with a message | |||
if ret: | |||
@@ -128,20 +111,6 @@ def execute_cmd(cmd): | |||
webnotes.local.session_obj.update() | |||
def call(fn, args): | |||
import inspect | |||
if hasattr(fn, 'fnargs'): | |||
fnargs = fn.fnargs | |||
else: | |||
fnargs, varargs, varkw, defaults = inspect.getargspec(fn) | |||
newargs = {} | |||
for a in fnargs: | |||
if a in args: | |||
newargs[a] = args.get(a) | |||
return fn(**newargs) | |||
def get_attr(cmd): | |||
"""get method object from cmd""" | |||
if '.' in cmd: | |||
@@ -151,93 +120,3 @@ def get_attr(cmd): | |||
webnotes.log("method:" + cmd) | |||
return method | |||
def print_response(): | |||
print_map = { | |||
'csv': print_csv, | |||
'download': print_raw, | |||
'json': print_json, | |||
'page': print_page | |||
} | |||
print_map.get(webnotes.response.get('type'), print_json)() | |||
def print_page(): | |||
"""print web page""" | |||
from webnotes.webutils import render | |||
render(webnotes.response['page_name']) | |||
def print_json(): | |||
make_logs() | |||
cleanup_docs() | |||
webnotes._response.headers["Content-Type"] = "text/json; charset: utf-8" | |||
import json | |||
print_zip(json.dumps(webnotes.local.response, default=json_handler, separators=(',',':'))) | |||
def print_csv(): | |||
webnotes._response.headers["Content-Type"] = \ | |||
"text/csv; charset: utf-8" | |||
webnotes._response.headers["Content-Disposition"] = \ | |||
"attachment; filename=%s.csv" % webnotes.response['doctype'].replace(' ', '_') | |||
webnotes._response.data = webnotes.response['result'] | |||
def print_raw(): | |||
webnotes._response.headers["Content-Type"] = \ | |||
mimetypes.guess_type(webnotes.response['filename'])[0] or "application/unknown" | |||
webnotes._response.headers["Content-Disposition"] = \ | |||
"filename=%s" % webnotes.response['filename'].replace(' ', '_') | |||
webnotes._response.data = webnotes.response['filecontent'] | |||
def make_logs(): | |||
"""make strings for msgprint and errprint""" | |||
import json | |||
from webnotes import conf | |||
from webnotes.utils import cstr | |||
if webnotes.error_log: | |||
# webnotes.response['exc'] = json.dumps("\n".join([cstr(d) for d in webnotes.error_log])) | |||
webnotes.response['exc'] = json.dumps([cstr(d) for d in webnotes.local.error_log]) | |||
if webnotes.local.message_log: | |||
webnotes.response['_server_messages'] = json.dumps([cstr(d) for d in webnotes.local.message_log]) | |||
if webnotes.debug_log and conf.get("logging") or False: | |||
webnotes.response['_debug_messages'] = json.dumps(webnotes.local.debug_log) | |||
def print_zip(response): | |||
response = response.encode('utf-8') | |||
orig_len = len(response) | |||
if accept_gzip() and orig_len>512: | |||
response = compressBuf(response) | |||
webnotes._response.headers["Content-Encoding"] = "gzip" | |||
webnotes._response.headers["Content-Length"] = str(len(response)) | |||
webnotes._response.data = response | |||
def json_handler(obj): | |||
"""serialize non-serializable data for json""" | |||
import datetime | |||
from werkzeug.local import LocalProxy | |||
# serialize date | |||
if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime)): | |||
return unicode(obj) | |||
elif isinstance(obj, LocalProxy): | |||
return unicode(obj) | |||
else: | |||
raise TypeError, """Object of type %s with value of %s is not JSON serializable""" % \ | |||
(type(obj), repr(obj)) | |||
def accept_gzip(): | |||
if "gzip" in webnotes.get_request_header("HTTP_ACCEPT_ENCODING", ""): | |||
return True | |||
def compressBuf(buf): | |||
import gzip, cStringIO | |||
zbuf = cStringIO.StringIO() | |||
zfile = gzip.GzipFile(mode = 'wb', fileobj = zbuf, compresslevel = 5) | |||
zfile.write(buf) | |||
zfile.close() | |||
return zbuf.getvalue() |
@@ -220,17 +220,28 @@ class Bean: | |||
idx_map[d.parentfield] = d.idx | |||
def run_method(self, method, *args, **kwargs): | |||
if not args: | |||
args = [] | |||
self.make_controller() | |||
def add_to_response(out, new_response): | |||
if isinstance(new_response, dict): | |||
out.update(new_response) | |||
if hasattr(self.controller, method): | |||
getattr(self.controller, method)(*args, **kwargs) | |||
add_to_response(webnotes.local.response, webnotes.call(getattr(self.controller, method), *args, **kwargs)) | |||
if hasattr(self.controller, 'custom_' + method): | |||
getattr(self.controller, 'custom_' + method)(*args, **kwargs) | |||
add_to_response(webnotes.local.response, webnotes.call(getattr(self.controller, 'custom_' + method), *args, **kwargs)) | |||
args = [self, method] + args | |||
for handler in webnotes.get_hooks("bean_event:" + self.doc.doctype + ":" + method) \ | |||
+ webnotes.get_hooks("bean_event:*:" + method): | |||
add_to_response(webnotes.local.response, webnotes.call(webnotes.get_attr(handler), *args, **kwargs)) | |||
notify(self, method, *args, **kwargs) | |||
self.set_doclist(self.controller.doclist) | |||
return webnotes.local.response | |||
def get_attr(self, method): | |||
self.make_controller() | |||
return getattr(self.controller, method, None) | |||
@@ -273,7 +284,10 @@ class Bean: | |||
self.set_doclist(new_doclist) | |||
def has_read_perm(self): | |||
return webnotes.has_permission(self.doc.doctype, "read", self.doc) | |||
return self.has_permission("read") | |||
def has_permission(self, permtype): | |||
return webnotes.has_permission(self.doc.doctype, permtype, self.doc) | |||
def save(self, check_links=1, ignore_permissions=None): | |||
if ignore_permissions: | |||
@@ -494,12 +508,6 @@ def clone(source_wrapper): | |||
return new_wrapper | |||
def notify(bean, caller, *args, **kwargs): | |||
for hook in webnotes.get_hooks().bean_event or []: | |||
doctype, trigger, handler = hook.split(":") | |||
if ((doctype=="*") or (doctype==bean.doc.doctype)) and caller==trigger: | |||
webnotes.get_attr(handler)(bean, trigger, *args, **kwargs) | |||
# for bc | |||
def getlist(doclist, parentfield): | |||
import webnotes.model.utils | |||
@@ -148,8 +148,7 @@ class Session: | |||
data = self.get_session_record() | |||
if data: | |||
# set language | |||
self.data = webnotes._dict({'data': data, | |||
'user':data.user, 'sid': self.sid}) | |||
self.data = webnotes._dict({'data': data, 'user':data.user, 'sid': self.sid}) | |||
else: | |||
self.start_as_guest() | |||
@@ -0,0 +1,102 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import json, inspect | |||
import datetime | |||
import gzip, cStringIO | |||
import webnotes | |||
import webnotes.utils | |||
import webnotes.sessions | |||
import webnotes.model.utils | |||
from werkzeug.local import LocalProxy | |||
def report_error(status_code): | |||
webnotes.errprint(webnotes.utils.get_traceback()) | |||
webnotes._response.status_code = status_code | |||
if webnotes.request_method == "POST": | |||
webnotes.conn.rollback() | |||
def build_response(): | |||
print_map = { | |||
'csv': print_csv, | |||
'download': print_raw, | |||
'json': print_json, | |||
'page': print_page | |||
} | |||
print_map.get(webnotes.response.get('type'), print_json)() | |||
def print_page(): | |||
"""print web page""" | |||
from webnotes.webutils import render | |||
render(webnotes.response['page_name']) | |||
def print_json(): | |||
make_logs() | |||
cleanup_docs() | |||
webnotes._response.headers["Content-Type"] = "text/json; charset: utf-8" | |||
print_zip(json.dumps(webnotes.local.response, default=json_handler, separators=(',',':'))) | |||
def cleanup_docs(): | |||
if webnotes.response.get('docs') and type(webnotes.response['docs'])!=dict: | |||
webnotes.response['docs'] = webnotes.model.utils.compress(webnotes.response['docs']) | |||
def print_csv(): | |||
webnotes._response.headers["Content-Type"] = \ | |||
"text/csv; charset: utf-8" | |||
webnotes._response.headers["Content-Disposition"] = \ | |||
"attachment; filename=%s.csv" % webnotes.response['doctype'].replace(' ', '_') | |||
webnotes._response.data = webnotes.response['result'] | |||
def print_raw(): | |||
webnotes._response.headers["Content-Type"] = \ | |||
mimetypes.guess_type(webnotes.response['filename'])[0] or "application/unknown" | |||
webnotes._response.headers["Content-Disposition"] = \ | |||
"filename=%s" % webnotes.response['filename'].replace(' ', '_') | |||
webnotes._response.data = webnotes.response['filecontent'] | |||
def make_logs(): | |||
"""make strings for msgprint and errprint""" | |||
if webnotes.error_log: | |||
# webnotes.response['exc'] = json.dumps("\n".join([cstr(d) for d in webnotes.error_log])) | |||
webnotes.response['exc'] = json.dumps([webnotes.utils.cstr(d) for d in webnotes.local.error_log]) | |||
if webnotes.local.message_log: | |||
webnotes.response['_server_messages'] = json.dumps([webnotes.utils.cstr(d) for d in webnotes.local.message_log]) | |||
if webnotes.debug_log and webnotes.conf.get("logging") or False: | |||
webnotes.response['_debug_messages'] = json.dumps(webnotes.local.debug_log) | |||
def print_zip(response): | |||
response = response.encode('utf-8') | |||
orig_len = len(response) | |||
if accept_gzip() and orig_len>512: | |||
response = compressBuf(response) | |||
webnotes._response.headers["Content-Encoding"] = "gzip" | |||
webnotes._response.headers["Content-Length"] = str(len(response)) | |||
webnotes._response.data = response | |||
def json_handler(obj): | |||
"""serialize non-serializable data for json""" | |||
# serialize date | |||
if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime)): | |||
return unicode(obj) | |||
elif isinstance(obj, LocalProxy): | |||
return unicode(obj) | |||
else: | |||
raise TypeError, """Object of type %s with value of %s is not JSON serializable""" % \ | |||
(type(obj), repr(obj)) | |||
def accept_gzip(): | |||
if "gzip" in webnotes.get_request_header("HTTP_ACCEPT_ENCODING", ""): | |||
return True | |||
def compressBuf(buf): | |||
zbuf = cStringIO.StringIO() | |||
zfile = gzip.GzipFile(mode = 'wb', fileobj = zbuf, compresslevel = 5) | |||
zfile.write(buf) | |||
zfile.close() | |||
return zbuf.getvalue() |
@@ -29,12 +29,25 @@ def get_form_params(): | |||
def execute(doctype, query=None, filters=None, fields=None, docstatus=None, | |||
group_by=None, order_by=None, limit_start=0, limit_page_length=None, | |||
as_list=False, with_childnames=False, debug=False): | |||
""" | |||
fields as list ["name", "owner"] or ["tabTask.name", "tabTask.owner"] | |||
filters as list of list [["Task", "name", "=", "TASK00001"]] | |||
""" | |||
if query: | |||
return run_custom_query(query) | |||
if not filters: filters = [] | |||
if not docstatus: docstatus = [] | |||
if not filters: | |||
filters = [] | |||
if isinstance(filters, basestring): | |||
filters = json.loads(filters) | |||
if not docstatus: | |||
docstatus = [] | |||
if not fields: | |||
fields = ["name"] | |||
if isinstance(fields, basestring): | |||
filters = json.loads(fields) | |||
args = prepare_args(doctype, filters, fields, docstatus, group_by, order_by, with_childnames) | |||
args.limit = add_limit(limit_start, limit_page_length) | |||