From b57f322c3d591c1b32208d2e5d310adf7bafcaa0 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 26 Mar 2014 18:15:29 +0530 Subject: [PATCH 1/5] FormMeta, Form Load, refactored client side Model and Meta - #478 --- frappe/__init__.py | 21 ++- frappe/boot.py | 15 +- frappe/core/doctype/doctype/doctype.py | 94 +++++------ frappe/core/doctype/page/page.py | 56 ++++--- frappe/core/doctype/user/user.js | 6 +- frappe/core/doctype/workflow/workflow.js | 8 +- .../permission_manager/permission_manager.js | 14 +- frappe/handler.py | 2 +- frappe/model/base_document.py | 10 ++ frappe/model/code.py | 1 + frappe/model/doctype.py | 2 + frappe/model/document.py | 66 +++++--- frappe/model/meta.py | 2 + frappe/public/build.json | 2 +- frappe/public/js/frappe/form/control.js | 5 +- frappe/public/js/frappe/form/formatters.js | 2 +- frappe/public/js/frappe/form/grid.js | 6 +- frappe/public/js/frappe/form/linked_with.js | 4 +- .../frappe/{model/doclist.js => form/save.js} | 76 +++++---- .../public/js/frappe/form/script_manager.js | 2 +- frappe/public/js/frappe/form/workflow.js | 4 +- frappe/public/js/frappe/model/create_new.js | 28 +++- frappe/public/js/frappe/model/meta.js | 11 +- frappe/public/js/frappe/model/model.js | 89 ++++------ frappe/public/js/frappe/model/perm.js | 12 +- frappe/public/js/frappe/model/sync.js | 98 ++--------- frappe/public/js/frappe/model/workflow.js | 34 +--- frappe/public/js/frappe/request.js | 3 +- frappe/public/js/frappe/views/reportview.js | 12 +- frappe/public/js/legacy/clientscriptAPI.js | 11 +- frappe/public/js/legacy/form.js | 77 +++------ frappe/public/js/legacy/handler.js | 19 ++- frappe/tests/test_document.py | 1 + frappe/tests/test_form_load.py | 16 ++ frappe/utils/response.py | 18 +- frappe/website/doctype/blog_post/blog_post.js | 2 +- frappe/widgets/form/load.py | 38 ++--- frappe/widgets/form/meta.py | 157 ++++++++++++++++++ frappe/widgets/form/run_method.py | 2 - frappe/widgets/form/utils.py | 2 - frappe/widgets/page.py | 33 ++-- 41 files changed, 548 insertions(+), 513 deletions(-) rename frappe/public/js/frappe/{model/doclist.js => form/save.js} (63%) create mode 100644 frappe/tests/test_form_load.py create mode 100644 frappe/widgets/form/meta.py diff --git a/frappe/__init__.py b/frappe/__init__.py index ca7802c25f..ca8d58d6ad 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -96,9 +96,9 @@ def init(site, sites_path=None): local.site_path = os.path.join(sites_path, site) local.message_log = [] local.debug_log = [] - local.response = _dict({}) local.lang = "en" local.request_method = request.method if request else None + local.response = _dict({"docs":[]}) local.conf = _dict(get_site_config()) local.initialised = True local.flags = _dict({}) @@ -115,6 +115,15 @@ def init(site, sites_path=None): setup_module_map() +def connect(site=None, db_name=None): + from database import Database + if site: + init(site) + local.db = Database(user=db_name or local.conf.db_name) + local.form_dict = _dict() + local.session = _dict() + set_user("Administrator") + def get_site_config(sites_path=None, site_path=None): config = {} @@ -200,16 +209,6 @@ def throw(msg, exc=ValidationError): def create_folder(path): if not os.path.exists(path): os.makedirs(path) -def connect(site=None, db_name=None): - from database import Database - if site: - init(site) - local.db = Database(user=db_name or local.conf.db_name) - local.response = _dict() - local.form_dict = _dict() - local.session = _dict() - set_user("Administrator") - def set_user(username): from frappe.utils.user import User local.session["user"] = username diff --git a/frappe/boot.py b/frappe/boot.py index 2470edd941..c811d4d7b1 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -65,9 +65,6 @@ def get_bootinfo(): for method in hooks.boot_session or []: frappe.get_attr(method)(bootinfo) - from frappe.model.utils import compress - bootinfo['docs'] = compress(bootinfo['docs']) - if bootinfo.lang: bootinfo.lang = unicode(bootinfo.lang) @@ -138,10 +135,10 @@ def add_home_page(bootinfo, doclist): home_page = frappe.get_application_home_page(frappe.session.user) try: - page_doclist = frappe.widgets.page.get(home_page) + page = frappe.widgets.page.get(home_page) except (frappe.DoesNotExistError, frappe.PermissionError), e: - page_doclist = frappe.widgets.page.get('desktop') - - bootinfo['home_page_html'] = page_doclist[0].content - bootinfo['home_page'] = page_doclist[0].name - doclist += page_doclist + frappe.message_log.pop() + page = frappe.widgets.page.get('desktop') + + bootinfo['home_page'] = page.name + doclist.append(page) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 8bac02bc72..2c05017e67 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -9,23 +9,20 @@ import os from frappe.utils import now, cint from frappe.model import no_value_fields +from frappe.model.document import Document -class DocType: - def __init__(self, doc=None, doclist=[]): - self.doc = doc - self.doclist = doclist - +class DocType(Document): def validate(self): if not frappe.conf.get("developer_mode"): frappe.throw("Not in Developer Mode! Set in site_config.json") for c in [".", "/", "#", "&", "=", ":", "'", '"']: - if c in self.doc.name: + if c in self.name: frappe.msgprint(c + " not allowed in name", raise_exception=1) self.validate_series() self.scrub_field_names() self.validate_title_field() - validate_fields(self.doclist.get({"doctype":"DocField"})) - validate_permissions(self.doclist.get({"doctype":"DocPerm"})) + validate_fields(self.get("fields")) + validate_permissions(self.get("permissions")) self.make_amendable() self.check_link_replacement_error() @@ -33,14 +30,14 @@ class DocType: if frappe.flags.in_import: return parent_list = frappe.db.sql("""SELECT parent - from tabDocField where fieldtype="Table" and options=%s""", self.doc.name) + from tabDocField where fieldtype="Table" and options=%s""", self.name) for p in parent_list: frappe.db.sql('UPDATE tabDocType SET modified=%s WHERE `name`=%s', (now(), p[0])) def scrub_field_names(self): restricted = ('name','parent','idx','owner','creation','modified','modified_by', 'parentfield','parenttype',"file_list") - for d in self.doclist: + for d in self.get("fields"): if d.parent and d.fieldtype: if (not d.fieldname): if d.label: @@ -52,16 +49,16 @@ class DocType: def validate_title_field(self): - if self.doc.title_field and \ - self.doc.title_field not in [d.fieldname for d in self.doclist.get({"doctype":"DocField"})]: + if self.title_field and \ + self.title_field not in [d.fieldname for d in self.get("fields")]: frappe.throw(_("Title field must be a valid fieldname")) def validate_series(self, autoname=None, name=None): - if not autoname: autoname = self.doc.autoname - if not name: name = self.doc.name + if not autoname: autoname = self.autoname + if not name: name = self.name - if not autoname and self.doclist.get({"fieldname":"naming_series"}): - self.doc.autoname = "naming_series:" + if not autoname and self.get("fields", {"fieldname":"naming_series"}): + self.autoname = "naming_series:" if autoname and (not autoname.startswith('field:')) and (not autoname.startswith('eval:')) \ and (not autoname=='Prompt') and (not autoname.startswith('naming_series:')): @@ -72,10 +69,10 @@ class DocType: def on_update(self): from frappe.model.db_schema import updatedb - updatedb(self.doc.name) + updatedb(self.name) self.change_modified_of_parent() - make_module_and_roles(self.doclist) + make_module_and_roles(self) from frappe import conf if (not frappe.flags.in_import) and conf.get('developer_mode') or 0: @@ -83,53 +80,53 @@ class DocType: self.make_controller_template() # update index - if not self.doc.custom: + if not self.custom: from frappe.model.code import load_doctype_module - module = load_doctype_module( self.doc.name, self.doc.module) + module = load_doctype_module( self.name, self.module) if hasattr(module, "on_doctype_update"): module.on_doctype_update() - frappe.clear_cache(doctype=self.doc.name) + frappe.clear_cache(doctype=self.name) def check_link_replacement_error(self): - for d in self.doclist.get({"doctype":"DocField", "fieldtype":"Select"}): + for d in self.get("fields", {"fieldtype":"Select"}): if (frappe.db.get_value("DocField", d.name, "options") or "").startswith("link:") \ and not d.options.startswith("link:"): frappe.msgprint("link: type Select fields are getting replaced. Please check for %s" % d.label, raise_exception=True) def on_trash(self): - frappe.db.sql("delete from `tabCustom Field` where dt = %s", self.doc.name) - frappe.db.sql("delete from `tabCustom Script` where dt = %s", self.doc.name) - frappe.db.sql("delete from `tabProperty Setter` where doc_type = %s", self.doc.name) - frappe.db.sql("delete from `tabReport` where ref_doctype=%s", self.doc.name) + frappe.db.sql("delete from `tabCustom Field` where dt = %s", self.name) + frappe.db.sql("delete from `tabCustom Script` where dt = %s", self.name) + frappe.db.sql("delete from `tabProperty Setter` where doc_type = %s", self.name) + frappe.db.sql("delete from `tabReport` where ref_doctype=%s", self.name) def before_rename(self, old, new, merge=False): if merge: frappe.throw(_("DocType can not be merged")) def after_rename(self, old, new, merge=False): - if self.doc.issingle: + if self.issingle: frappe.db.sql("""update tabSingles set doctype=%s where doctype=%s""", (new, old)) else: frappe.db.sql("rename table `tab%s` to `tab%s`" % (old, new)) def export_doc(self): from frappe.modules.export_file import export_to_files - export_to_files(record_list=[['DocType', self.doc.name]]) + export_to_files(record_list=[['DocType', self.name]]) def import_doc(self): from frappe.modules.import_module import import_from_files - import_from_files(record_list=[[self.doc.module, 'doctype', self.doc.name]]) + import_from_files(record_list=[[self.module, 'doctype', self.name]]) def make_controller_template(self): from frappe.modules import get_doc_path, get_module_path, scrub - pypath = os.path.join(get_doc_path(self.doc.module, - self.doc.doctype, self.doc.name), scrub(self.doc.name) + '.py') + pypath = os.path.join(get_doc_path(self.module, + self.doctype, self.name), scrub(self.name) + '.py') if not os.path.exists(pypath): # get app publisher for copyright - app = frappe.local.module_app[frappe.scrub(self.doc.module)] + app = frappe.local.module_app[frappe.scrub(self.module)] if not app: frappe.throw("App not found!") app_publisher = frappe.get_hooks(hook="app_publisher", app_name=app)[0] @@ -143,23 +140,22 @@ class DocType: """ if is_submittable is set, add amended_from docfields """ - if self.doc.is_submittable: + if self.is_submittable: if not frappe.db.sql("""select name from tabDocField - where fieldname = 'amended_from' and parent = %s""", self.doc.name): - new = self.doc.addchild('fields', 'DocField', self.doclist) - new.label = 'Amended From' - new.fieldtype = 'Link' - new.fieldname = 'amended_from' - new.options = self.doc.name - new.permlevel = 0 - new.read_only = 1 - new.print_hide = 1 - new.no_copy = 1 - new.idx = self.get_max_idx() + 1 + where fieldname = 'amended_from' and parent = %s""", self.name): + self.append("fields", { + "label": "Amended From", + "fieldtype": "Link", + "fieldname": "amended_from", + "options": self.name, + "read_only": 1, + "print_hide": 1, + "no_copy": 1 + }) def get_max_idx(self): max_idx = frappe.db.sql("""select max(idx) from `tabDocField` where parent = %s""", - self.doc.name) + self.name) return max_idx and max_idx[0][0] or 0 def validate_fields_for_doctype(doctype): @@ -329,14 +325,14 @@ def validate_permissions(permissions, for_remove=False): check_level_zero_is_set(d) remove_rights_for_single(d) -def make_module_and_roles(doclist, perm_doctype="DocPerm"): +def make_module_and_roles(doc, perm_fieldname="permissions"): try: - if not frappe.db.exists("Module Def", doclist[0].module): - m = frappe.bean({"doctype": "Module Def", "module_name": doclist[0].module}) + if not frappe.db.exists("Module Def", doc.module): + m = frappe.bean({"doctype": "Module Def", "module_name": doc.module}) m.insert() default_roles = ["Administrator", "Guest", "All"] - roles = [p.role for p in doclist.get({"doctype": perm_doctype})] + default_roles + roles = [p.role for p in doc.get(permissions)] + default_roles for role in list(set(roles)): if not frappe.db.exists("Role", role): diff --git a/frappe/core/doctype/page/page.py b/frappe/core/doctype/page/page.py index 3bb1cc587e..1a71c9ad00 100644 --- a/frappe/core/doctype/page/page.py +++ b/frappe/core/doctype/page/page.py @@ -3,11 +3,9 @@ from __future__ import unicode_literals import frappe +from frappe.model.document import Document -class DocType: - def __init__(self, d, dl): - self.doc, self.doclist = d,dl - +class Page(Document): def autoname(self): """ Creates a url friendly name for this page. @@ -15,17 +13,17 @@ class DocType: it will add name-1, name-2 etc. """ from frappe.utils import cint - if (self.doc.name and self.doc.name.startswith('New Page')) or not self.doc.name: - self.doc.name = self.doc.page_name.lower().replace('"','').replace("'",'').\ + if (self.name and self.name.startswith('New Page')) or not self.name: + self.name = self.page_name.lower().replace('"','').replace("'",'').\ replace(' ', '-')[:20] - if frappe.db.exists('Page',self.doc.name): + if frappe.db.exists('Page',self.name): cnt = frappe.db.sql("""select name from tabPage - where name like "%s-%%" order by name desc limit 1""" % self.doc.name) + where name like "%s-%%" order by name desc limit 1""" % self.name) if cnt: cnt = cint(cnt[0][0].split('-')[-1]) + 1 else: cnt = 1 - self.doc.name += '-' + str(cnt) + self.name += '-' + str(cnt) # export def on_update(self): @@ -35,16 +33,16 @@ class DocType: """ from frappe import conf from frappe.core.doctype.doctype.doctype import make_module_and_roles - make_module_and_roles(self.doclist, "Page Role") + make_module_and_roles(self, "roles") - if not frappe.flags.in_import and getattr(conf,'developer_mode', 0) and self.doc.standard=='Yes': + if not frappe.flags.in_import and getattr(conf,'developer_mode', 0) and self.standard=='Yes': from frappe.modules.export_file import export_to_files from frappe.modules import get_module_path, scrub import os - export_to_files(record_list=[['Page', self.doc.name]]) + export_to_files(record_list=[['Page', self.name]]) # write files - path = os.path.join(get_module_path(self.doc.module), 'page', scrub(self.doc.name), scrub(self.doc.name)) + path = os.path.join(get_module_path(self.module), 'page', scrub(self.name), scrub(self.name)) # js if not os.path.exists(path + '.js'): @@ -55,35 +53,39 @@ class DocType: title: '%s', single_column: true }); -}""" % (self.doc.name, self.doc.title)) +}""" % (self.name, self.title)) - def get_from_files(self): - """ - Loads page info from files in module - """ + def as_dict(self): + d = super(Page, self).as_dict() + for key in ("script", "style", "content"): + d[key] = self.get(key) + return d + + def load_assets(self): from frappe.modules import get_module_path, scrub import os - - path = os.path.join(get_module_path(self.doc.module), 'page', scrub(self.doc.name)) + + path = os.path.join(get_module_path(self.module), 'page', scrub(self.name)) # script - fpath = os.path.join(path, scrub(self.doc.name) + '.js') + fpath = os.path.join(path, scrub(self.name) + '.js') if os.path.exists(fpath): with open(fpath, 'r') as f: - self.doc.script = f.read() + self.script = f.read() # css - fpath = os.path.join(path, scrub(self.doc.name) + '.css') + fpath = os.path.join(path, scrub(self.name) + '.css') if os.path.exists(fpath): with open(fpath, 'r') as f: - self.doc.style = f.read() + self.style = f.read() # html - fpath = os.path.join(path, scrub(self.doc.name) + '.html') + fpath = os.path.join(path, scrub(self.name) + '.html') if os.path.exists(fpath): with open(fpath, 'r') as f: - self.doc.content = f.read() + self.content = f.read() if frappe.lang != 'en': from frappe.translate import get_lang_js - self.doc.script += get_lang_js("page", self.doc.name) + self.script += get_lang_js("page", self.name) + \ No newline at end of file diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index d7de077f58..a611a4d122 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -151,8 +151,7 @@ frappe.RoleEditor = Class.extend({ .each(function(i, checkbox) { checkbox.checked = false; }); // set user roles as checked - $.each(frappe.model.get("UserRole", {parent: cur_frm.doc.name, - parentfield: "user_roles"}), function(i, user_role) { + $.each((cur_frm.doc.user_roles || []), function(i, user_role) { var checkbox = $(me.wrapper) .find('[data-user-role="'+user_role.role+'"] input[type="checkbox"]').get(0); if(checkbox) checkbox.checked = true; @@ -163,8 +162,7 @@ frappe.RoleEditor = Class.extend({ var existing_roles_map = {}; var existing_roles_list = []; - $.each(frappe.model.get("UserRole", {parent: cur_frm.doc.name, - parentfield: "user_roles"}), function(i, user_role) { + $.each((cur_frm.doc.user_roles || []), function(i, user_role) { existing_roles_map[user_role.role] = user_role.name; existing_roles_list.push(user_role.role); }); diff --git a/frappe/core/doctype/workflow/workflow.js b/frappe/core/doctype/workflow/workflow.js index d1f25446d5..26b83403e5 100644 --- a/frappe/core/doctype/workflow/workflow.js +++ b/frappe/core/doctype/workflow/workflow.js @@ -20,11 +20,9 @@ frappe.core.Workflow = frappe.ui.form.Controller.extend({ } }, update_field_options: function() { - var fields = $.map(frappe.model.get("DocField", { - parent: this.frm.doc.document_type, - fieldtype: ["not in", frappe.model.no_value_type] - }), - function(d) { return d.fieldname; }); + var fields = $.map(frappe.model.get_doc("DocType", this.frm.doc.document_type).fields, function(d) { + return frappe.model.no_value_type.indexOf(d.fieldtype)===-1 ? d.fieldname : null; + }) frappe.meta.get_docfield("Workflow Document State", "update_field", this.frm.doc.name).options = [""].concat(fields); } diff --git a/frappe/core/page/permission_manager/permission_manager.js b/frappe/core/page/permission_manager/permission_manager.js index 95ec254578..9c05ea679a 100644 --- a/frappe/core/page/permission_manager/permission_manager.js +++ b/frappe/core/page/permission_manager/permission_manager.js @@ -428,17 +428,15 @@ frappe.PermissionEngine = Class.extend({ }); }, get_user_fields: function(doctype) { - var user_fields = frappe.model.get("DocField", {parent:doctype, - fieldtype:"Link", options:"User"}); - - user_fields = user_fields.concat(frappe.model.get("DocField", {parent:doctype, - fieldtype:"Select", link_doctype:"User"})) - + var user_fields = frappe.model.get_children("DocType", doctype, "fields", {fieldtype:"Link", options:"User"}) + user_fields = user_fields.concat(frappe.model.get_children("DocType", doctype, "fields", + {fieldtype:"Select", link_doctype:"User"})) + return user_fields }, get_link_fields: function(doctype) { - return link_fields = frappe.model.get("DocField", {parent:doctype, - fieldtype:"Link", options:["not in", ["User", '[Select]']]}); + return frappe.model.get_children("DocType", doctype, "fields", + {fieldtype:"Link", options:["not in", ["User", '[Select]']]}); } }) diff --git a/frappe/handler.py b/frappe/handler.py index 4076e6fecd..3f780567c8 100755 --- a/frappe/handler.py +++ b/frappe/handler.py @@ -65,7 +65,7 @@ def handle(): if cmd!='login': execute_cmd(cmd) - + return build_response("json") def execute_cmd(cmd): diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 65c012bcef..4877ec97fb 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -110,6 +110,16 @@ class BaseDocument(object): return self._valid_columns + def as_dict(self): + doc = self.get_valid_dict() + doc["doctype"] = self.doctype + for df in self.get_table_fields(): + doc[df.fieldname] = [d.as_dict() for d in (self.get(df.fieldname) or [])] + return doc + + def get_table_fields(self): + return self.meta.get('fields', {"fieldtype":"Table"}) + def get_table_field_doctype(self, fieldname): return self.meta.get("fields", {"fieldname":fieldname})[0].options diff --git a/frappe/model/code.py b/frappe/model/code.py index 9972cbfc62..b00815b623 100644 --- a/frappe/model/code.py +++ b/frappe/model/code.py @@ -41,6 +41,7 @@ def get_obj(dt = None, dn = None, doc=None, doclist=None, with_children = 0): def get_server_obj(doc, doclist = [], basedoctype = ''): # for test module = get_doctype_module(doc.doctype) + classname = doc.doctype.replace(" ", "") return load_doctype_module(doc.doctype, module).DocType(doc, doclist) def run_server_obj(server_obj, method_name, arg=None): diff --git a/frappe/model/doctype.py b/frappe/model/doctype.py index 5b651f6744..5fecaad9d5 100644 --- a/frappe/model/doctype.py +++ b/frappe/model/doctype.py @@ -242,6 +242,8 @@ def clear_cache(doctype=None): def clear_single(dt): frappe.cache().delete_value(cache_name(dt, False)) frappe.cache().delete_value(cache_name(dt, True)) + frappe.cache().delete_value("meta:" + dt) + frappe.cache().delete_value("form_meta:" + dt) if doctype_cache and (dt in doctype_cache): del doctype_cache[dt] diff --git a/frappe/model/document.py b/frappe/model/document.py index b3169da79d..3d2cb04b90 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -16,15 +16,20 @@ def get_doc(arg1, arg2=None): doctype = arg1 else: doctype = arg1.get("doctype") - + + controller = get_controller(doctype) + if controller: + return controller(arg1, arg2) + + return Document(arg1, arg2) + +def get_controller(doctype): module = load_doctype_module(doctype) classname = doctype.replace(" ", "") if hasattr(module, classname): _class = getattr(module, classname) if issubclass(_class, Document): - return getattr(module, classname)(arg1, arg2) - - return Document(arg1, arg2) + return getattr(module, classname) class Document(BaseDocument): def __init__(self, arg1, arg2=None): @@ -59,6 +64,9 @@ class Document(BaseDocument): else: d = frappe.db.get_value(self.doctype, self.name, "*", as_dict=1) + if not d: + frappe.throw("{}: {}, {}".format(_("Not Found"), + self.doctype, self.name), frappe.DoesNotExistError) self.update(d, valid_columns = d.keys()) for df in self.get_table_fields(): @@ -70,9 +78,6 @@ class Document(BaseDocument): else: self.set(df.fieldname, []) - def get_table_fields(self): - return self.meta.get('fields', {"fieldtype":"Table"}) - def has_permission(self, permtype): if getattr(self, "_ignore_permissions", False): return True @@ -293,23 +298,10 @@ class Document(BaseDocument): def run_method(self, method, *args, **kwargs): """run standard triggers, plus those in frappe""" - def add_to_response(out, new_response): - if isinstance(new_response, dict): - out.update(new_response) - if hasattr(self, method): - add_to_response(frappe.local.response, - getattr(self, method)(*args, **kwargs)) - - args = [self, method] + list(args or []) - - for handler in frappe.get_hooks("bean_event:" + self.doctype + ":" + method) \ - + frappe.get_hooks("bean_event:*:" + method): - add_to_response(frappe.local.response, - frappe.call(frappe.get_attr(handler), *args, **kwargs)) - - return frappe.local.response - + fn = lambda self, *args, **kwargs: getattr(self, method)(*args, **kwargs) + return Document.hook(fn)(self, *args, **kwargs) + def run_before_save_methods(self): if self._action=="save": self.run_method("validate") @@ -332,3 +324,31 @@ class Document(BaseDocument): self.run_method("on_cancel") elif self._action=="update_after_submit": self.run_method("on_update_after_submit") + + @staticmethod + def hook(f): + def add_to_response(new_response): + if isinstance(new_response, dict): + frappe.local.response.update(new_response) + + def compose(fn, *hooks): + def runner(self, method, *args, **kwargs): + add_to_response(fn(self, *args, **kwargs)) + for f in hooks: + add_to_response(f(self, method, *args, **kwargs)) + return frappe.local.response + + return runner + + def composer(self, *args, **kwargs): + hooks = [] + method = f.__name__ + for handler in frappe.get_hooks("bean_event:" + self.doctype + ":" + method) \ + + frappe.get_hooks("bean_event:*:" + method): + hooks.append(frappe.getattr(handler)) + + composed = compose(f, *hooks) + return composed(self, method, *args, **kwargs) + + return composer + diff --git a/frappe/model/meta.py b/frappe/model/meta.py index cf694e750d..730b90d5e1 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -12,6 +12,8 @@ from frappe.model.document import Document ###### def get_meta(doctype, cached=True): + # TODO: cache to be cleared + if cached: if doctype not in frappe.local.meta: frappe.local.meta[doctype] = frappe.cache().get_value("meta:" + doctype, lambda: Meta(doctype)) diff --git a/frappe/public/build.json b/frappe/public/build.json index 29134210f3..859d9b8998 100644 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -83,7 +83,6 @@ "public/js/frappe/model/model.js", "public/js/frappe/model/meta.js", - "public/js/frappe/model/doclist.js", "public/js/frappe/model/sync.js", "public/js/frappe/model/create_new.js", "public/js/frappe/model/perm.js", @@ -135,6 +134,7 @@ "public/js/frappe/form/toolbar.js", "public/js/frappe/form/infobar.js", "public/js/frappe/form/dashboard.js", + "public/js/frappe/form/save.js", "public/js/frappe/form/script_manager.js", "public/js/frappe/form/control.js", "public/js/frappe/form/link_selector.js", diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index 93ad0133ff..a652c2cc6d 100644 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -980,8 +980,9 @@ frappe.ui.form.ControlTable = frappe.ui.form.Control.extend({ this._super(); // add title if prev field is not column / section heading or html - var prev_fieldtype = frappe.model.get("DocField", - {parent: this.frm.doctype, idx: this.df.idx-1})[0].fieldtype; + var prev_fieldtype = frappe.model.get_children("DocType", this.frm.doctype, "fields", + {idx: this.df.idx-1}); + prev_fieldtype = prev_fieldtype ? prev_fieldtype[0].fieldtype : ""; if(["Column Break", "Section Break", "HTML"].indexOf(prev_fieldtype)===-1) { $("