Browse Source

FormMeta, Form Load, refactored client side Model and Meta - #478

version-14
Rushabh Mehta 11 years ago
parent
commit
b57f322c3d
41 changed files with 548 additions and 513 deletions
  1. +10
    -11
      frappe/__init__.py
  2. +6
    -9
      frappe/boot.py
  3. +45
    -49
      frappe/core/doctype/doctype/doctype.py
  4. +29
    -27
      frappe/core/doctype/page/page.py
  5. +2
    -4
      frappe/core/doctype/user/user.js
  6. +3
    -5
      frappe/core/doctype/workflow/workflow.js
  7. +6
    -8
      frappe/core/page/permission_manager/permission_manager.js
  8. +1
    -1
      frappe/handler.py
  9. +10
    -0
      frappe/model/base_document.py
  10. +1
    -0
      frappe/model/code.py
  11. +2
    -0
      frappe/model/doctype.py
  12. +43
    -23
      frappe/model/document.py
  13. +2
    -0
      frappe/model/meta.py
  14. +1
    -1
      frappe/public/build.json
  15. +3
    -2
      frappe/public/js/frappe/form/control.js
  16. +1
    -1
      frappe/public/js/frappe/form/formatters.js
  17. +1
    -5
      frappe/public/js/frappe/form/grid.js
  18. +1
    -3
      frappe/public/js/frappe/form/linked_with.js
  19. +37
    -39
      frappe/public/js/frappe/form/save.js
  20. +1
    -1
      frappe/public/js/frappe/form/script_manager.js
  21. +2
    -2
      frappe/public/js/frappe/form/workflow.js
  22. +22
    -6
      frappe/public/js/frappe/model/create_new.js
  23. +9
    -2
      frappe/public/js/frappe/model/meta.js
  24. +31
    -58
      frappe/public/js/frappe/model/model.js
  25. +2
    -10
      frappe/public/js/frappe/model/perm.js
  26. +12
    -86
      frappe/public/js/frappe/model/sync.js
  27. +8
    -26
      frappe/public/js/frappe/model/workflow.js
  28. +1
    -2
      frappe/public/js/frappe/request.js
  29. +6
    -6
      frappe/public/js/frappe/views/reportview.js
  30. +2
    -9
      frappe/public/js/legacy/clientscriptAPI.js
  31. +20
    -57
      frappe/public/js/legacy/form.js
  32. +10
    -9
      frappe/public/js/legacy/handler.js
  33. +1
    -0
      frappe/tests/test_document.py
  34. +16
    -0
      frappe/tests/test_form_load.py
  35. +10
    -8
      frappe/utils/response.py
  36. +1
    -1
      frappe/website/doctype/blog_post/blog_post.js
  37. +19
    -19
      frappe/widgets/form/load.py
  38. +157
    -0
      frappe/widgets/form/meta.py
  39. +0
    -2
      frappe/widgets/form/run_method.py
  40. +0
    -2
      frappe/widgets/form/utils.py
  41. +14
    -19
      frappe/widgets/page.py

+ 10
- 11
frappe/__init__.py View File

@@ -96,9 +96,9 @@ def init(site, sites_path=None):
local.site_path = os.path.join(sites_path, site) local.site_path = os.path.join(sites_path, site)
local.message_log = [] local.message_log = []
local.debug_log = [] local.debug_log = []
local.response = _dict({})
local.lang = "en" local.lang = "en"
local.request_method = request.method if request else None local.request_method = request.method if request else None
local.response = _dict({"docs":[]})
local.conf = _dict(get_site_config()) local.conf = _dict(get_site_config())
local.initialised = True local.initialised = True
local.flags = _dict({}) local.flags = _dict({})
@@ -115,6 +115,15 @@ def init(site, sites_path=None):


setup_module_map() 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): def get_site_config(sites_path=None, site_path=None):
config = {} config = {}
@@ -200,16 +209,6 @@ def throw(msg, exc=ValidationError):
def create_folder(path): def create_folder(path):
if not os.path.exists(path): os.makedirs(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): def set_user(username):
from frappe.utils.user import User from frappe.utils.user import User
local.session["user"] = username local.session["user"] = username


+ 6
- 9
frappe/boot.py View File

@@ -65,9 +65,6 @@ def get_bootinfo():
for method in hooks.boot_session or []: for method in hooks.boot_session or []:
frappe.get_attr(method)(bootinfo) frappe.get_attr(method)(bootinfo)
from frappe.model.utils import compress
bootinfo['docs'] = compress(bootinfo['docs'])

if bootinfo.lang: if bootinfo.lang:
bootinfo.lang = unicode(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) home_page = frappe.get_application_home_page(frappe.session.user)


try: try:
page_doclist = frappe.widgets.page.get(home_page)
page = frappe.widgets.page.get(home_page)
except (frappe.DoesNotExistError, frappe.PermissionError), e: 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)

+ 45
- 49
frappe/core/doctype/doctype/doctype.py View File

@@ -9,23 +9,20 @@ import os


from frappe.utils import now, cint from frappe.utils import now, cint
from frappe.model import no_value_fields 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): def validate(self):
if not frappe.conf.get("developer_mode"): if not frappe.conf.get("developer_mode"):
frappe.throw("Not in Developer Mode! Set in site_config.json") frappe.throw("Not in Developer Mode! Set in site_config.json")
for c in [".", "/", "#", "&", "=", ":", "'", '"']: for c in [".", "/", "#", "&", "=", ":", "'", '"']:
if c in self.doc.name:
if c in self.name:
frappe.msgprint(c + " not allowed in name", raise_exception=1) frappe.msgprint(c + " not allowed in name", raise_exception=1)
self.validate_series() self.validate_series()
self.scrub_field_names() self.scrub_field_names()
self.validate_title_field() 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.make_amendable()
self.check_link_replacement_error() self.check_link_replacement_error()


@@ -33,14 +30,14 @@ class DocType:
if frappe.flags.in_import: if frappe.flags.in_import:
return return
parent_list = frappe.db.sql("""SELECT parent 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: for p in parent_list:
frappe.db.sql('UPDATE tabDocType SET modified=%s WHERE `name`=%s', (now(), p[0])) frappe.db.sql('UPDATE tabDocType SET modified=%s WHERE `name`=%s', (now(), p[0]))


def scrub_field_names(self): def scrub_field_names(self):
restricted = ('name','parent','idx','owner','creation','modified','modified_by', restricted = ('name','parent','idx','owner','creation','modified','modified_by',
'parentfield','parenttype',"file_list") 'parentfield','parenttype',"file_list")
for d in self.doclist:
for d in self.get("fields"):
if d.parent and d.fieldtype: if d.parent and d.fieldtype:
if (not d.fieldname): if (not d.fieldname):
if d.label: if d.label:
@@ -52,16 +49,16 @@ class DocType:
def validate_title_field(self): 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")) frappe.throw(_("Title field must be a valid fieldname"))
def validate_series(self, autoname=None, name=None): 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:')) \ if autoname and (not autoname.startswith('field:')) and (not autoname.startswith('eval:')) \
and (not autoname=='Prompt') and (not autoname.startswith('naming_series:')): and (not autoname=='Prompt') and (not autoname.startswith('naming_series:')):
@@ -72,10 +69,10 @@ class DocType:


def on_update(self): def on_update(self):
from frappe.model.db_schema import updatedb from frappe.model.db_schema import updatedb
updatedb(self.doc.name)
updatedb(self.name)


self.change_modified_of_parent() self.change_modified_of_parent()
make_module_and_roles(self.doclist)
make_module_and_roles(self)
from frappe import conf from frappe import conf
if (not frappe.flags.in_import) and conf.get('developer_mode') or 0: if (not frappe.flags.in_import) and conf.get('developer_mode') or 0:
@@ -83,53 +80,53 @@ class DocType:
self.make_controller_template() self.make_controller_template()
# update index # update index
if not self.doc.custom:
if not self.custom:
from frappe.model.code import load_doctype_module 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"): if hasattr(module, "on_doctype_update"):
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): 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:") \ if (frappe.db.get_value("DocField", d.name, "options") or "").startswith("link:") \
and not d.options.startswith("link:"): and not d.options.startswith("link:"):
frappe.msgprint("link: type Select fields are getting replaced. Please check for %s" % d.label, frappe.msgprint("link: type Select fields are getting replaced. Please check for %s" % d.label,
raise_exception=True) raise_exception=True)


def on_trash(self): 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): def before_rename(self, old, new, merge=False):
if merge: if merge:
frappe.throw(_("DocType can not be merged")) frappe.throw(_("DocType can not be merged"))
def after_rename(self, old, new, merge=False): 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)) frappe.db.sql("""update tabSingles set doctype=%s where doctype=%s""", (new, old))
else: else:
frappe.db.sql("rename table `tab%s` to `tab%s`" % (old, new)) frappe.db.sql("rename table `tab%s` to `tab%s`" % (old, new))
def export_doc(self): def export_doc(self):
from frappe.modules.export_file import export_to_files 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): def import_doc(self):
from frappe.modules.import_module import import_from_files 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): def make_controller_template(self):
from frappe.modules import get_doc_path, get_module_path, scrub 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): if not os.path.exists(pypath):
# get app publisher for copyright # 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: if not app:
frappe.throw("App not found!") frappe.throw("App not found!")
app_publisher = frappe.get_hooks(hook="app_publisher", app_name=app)[0] 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 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 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): def get_max_idx(self):
max_idx = frappe.db.sql("""select max(idx) from `tabDocField` where parent = %s""", 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 return max_idx and max_idx[0][0] or 0


def validate_fields_for_doctype(doctype): def validate_fields_for_doctype(doctype):
@@ -329,14 +325,14 @@ def validate_permissions(permissions, for_remove=False):
check_level_zero_is_set(d) check_level_zero_is_set(d)
remove_rights_for_single(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: 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() m.insert()
default_roles = ["Administrator", "Guest", "All"] 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)): for role in list(set(roles)):
if not frappe.db.exists("Role", role): if not frappe.db.exists("Role", role):


+ 29
- 27
frappe/core/doctype/page/page.py View File

@@ -3,11 +3,9 @@


from __future__ import unicode_literals from __future__ import unicode_literals
import frappe 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): def autoname(self):
""" """
Creates a url friendly name for this page. Creates a url friendly name for this page.
@@ -15,17 +13,17 @@ class DocType:
it will add name-1, name-2 etc. it will add name-1, name-2 etc.
""" """
from frappe.utils import cint 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] 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 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: if cnt:
cnt = cint(cnt[0][0].split('-')[-1]) + 1 cnt = cint(cnt[0][0].split('-')[-1]) + 1
else: else:
cnt = 1 cnt = 1
self.doc.name += '-' + str(cnt)
self.name += '-' + str(cnt)


# export # export
def on_update(self): def on_update(self):
@@ -35,16 +33,16 @@ class DocType:
""" """
from frappe import conf from frappe import conf
from frappe.core.doctype.doctype.doctype import make_module_and_roles 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.export_file import export_to_files
from frappe.modules import get_module_path, scrub from frappe.modules import get_module_path, scrub
import os import os
export_to_files(record_list=[['Page', self.doc.name]])
export_to_files(record_list=[['Page', self.name]])
# write files # 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 # js
if not os.path.exists(path + '.js'): if not os.path.exists(path + '.js'):
@@ -55,35 +53,39 @@ class DocType:
title: '%s', title: '%s',
single_column: true 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 from frappe.modules import get_module_path, scrub
import os 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 # 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): if os.path.exists(fpath):
with open(fpath, 'r') as f: with open(fpath, 'r') as f:
self.doc.script = f.read()
self.script = f.read()


# css # 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): if os.path.exists(fpath):
with open(fpath, 'r') as f: with open(fpath, 'r') as f:
self.doc.style = f.read()
self.style = f.read()
# html # 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): if os.path.exists(fpath):
with open(fpath, 'r') as f: with open(fpath, 'r') as f:
self.doc.content = f.read()
self.content = f.read()
if frappe.lang != 'en': if frappe.lang != 'en':
from frappe.translate import get_lang_js 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)

+ 2
- 4
frappe/core/doctype/user/user.js View File

@@ -151,8 +151,7 @@ frappe.RoleEditor = Class.extend({
.each(function(i, checkbox) { checkbox.checked = false; }); .each(function(i, checkbox) { checkbox.checked = false; });
// set user roles as checked // 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) var checkbox = $(me.wrapper)
.find('[data-user-role="'+user_role.role+'"] input[type="checkbox"]').get(0); .find('[data-user-role="'+user_role.role+'"] input[type="checkbox"]').get(0);
if(checkbox) checkbox.checked = true; if(checkbox) checkbox.checked = true;
@@ -163,8 +162,7 @@ frappe.RoleEditor = Class.extend({
var existing_roles_map = {}; var existing_roles_map = {};
var existing_roles_list = []; 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_map[user_role.role] = user_role.name;
existing_roles_list.push(user_role.role); existing_roles_list.push(user_role.role);
}); });


+ 3
- 5
frappe/core/doctype/workflow/workflow.js View File

@@ -20,11 +20,9 @@ frappe.core.Workflow = frappe.ui.form.Controller.extend({
} }
}, },
update_field_options: function() { 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 frappe.meta.get_docfield("Workflow Document State", "update_field", this.frm.doc.name).options
= [""].concat(fields); = [""].concat(fields);
} }


+ 6
- 8
frappe/core/page/permission_manager/permission_manager.js View File

@@ -428,17 +428,15 @@ frappe.PermissionEngine = Class.extend({
}); });
}, },
get_user_fields: function(doctype) { 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 return user_fields
}, },
get_link_fields: function(doctype) { 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]']]});
} }
}) })




+ 1
- 1
frappe/handler.py View File

@@ -65,7 +65,7 @@ def handle():
if cmd!='login': if cmd!='login':
execute_cmd(cmd) execute_cmd(cmd)
return build_response("json") return build_response("json")


def execute_cmd(cmd): def execute_cmd(cmd):


+ 10
- 0
frappe/model/base_document.py View File

@@ -110,6 +110,16 @@ class BaseDocument(object):
return self._valid_columns 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): def get_table_field_doctype(self, fieldname):
return self.meta.get("fields", {"fieldname":fieldname})[0].options return self.meta.get("fields", {"fieldname":fieldname})[0].options


+ 1
- 0
frappe/model/code.py View File

@@ -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 = ''): def get_server_obj(doc, doclist = [], basedoctype = ''):
# for test # for test
module = get_doctype_module(doc.doctype) module = get_doctype_module(doc.doctype)
classname = doc.doctype.replace(" ", "")
return load_doctype_module(doc.doctype, module).DocType(doc, doclist) return load_doctype_module(doc.doctype, module).DocType(doc, doclist)


def run_server_obj(server_obj, method_name, arg=None): def run_server_obj(server_obj, method_name, arg=None):


+ 2
- 0
frappe/model/doctype.py View File

@@ -242,6 +242,8 @@ def clear_cache(doctype=None):
def clear_single(dt): def clear_single(dt):
frappe.cache().delete_value(cache_name(dt, False)) frappe.cache().delete_value(cache_name(dt, False))
frappe.cache().delete_value(cache_name(dt, True)) 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): if doctype_cache and (dt in doctype_cache):
del doctype_cache[dt] del doctype_cache[dt]


+ 43
- 23
frappe/model/document.py View File

@@ -16,15 +16,20 @@ def get_doc(arg1, arg2=None):
doctype = arg1 doctype = arg1
else: else:
doctype = arg1.get("doctype") 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) module = load_doctype_module(doctype)
classname = doctype.replace(" ", "") classname = doctype.replace(" ", "")
if hasattr(module, classname): if hasattr(module, classname):
_class = getattr(module, classname) _class = getattr(module, classname)
if issubclass(_class, Document): if issubclass(_class, Document):
return getattr(module, classname)(arg1, arg2)
return Document(arg1, arg2)
return getattr(module, classname)


class Document(BaseDocument): class Document(BaseDocument):
def __init__(self, arg1, arg2=None): def __init__(self, arg1, arg2=None):
@@ -59,6 +64,9 @@ class Document(BaseDocument):
else: else:
d = frappe.db.get_value(self.doctype, self.name, "*", as_dict=1) 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()) self.update(d, valid_columns = d.keys())


for df in self.get_table_fields(): for df in self.get_table_fields():
@@ -70,9 +78,6 @@ class Document(BaseDocument):
else: else:
self.set(df.fieldname, []) self.set(df.fieldname, [])
def get_table_fields(self):
return self.meta.get('fields', {"fieldtype":"Table"})

def has_permission(self, permtype): def has_permission(self, permtype):
if getattr(self, "_ignore_permissions", False): if getattr(self, "_ignore_permissions", False):
return True return True
@@ -293,23 +298,10 @@ class Document(BaseDocument):
def run_method(self, method, *args, **kwargs): def run_method(self, method, *args, **kwargs):
"""run standard triggers, plus those in frappe""" """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): 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): def run_before_save_methods(self):
if self._action=="save": if self._action=="save":
self.run_method("validate") self.run_method("validate")
@@ -332,3 +324,31 @@ class Document(BaseDocument):
self.run_method("on_cancel") self.run_method("on_cancel")
elif self._action=="update_after_submit": elif self._action=="update_after_submit":
self.run_method("on_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


+ 2
- 0
frappe/model/meta.py View File

@@ -12,6 +12,8 @@ from frappe.model.document import Document
###### ######


def get_meta(doctype, cached=True): def get_meta(doctype, cached=True):
# TODO: cache to be cleared

if cached: if cached:
if doctype not in frappe.local.meta: if doctype not in frappe.local.meta:
frappe.local.meta[doctype] = frappe.cache().get_value("meta:" + doctype, lambda: Meta(doctype)) frappe.local.meta[doctype] = frappe.cache().get_value("meta:" + doctype, lambda: Meta(doctype))


+ 1
- 1
frappe/public/build.json View File

@@ -83,7 +83,6 @@
"public/js/frappe/model/model.js", "public/js/frappe/model/model.js",
"public/js/frappe/model/meta.js", "public/js/frappe/model/meta.js",
"public/js/frappe/model/doclist.js",
"public/js/frappe/model/sync.js", "public/js/frappe/model/sync.js",
"public/js/frappe/model/create_new.js", "public/js/frappe/model/create_new.js",
"public/js/frappe/model/perm.js", "public/js/frappe/model/perm.js",
@@ -135,6 +134,7 @@
"public/js/frappe/form/toolbar.js", "public/js/frappe/form/toolbar.js",
"public/js/frappe/form/infobar.js", "public/js/frappe/form/infobar.js",
"public/js/frappe/form/dashboard.js", "public/js/frappe/form/dashboard.js",
"public/js/frappe/form/save.js",
"public/js/frappe/form/script_manager.js", "public/js/frappe/form/script_manager.js",
"public/js/frappe/form/control.js", "public/js/frappe/form/control.js",
"public/js/frappe/form/link_selector.js", "public/js/frappe/form/link_selector.js",


+ 3
- 2
frappe/public/js/frappe/form/control.js View File

@@ -980,8 +980,9 @@ frappe.ui.form.ControlTable = frappe.ui.form.Control.extend({
this._super(); this._super();
// add title if prev field is not column / section heading or html // 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) { if(["Column Break", "Section Break", "HTML"].indexOf(prev_fieldtype)===-1) {
$("<label>" + this.df.label + "<label>").appendTo(this.wrapper); $("<label>" + this.df.label + "<label>").appendTo(this.wrapper);


+ 1
- 1
frappe/public/js/frappe/form/formatters.js View File

@@ -98,7 +98,7 @@ frappe.form.formatters = {
return "<pre>" + (value==null ? "" : $("<div>").text(value).html()) + "</pre>" return "<pre>" + (value==null ? "" : $("<div>").text(value).html()) + "</pre>"
}, },
WorkflowState: function(value) { WorkflowState: function(value) {
workflow_state = frappe.model.get("Workflow State", value)[0];
workflow_state = frappe.model.get_doc("Workflow State", value);
if(workflow_state) { if(workflow_state) {
return repl("<span class='label label-%(style)s' \ return repl("<span class='label label-%(style)s' \
data-workflow-state='%(value)s'\ data-workflow-state='%(value)s'\


+ 1
- 5
frappe/public/js/frappe/form/grid.js View File

@@ -118,11 +118,7 @@ frappe.ui.form.Grid = Class.extend({
}); });
}, },
get_data: function() { get_data: function() {
var data = frappe.model.get(this.df.options, {
"parenttype": this.frm.doctype,
"parentfield": this.df.fieldname,
"parent": this.frm.docname
});
var data = this.frm.doc[this.df.fieldname] || [];
data.sort(function(a, b) { return a.idx - b.idx}); data.sort(function(a, b) { return a.idx - b.idx});
return data; return data;
}, },


+ 1
- 3
frappe/public/js/frappe/form/linked_with.js View File

@@ -1,9 +1,7 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt // MIT License. See license.txt


// for license information please see license.txt

frappe.provide("frappe.ui.form")
frappe.provide("frappe.ui.form");


frappe.ui.form.LinkedWith = Class.extend({ frappe.ui.form.LinkedWith = Class.extend({
init: function(opts) { init: function(opts) {


frappe/public/js/frappe/model/doclist.js → frappe/public/js/frappe/form/save.js View File

@@ -1,45 +1,38 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
// MIT License. See license.txt


frappe.provide("frappe.model");
frappe.model.DocList = Class.extend({
init: function(doctype, name) {
this.doctype = doctype; this.name = name;
this.doclist = frappe.model.get_doclist(this.doctype, this.name);
this.doc = this.doclist[0];
},
save: function(action, callback, btn) {
this.check_name();
if(this.check_mandatory()) {
var me = this;
this._call({
frappe.provide("frappe.ui.form");

frappe.ui.form.save = function(frm, action, callback, btn) {
var save = function() {
check_name();
if(check_mandatory()) {
_call({
method: "frappe.widgets.form.save.savedocs", method: "frappe.widgets.form.save.savedocs",
args: { docs: frappe.model.compress(this.doclist), action:action},
args: { docs: frm.doc, action:action},
callback: function(r) { callback: function(r) {
$(document).trigger("save", me.doc);
$(document).trigger("save", frm.doc);
callback(r); callback(r);
}, },
btn: btn btn: btn
}); });
} }
},
};
cancel: function(callback, btn) {
var me = this;
this._call({
var cancel = function() {
_call({
method: "frappe.widgets.form.save.cancel", method: "frappe.widgets.form.save.cancel",
args: { doctype: this.doctype, name: this.name },
args: { doctype: frm.doc.doctype, name: frm.doc.name },
callback: function(r) { callback: function(r) {
$(document).trigger("save", frappe.model.get_doc(me.doctype, me.name));
$(document).trigger("save", frm.doc);
callback(r); callback(r);
}, },
btn: btn btn: btn
}); });
},
};
check_name: function() {
var doc = this.doclist[0];
var check_name = function() {
var doc = frm.doc;
var meta = locals.DocType[doc.doctype]; var meta = locals.DocType[doc.doctype];
if(doc.__islocal && (meta && meta.autoname if(doc.__islocal && (meta && meta.autoname
&& meta.autoname.toLowerCase()=='prompt')) { && meta.autoname.toLowerCase()=='prompt')) {
@@ -51,23 +44,22 @@ frappe.model.DocList = Class.extend({
throw "name required"; throw "name required";
} }
} }
},
};
check_mandatory: function() {
var me = this;
var check_mandatory = function() {
var has_errors = false; var has_errors = false;
this.scroll_set = false;
frm.scroll_set = false;
if(this.doc.docstatus==2) return true; // don't check for cancel
if(frm.doc.docstatus==2) return true; // don't check for cancel
$.each(this.doclist, function(i, doc) {
$.each(frm.model.get_all_docs(frm.doc), function(i, doc) {
var error_fields = []; var error_fields = [];
$.each(frappe.meta.docfield_list[doc.doctype] || [], function(i, docfield) { $.each(frappe.meta.docfield_list[doc.doctype] || [], function(i, docfield) {
if(docfield.fieldname) { if(docfield.fieldname) {
var df = frappe.meta.get_docfield(doc.doctype, var df = frappe.meta.get_docfield(doc.doctype,
docfield.fieldname, me.doclist[0].name);
docfield.fieldname, frm.doc.name);


if(df.reqd && !frappe.model.has_value(doc.doctype, doc.name, df.fieldname)) { if(df.reqd && !frappe.model.has_value(doc.doctype, doc.name, df.fieldname)) {
has_errors = true; has_errors = true;
@@ -87,17 +79,17 @@ frappe.model.DocList = Class.extend({
}); });
return !has_errors; return !has_errors;
},
};
scroll_to: function(fieldname) {
var scroll_to = function(fieldname) {
var f = cur_frm.fields_dict[fieldname]; var f = cur_frm.fields_dict[fieldname];
if(f) { if(f) {
$(document).scrollTop($(f.wrapper).offset().top - 100); $(document).scrollTop($(f.wrapper).offset().top - 100);
} }
this.scroll_set = true;
},
frm.scroll_set = true;
};


_call: function(opts) {
var _call = function(opts) {
// opts = { // opts = {
// method: "some server method", // method: "some server method",
// args: {args to be passed}, // args: {args to be passed},
@@ -114,5 +106,11 @@ frappe.model.DocList = Class.extend({
opts.callback && opts.callback(r); opts.callback && opts.callback(r);
} }
}) })
},
});
};
if(action==="cancel") {
cancel();
} else {
save();
}
}

+ 1
- 1
frappe/public/js/frappe/form/script_manager.js View File

@@ -109,7 +109,7 @@ frappe.ui.form.ScriptManager = Class.extend({
} }
}, },
copy_from_first_row: function(parentfield, current_row, fieldnames) { copy_from_first_row: function(parentfield, current_row, fieldnames) {
var doclist = frappe.model.get_doclist(this.frm.doc.doctype, this.frm.doc.name, {parentfield: parentfield});
var doclist = this.frm.doc[parentfield];
if(doclist.length===1 || doclist[0]===current_row) return; if(doclist.length===1 || doclist[0]===current_row) return;
$.each(fieldnames, function(i, fieldname) { $.each(fieldnames, function(i, fieldname) {


+ 2
- 2
frappe/public/js/frappe/form/workflow.js View File

@@ -79,7 +79,7 @@ frappe.ui.form.States = Class.extend({
// show current state on the button // show current state on the button
this.workflow_button.find(".state-text").text(state); this.workflow_button.find(".state-text").text(state);
var state_doc = frappe.model.get("Workflow State", {name:state})[0];
var state_doc = frappe.model.get_doc("Workflow State", state);


if (state_doc) { if (state_doc) {
// set the icon // set the icon
@@ -109,7 +109,7 @@ frappe.ui.form.States = Class.extend({


$.each(frappe.workflow.get_transitions(this.frm.doctype, state), function(i, d) { $.each(frappe.workflow.get_transitions(this.frm.doctype, state), function(i, d) {
if(in_list(user_roles, d.allowed)) { if(in_list(user_roles, d.allowed)) {
d.icon = frappe.model.get("Workflow State", {name:d.next_state})[0].icon;
d.icon = frappe.model.get("Workflow State", d.next_state).icon;
$(repl('<li><a href="#" data-action="%(action)s">\ $(repl('<li><a href="#" data-action="%(action)s">\
<i class="icon icon-%(icon)s"></i> %(action)s</a></li>', d)) <i class="icon icon-%(icon)s"></i> %(action)s</a></li>', d))


+ 22
- 6
frappe/public/js/frappe/model/create_new.js View File

@@ -122,20 +122,36 @@ $.extend(frappe.model, {
return d; return d;
}, },
copy_doc: function(dt, dn, from_amend) {
copy_doc: function(doc, from_amend) {
var no_copy_list = ['name','amended_from','amendment_date','cancel_reason']; var no_copy_list = ['name','amended_from','amendment_date','cancel_reason'];
var newdoc = frappe.model.get_new_doc(dt);
var newdoc = frappe.model.get_new_doc(doc.doctype);


for(var key in locals[dt][dn]) {
for(var key in doc) {
// dont copy name and blank fields // dont copy name and blank fields
var df = frappe.meta.get_docfield(dt, key);
var df = frappe.meta.get_docfield(doc.doctype, key);
if(key.substr(0,2)!='__' if(key.substr(0,2)!='__'
&& !in_list(no_copy_list, key) && !in_list(no_copy_list, key)
&& !(df && (!from_amend && cint(df.no_copy)==1))) { && !(df && (!from_amend && cint(df.no_copy)==1))) {
newdoc[key] = locals[dt][dn][key];
value = doc[key];
if(df.fieldtype==="Table") {
newdoc[key] = [];
$.each(value || [], function(i, d) {
newdoc[key].push(frappe.model.copy_doc(d, from_amend))
})
} else {
newdoc[key] = doc[key];
}
} }
} }

newdoc.__islocal = 1;
newdoc.docstatus = 0;
newdoc.owner = user;
newdoc.creation = '';
newdoc.modified_by = user;
newdoc.modified = '';

return newdoc; return newdoc;
}, },
@@ -167,7 +183,7 @@ $.extend(frappe.model, {
method: opts.method, method: opts.method,
args: { args: {
"source_name": opts.source_name, "source_name": opts.source_name,
"target_doclist": frappe.model.get_doclist(cur_frm.doc.doctype, cur_frm.doc.name)
"target_doclist": cur_frm.doc
}, },
callback: function(r) { callback: function(r) {
if(!r.exc) { if(!r.exc) {


+ 9
- 2
frappe/public/js/frappe/model/meta.js View File

@@ -8,6 +8,13 @@ frappe.provide('frappe.meta.doctypes');
frappe.provide("frappe.meta.precision_map"); frappe.provide("frappe.meta.precision_map");


$.extend(frappe.meta, { $.extend(frappe.meta, {
sync: function(doc) {
$.each(doc.fields, function(i, df) {
frappe.meta.add_field(df);
})
frappe.meta.sync_messages(doc);
},
// build docfield_map and docfield_list // build docfield_map and docfield_list
add_field: function(df) { add_field: function(df) {
frappe.provide('frappe.meta.docfield_map.' + df.parent); frappe.provide('frappe.meta.docfield_map.' + df.parent);
@@ -84,8 +91,8 @@ $.extend(frappe.meta, {
}, },


get_parentfield: function(parent_dt, child_dt) { get_parentfield: function(parent_dt, child_dt) {
var df = frappe.model.get("DocField", {parent:parent_dt, fieldtype:"Table",
options:child_dt})
var df = (frappe.model.get_doc("DocType", parent_dt).fields || []).filter(function(d)
{ return d.fieldtype==="Table" && options===child_dt })
if(!df.length) if(!df.length)
throw "parentfield not found for " + parent_dt + ", " + child_dt; throw "parentfield not found for " + parent_dt + ", " + child_dt;
return df[0].fieldname; return df[0].fieldname;


+ 31
- 58
frappe/public/js/frappe/model/model.js View File

@@ -46,8 +46,8 @@ $.extend(frappe.model, {
} else { } else {
var cached_timestamp = null; var cached_timestamp = null;
if(localStorage["_doctype:" + doctype]) { if(localStorage["_doctype:" + doctype]) {
var cached_doclist = JSON.parse(localStorage["_doctype:" + doctype]);
cached_timestamp = cached_doclist[0].modified;
var cached_doc = JSON.parse(localStorage["_doctype:" + doctype]);
cached_timestamp = cached_doc.modified;
} }
return frappe.call({ return frappe.call({
method:'frappe.widgets.form.load.getdoctype', method:'frappe.widgets.form.load.getdoctype',
@@ -64,7 +64,7 @@ $.extend(frappe.model, {
return; return;
} }
if(r.message=="use_cache") { if(r.message=="use_cache") {
frappe.model.sync(cached_doclist);
frappe.model.sync(cached_doc);
} else { } else {
localStorage["_doctype:" + doctype] = JSON.stringify(r.docs); localStorage["_doctype:" + doctype] = JSON.stringify(r.docs);
} }
@@ -206,10 +206,10 @@ $.extend(frappe.model, {
}, },


get: function(doctype, filters) { get: function(doctype, filters) {
var src = locals[doctype] || locals[":" + doctype] || [];
if($.isEmptyObject(src))
var docsdict = locals[doctype] || locals[":" + doctype] || [];
if($.isEmptyObject(docsdict))
return []; return [];
return frappe.utils.filter_dict(src, filters);
return frappe.utils.filter_dict(docsdict, filters);
}, },
get_value: function(doctype, filters, fieldname) { get_value: function(doctype, filters, fieldname) {
@@ -277,55 +277,16 @@ $.extend(frappe.model, {
return locals[doctype] ? locals[doctype][name] : null; return locals[doctype] ? locals[doctype][name] : null;
}, },
get_doclist: function(doctype, name, filters) {
var doclist = [];
if(!locals[doctype])
return doclist;

doclist[0] = locals[doctype][name];

$.each(frappe.model.get("DocField", {parent:doctype, fieldtype:"Table"}),
function(i, table_field) {
var child_doclist = frappe.model.get(table_field.options, {
parent:name, parenttype: doctype,
parentfield: table_field.fieldname});
if($.isArray(child_doclist)) {
child_doclist.sort(function(a, b) { return a.idx - b.idx; });
doclist = doclist.concat(child_doclist);
}
}
);
if(filters) {
doclist = frappe.utils.filter_dict(doclist, filters);
}
return doclist;
},

get_children: function(doctype, parent, parentfield, parenttype) {
if(parenttype) {
var l = frappe.model.get(doctype, {parent:parent,
parentfield:parentfield, parenttype:parenttype});
get_children: function(doctype, parent, parentfield, filters) {
if($.isPlainObject(parentfield)) {
var doc = doctype;
var filters = parentfield;
var parentfield = parent;
return frappe.utils.filter_dict((doc[parentfield] || []), filters);
} else { } else {
var l = frappe.model.get(doctype, {parent:parent,
parentfield:parentfield});
return frappe.utils.filter_dict((frappe.model.get_doc(doctype, parent)[parentfield] || []), filters);
} }

if(l.length) {
l.sort(function(a,b) { return flt(a.idx) - flt(b.idx) });
// renumber
$.each(l, function(i, v) { v.idx = i+1; }); // for chrome bugs ???
}
return l;
},

clear_doclist: function(doctype, name) {
$.each(frappe.model.get_doclist(doctype, name), function(i, d) {
if(d) frappe.model.clear_doc(d.doctype, d.name);
});
}, },
clear_table: function(doctype, parenttype, parent, parentfield) { clear_table: function(doctype, parenttype, parent, parentfield) {
@@ -337,7 +298,7 @@ $.extend(frappe.model, {
}, },


remove_from_locals: function(doctype, name) { remove_from_locals: function(doctype, name) {
this.clear_doclist(doctype, name);
this.clear_doc(doctype, name);
if(frappe.views.formview[doctype]) { if(frappe.views.formview[doctype]) {
delete frappe.views.formview[doctype].frm.opendocs[name]; delete frappe.views.formview[doctype].frm.opendocs[name];
} }
@@ -358,13 +319,14 @@ $.extend(frappe.model, {
get_no_copy_list: function(doctype) { get_no_copy_list: function(doctype) {
var no_copy_list = ['name','amended_from','amendment_date','cancel_reason']; var no_copy_list = ['name','amended_from','amendment_date','cancel_reason'];
$.each(frappe.model.get("DocField", {parent:doctype}), function(i, df) {
$.each(frappe.model.get_doc("DocType", doctype).fields || [], function(i, df) {
if(cint(df.no_copy)) no_copy_list.push(df.fieldname); if(cint(df.no_copy)) no_copy_list.push(df.fieldname);
}) })
return no_copy_list; return no_copy_list;
}, },


// args: source (doclist), target (doctype), table_map, field_map, callback
// args: source (doc), target (doc), table_map, field_map, callback
map: function(args) { map: function(args) {
frappe.model.with_doctype(args.target, function() { frappe.model.with_doctype(args.target, function() {
var map_info = frappe.model.map_info[args.target] var map_info = frappe.model.map_info[args.target]
@@ -436,7 +398,7 @@ $.extend(frappe.model, {
}, },
callback: function(r, rt) { callback: function(r, rt) {
if(!r.exc) { if(!r.exc) {
frappe.model.clear_doclist(doctype, docname);
frappe.model.clear_doc(doctype, docname);
if(frappe.ui.toolbar.recent) if(frappe.ui.toolbar.recent)
frappe.ui.toolbar.recent.remove(doctype, docname); frappe.ui.toolbar.recent.remove(doctype, docname);
if(callback) callback(r,rt); if(callback) callback(r,rt);
@@ -499,9 +461,20 @@ $.extend(frappe.model, {
frappe.throw(frappe._("Please specify") + ": " + frappe.throw(frappe._("Please specify") + ": " +
frappe._(frappe.meta.get_label(doc.doctype, fieldname, doc.parent || doc.name))); frappe._(frappe.meta.get_label(doc.doctype, fieldname, doc.parent || doc.name)));
} }
},
get_all_docs: function(doc) {
var all = [doc];
for(key in doc) {
if($.isArray(doc[key])) {
$.each(doc[key], function(i, d) {
all.push(d);
});
}
}
return all;
} }
}); });


// legacy // legacy
getchildren = frappe.model.get_children getchildren = frappe.model.get_children
make_doclist = frappe.model.get_doclist

+ 2
- 10
frappe/public/js/frappe/model/perm.js View File

@@ -38,17 +38,9 @@ $.extend(frappe.perm, {
var perm = [{read: 0}]; var perm = [{read: 0}];
var meta = frappe.model.get_doc("DocType", doctype); var meta = frappe.model.get_doc("DocType", doctype);
if(!meta) { if(!meta) {
return perm; return perm;
} else if(meta.istable) {
// if a child table, use permissions of parent form
var parent_df = frappe.model.get("DocField", {fieldtype: "Table", options: doctype});
if(parent_df.length) {
if(docname) {
docname = frappe.model.get_doc(doctype, docname).parent;
}
doctype = parent_df[0].parent;
}
} }
if(user==="Administrator" || user_roles.indexOf("Administrator")!==-1) { if(user==="Administrator" || user_roles.indexOf("Administrator")!==-1) {
@@ -60,7 +52,7 @@ $.extend(frappe.perm, {
return perm; return perm;
} }
var docperms = frappe.model.get("DocPerm", {parent: doctype});
var docperms = frappe.model.get_doc("DocType", doctype).permissions || [];
$.each(docperms, function(i, p) { $.each(docperms, function(i, p) {
// if user has this role // if user has this role
if(user_roles.indexOf(p.role)!==-1) { if(user_roles.indexOf(p.role)!==-1) {


+ 12
- 86
frappe/public/js/frappe/model/sync.js View File

@@ -5,35 +5,21 @@ $.extend(frappe.model, {
docinfo: {}, docinfo: {},
sync: function(r) { sync: function(r) {
/* docs: /* docs:
extract doclist, docinfo (attachments, comments, assignments)
extract docs, docinfo (attachments, comments, assignments)
from incoming request and set in `locals` and `frappe.model.docinfo` from incoming request and set in `locals` and `frappe.model.docinfo`
*/ */


if(!r.docs && !r.docinfo) r = {docs:r}; if(!r.docs && !r.docinfo) r = {docs:r};


if(r.docs) { if(r.docs) {
var doclist = r.docs;
if(doclist._kl)
doclist = frappe.model.expand(doclist);

if(doclist && doclist.length)
frappe.model.clear_doclist(doclist[0].doctype, doclist[0].name)

var last_parent_name = null; var last_parent_name = null;
var dirty = []; var dirty = [];
$.each(doclist, function(i, d) {

$.each(r.docs, function(i, d) {
if(!d.name && d.__islocal) { // get name (local if required) if(!d.name && d.__islocal) { // get name (local if required)
frappe.model.clear_doc(d)
d.name = frappe.model.get_new_name(d.doctype); d.name = frappe.model.get_new_name(d.doctype);
frappe.provide("frappe.model.docinfo." + d.doctype + "." + d.name); frappe.provide("frappe.model.docinfo." + d.doctype + "." + d.name);
if(!d.parenttype)
last_parent_name = d.name;
if(dirty.indexOf(d.parenttype || d.doctype)===-1) dirty.push(d.parenttype || d.doctype);
}

// set parent for subsequent orphans
if(d.parenttype && !d.parent && d.__islocal) {
d.parent = last_parent_name;
} }


if(!locals[d.doctype]) if(!locals[d.doctype])
@@ -41,14 +27,15 @@ $.extend(frappe.model, {


locals[d.doctype][d.name] = d; locals[d.doctype][d.name] = d;
d.__last_sync_on = new Date(); d.__last_sync_on = new Date();
if(d.doctype==="DocType") {
frappe.meta.sync(d);
}


if(cur_frm && cur_frm.doctype==d.doctype && cur_frm.docname==d.name) { if(cur_frm && cur_frm.doctype==d.doctype && cur_frm.docname==d.name) {
cur_frm.doc = d; cur_frm.doc = d;
} }


if(d.doctype=='DocField') frappe.meta.add_field(d);
if(d.doctype=='DocType') frappe.meta.sync_messages(d);

if(d.localname) { if(d.localname) {
frappe.model.new_names[d.localname] = d.name; frappe.model.new_names[d.localname] = d.name;
$(document).trigger('rename', [d.doctype, d.localname, d.name]); $(document).trigger('rename', [d.doctype, d.localname, d.name]);
@@ -66,10 +53,10 @@ $.extend(frappe.model, {


} }
// set docinfo
// set docinfo (comments, assign, attachments)
if(r.docinfo) { if(r.docinfo) {
if(doclist) {
var doc = doclist[0];
if(r.docs) {
var doc = r.docs[0];
} else { } else {
var doc = cur_frm.doc; var doc = cur_frm.doc;
} }
@@ -78,68 +65,7 @@ $.extend(frappe.model, {
frappe.model.docinfo[doc.doctype][doc.name] = r.docinfo; frappe.model.docinfo[doc.doctype][doc.name] = r.docinfo;
} }
return doclist;
return r.docs;
}, },
expand: function(data) {
function zip(k,v) {
var obj = {};
for(var i=0;i<k.length;i++) {
obj[k[i]] = v[i];
}
return obj;
}

var l = [];
for(var i=0;i<data._vl.length;i++) {
l.push(zip(data._kl[data._vl[i][0]], data._vl[i]));
}
return l;
},
compress: function(doclist) {
var all_keys = {}; var values = [];
function get_key_list(doctype) {
// valid standard keys
var key_list = ['doctype', 'name', 'docstatus', 'owner', 'parent',
'parentfield', 'parenttype', 'idx', 'creation', 'modified',
'modified_by', '__islocal', '__newname', '__modified',
'_user_tags', '__temp', '_comments'];

for(key in frappe.meta.docfield_map[doctype]) { // all other values
if(!in_list(key_list, key)
&& !in_list(frappe.model.no_value_type, frappe.meta.docfield_map[doctype][key].fieldtype)
&& !frappe.meta.docfield_map[doctype][key].no_column) {
key_list[key_list.length] = key
}
}
return key_list;
}
for(var i=0; i<doclist.length;i++) {
var doc = doclist[i];
// make keys
if(!all_keys[doc.doctype]) {
all_keys[doc.doctype] = get_key_list(doc.doctype);
// doctype must be first
}
var row = []
var key_list = all_keys[doc.doctype];
// make data rows
for(var j=0;j<key_list.length;j++) {
row.push(doc[key_list[j]]);
}
values.push(row);
}

return JSON.stringify({'_vl':values, '_kl':all_keys});
}
}); });

// legacy
compress_doclist = frappe.model.compress;

+ 8
- 26
frappe/public/js/frappe/model/workflow.js View File

@@ -23,31 +23,19 @@ frappe.workflow = {
}, },
get_default_state: function(doctype) { get_default_state: function(doctype) {
frappe.workflow.setup(doctype); frappe.workflow.setup(doctype);
return frappe.model.get("Workflow Document State", {
parent: frappe.workflow.workflows[doctype].name,
idx: 1
})[0].state;
return frappe.workflow.workflows[doctype].workflow_document_states[0].state;
}, },
get_transitions: function(doctype, state) { get_transitions: function(doctype, state) {
frappe.workflow.setup(doctype); frappe.workflow.setup(doctype);
return frappe.model.get("Workflow Transition", {
parent: frappe.workflow.workflows[doctype].name,
state: state
});
return frappe.model.get_children(frappe.workflow.workflows[doctype], "workflow_transitions", {state:state});
}, },
get_document_state: function(doctype, state) { get_document_state: function(doctype, state) {
frappe.workflow.setup(doctype); frappe.workflow.setup(doctype);
return frappe.model.get("Workflow Document State", {
parent: frappe.workflow.workflows[doctype].name,
state: state
})[0];
return frappe.model.get_children(frappe.workflow.workflows[doctype], "workflow_document_states", {state:state})[0];
}, },
get_next_state: function(doctype, state, action) { get_next_state: function(doctype, state, action) {
return frappe.model.get("Workflow Transition", {
parent: frappe.workflow.workflows[doctype].name,
state: state,
action: action
})[0].next_state;
return frappe.model.get_children(frappe.workflow.workflows[doctype], "workflow_transitions", {
state:state, action:action})[0].next_state;
}, },
is_read_only: function(doctype, name) { is_read_only: function(doctype, name) {
var state_fieldname = frappe.workflow.get_state_fieldname(doctype); var state_fieldname = frappe.workflow.get_state_fieldname(doctype);
@@ -60,13 +48,7 @@ frappe.workflow = {
var state = locals[doctype][name][state_fieldname] || var state = locals[doctype][name][state_fieldname] ||
frappe.workflow.get_default_state(doctype); frappe.workflow.get_default_state(doctype);


var workflow_doc_state = frappe.model.get("Workflow Document State",
{
parent: frappe.workflow.workflows[doctype].name,
state: state
});
var allow_edit = workflow_doc_state.length ?
workflow_doc_state[0].allow_edit : null;
var allow_edit = state ? frappe.workflow.get_document_state(doctype, state).allow_edit : null;


if(user_roles.indexOf(allow_edit)==-1) { if(user_roles.indexOf(allow_edit)==-1) {
return true; return true;
@@ -75,8 +57,8 @@ frappe.workflow = {
return false; return false;
}, },
get_update_fields: function(doctype) { get_update_fields: function(doctype) {
var update_fields = $.unique($.map(frappe.model.get("Workflow Document State",
{parent:frappe.workflow.workflows[doctype].name}), function(d) {
var update_fields = $.unique($.map(frappe.workflow.workflows[doctype].workflow_document_states || [],
function(d) {
return d.update_field; return d.update_field;
})); }));
return update_fields; return update_fields;


+ 1
- 2
frappe/public/js/frappe/request.js View File

@@ -16,8 +16,7 @@ frappe.call = function(opts) {
} else if(opts.doc) { } else if(opts.doc) {
$.extend(args, { $.extend(args, {
cmd: "runserverobj", cmd: "runserverobj",
docs: frappe.model.compress(frappe.model.get_doclist(opts.doc.doctype,
opts.doc.name)),
docs: frappe.model.get_doc(opts.doc.doctype, opts.doc.name),
method: opts.method, method: opts.method,
args: opts.args, args: opts.args,
}); });


+ 6
- 6
frappe/public/js/frappe/views/reportview.js View File

@@ -337,13 +337,13 @@ frappe.views.ReportView = frappe.ui.Listing.extend({
callback: function(r) { callback: function(r) {
if(!r.exc) { if(!r.exc) {
d.hide(); d.hide();
var doclist = r.message;
var doc = r.message;
$.each(me.dataView.getItems(), function(i, item) { $.each(me.dataView.getItems(), function(i, item) {
if (item.name === doclist[0].name) {
var new_item = $.extend({}, item, doclist[0]);
$.each(doclist, function(i, doc) {
if(item[doc.doctype + ":name"]===doc.name) {
$.each(doc, function(k, v) {
if (item.name === doc.name) {
var new_item = $.extend({}, item);
$.each(frappe.model.get_all_docs(doc), function(i, d) {
if(item[d.doctype + ":name"]===d.name) {
$.each(d, function(k, v) {
if(frappe.model.std_fields_list.indexOf(k)===-1) { if(frappe.model.std_fields_list.indexOf(k)===-1) {
new_item[k] = v; new_item[k] = v;
} }


+ 2
- 9
frappe/public/js/legacy/clientscriptAPI.js View File

@@ -4,10 +4,7 @@
get_server_fields = function(method, arg, table_field, doc, dt, dn, allow_edit, call_back) { get_server_fields = function(method, arg, table_field, doc, dt, dn, allow_edit, call_back) {
frappe.dom.freeze(); frappe.dom.freeze();
return $c('runserverobj', return $c('runserverobj',
args={'method':method,
'docs':frappe.model.compress(make_doclist(doc.doctype, doc.name)),
'arg':arg
},
args={'method': method, 'docs': doc, 'arg': arg },
function(r, rt) { function(r, rt) {
frappe.dom.unfreeze(); frappe.dom.unfreeze();
if (r.message) { if (r.message) {
@@ -125,10 +122,6 @@ _f.Frm.prototype.get_doc = function() {
return locals[this.doctype][this.docname]; return locals[this.doctype][this.docname];
} }


_f.Frm.prototype.get_doclist = function(filters) {
return frappe.model.get_doclist(this.doctype, this.docname, filters);
}

_f.Frm.prototype.field_map = function(fnames, fn) { _f.Frm.prototype.field_map = function(fnames, fn) {
if(typeof fnames==='string') { if(typeof fnames==='string') {
if(fnames == '*') { if(fnames == '*') {
@@ -170,7 +163,7 @@ _f.Frm.prototype.toggle_display = function(fnames, show) {
} }


_f.Frm.prototype.call_server = function(method, args, callback) { _f.Frm.prototype.call_server = function(method, args, callback) {
return $c_obj(cur_frm.get_doclist(), method, args, callback);
return $c_obj(cur_frm.doc, method, args, callback);
} }


_f.Frm.prototype.get_files = function() { _f.Frm.prototype.get_files = function() {


+ 20
- 57
frappe/public/js/legacy/form.js View File

@@ -548,13 +548,11 @@ _f.Frm.prototype.setnewdoc = function() {
_f.Frm.prototype.runscript = function(scriptname, callingfield, onrefresh) { _f.Frm.prototype.runscript = function(scriptname, callingfield, onrefresh) {
var me = this; var me = this;
if(this.docname) { if(this.docname) {
// make doc list
var doclist = frappe.model.compress(make_doclist(this.doctype, this.docname));
// send to run // send to run
if(callingfield) if(callingfield)
$(callingfield.input).set_working(); $(callingfield.input).set_working();


return $c('runserverobj', {'docs':doclist, 'method':scriptname },
return $c('runserverobj', {'docs':this.doc, 'method':scriptname },
function(r, rtxt) { function(r, rtxt) {
// run refresh // run refresh
if(onrefresh) if(onrefresh)
@@ -577,45 +575,10 @@ _f.Frm.prototype.copy_doc = function(onload, from_amend) {
return; return;
} }
var dn = this.docname;
// copy parent
var newdoc = frappe.model.copy_doc(this.doctype, dn, from_amend);
newdoc.idx = null;
// copy chidren
var dl = make_doclist(this.doctype, dn);

// table fields dict - for no_copy check
var tf_dict = {};

for(var d in dl) {
d1 = dl[d];
// get tabel field
if(d1.parentfield && !tf_dict[d1.parentfield]) {
tf_dict[d1.parentfield] = frappe.meta.get_docfield(d1.parenttype, d1.parentfield);
}
if(d1.parent==dn && cint(tf_dict[d1.parentfield].no_copy)!=1) {
var ch = frappe.model.copy_doc(d1.doctype, d1.name, from_amend);
ch.parent = newdoc.name;
ch.docstatus = 0;
ch.owner = user;
ch.creation = '';
ch.modified_by = user;
ch.modified = '';
}
}

newdoc.__islocal = 1;
newdoc.docstatus = 0;
newdoc.owner = user;
newdoc.creation = '';
newdoc.modified_by = user;
newdoc.modified = '';
var newdoc = frappe.model.copy_doc(this.doc, from_amend);


newdoc.idx = null;
if(onload)onload(newdoc); if(onload)onload(newdoc);

loaddoc(newdoc.doctype, newdoc.name); loaddoc(newdoc.doctype, newdoc.name);
} }


@@ -651,19 +614,15 @@ _f.Frm.prototype._save = function(save_action, callback, btn, on_error) {
scroll(0, 0); scroll(0, 0);
// validate // validate
if(save_action!="Cancel") {
validated = true;
this.script_manager.trigger("validate");
if(!validated) {
if(on_error)
on_error();
return;
}
validated = true;
this.script_manager.trigger("validate");
if(!validated) {
if(on_error)
on_error();
return;
} }

var doclist = new frappe.model.DocList(this.doctype, this.docname);

doclist.save(save_action || "Save", function(r) {
var after_save = function(r) {
if(!r.exc) { if(!r.exc) {
me.refresh(); me.refresh();
} else { } else {
@@ -680,7 +639,9 @@ _f.Frm.prototype._save = function(save_action, callback, btn, on_error) {
} }
frappe._from_link = null; frappe._from_link = null;
} }
}, btn);
}
frappe.ui.form.save(this, save_action || "Save", after_save, btn);
} }




@@ -713,14 +674,16 @@ _f.Frm.prototype.savecancel = function(btn, on_error) {
on_error(); on_error();
return; return;
} }
var doclist = new frappe.model.DocList(me.doctype, me.docname);
doclist.cancel(function(r) {

var after_cancel = function(r) {
if(!r.exc) { if(!r.exc) {
me.refresh(); me.refresh();
me.script_manager.trigger("after_cancel"); me.script_manager.trigger("after_cancel");
} else {
on_error();
} }
}, btn, on_error);
}
frappe.ui.form.save(this, "cancel", after_cancel, btn);
}); });
} }




+ 10
- 9
frappe/public/js/legacy/handler.js View File

@@ -13,7 +13,7 @@ function $c(command, args, callback, error, no_spinner, freeze_msg, btn) {
} }


// For calling an object // For calling an object
function $c_obj(doclist, method, arg, callback, no_spinner, freeze_msg, btn) {
function $c_obj(doc, method, arg, callback, no_spinner, freeze_msg, btn) {
if(arg && typeof arg!='string') arg = JSON.stringify(arg); if(arg && typeof arg!='string') arg = JSON.stringify(arg);
args = { args = {
@@ -22,10 +22,11 @@ function $c_obj(doclist, method, arg, callback, no_spinner, freeze_msg, btn) {
method: method method: method
}; };
if(typeof doclist=='string')
args.doctype = doclist;
else
args.docs = frappe.model.compress(doclist)
if(typeof doc=='string') {
args.doctype = doc;
} else {
args.docs = doc
}
return frappe.request.call({ return frappe.request.call({
args: args, args: args,
@@ -53,7 +54,7 @@ function $c_page(module, page, method, arg, callback, no_spinner, freeze_msg, bt
} }


// For calling an for output as csv // For calling an for output as csv
function $c_obj_csv(doclist, method, arg) {
function $c_obj_csv(doc, method, arg) {
// single // single
var args = {} var args = {}
@@ -62,10 +63,10 @@ function $c_obj_csv(doclist, method, arg) {
args.method = method; args.method = method;
args.arg = arg; args.arg = arg;
if(doclist.substr)
args.doctype = doclist;
if(doc.substr)
args.doctype = doc;
else else
args.docs = frappe.model.compress(doclist);
args.docs = doc;


// open // open
open_url_post(frappe.request.url, args); open_url_post(frappe.request.url, args);


+ 1
- 0
frappe/tests/test_document.py View File

@@ -120,5 +120,6 @@ class TestDocument(unittest.TestCase):
d.starts_on = "2014-01-01" d.starts_on = "2014-01-01"
d.ends_on = "2013-01-01" d.ends_on = "2013-01-01"
self.assertRaises(frappe.ValidationError, d.validate) self.assertRaises(frappe.ValidationError, d.validate)
self.assertRaises(frappe.ValidationError, d.run_method, "validate")
self.assertRaises(frappe.ValidationError, d.save) self.assertRaises(frappe.ValidationError, d.save)

+ 16
- 0
frappe/tests/test_form_load.py View File

@@ -0,0 +1,16 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

import frappe, unittest
from frappe.widgets.form.meta import get_meta
from frappe.widgets.form.load import getdoctype, getdoc

class TestFormLoad(unittest.TestCase):
def test_load(self):
getdoctype("DocType")
self.assertEquals(frappe.response.docs[0].name, "DocType")
self.assertTrue(frappe.response.docs[0].get("__js"))

frappe.response.docs = []
d = getdoctype("Event")
self.assertTrue(frappe.response.docs[0].get("__calendar_js"))

+ 10
- 8
frappe/utils/response.py View File

@@ -9,7 +9,8 @@ import mimetypes
import os import os
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.doc import Document
import frappe.model.doc
import frappe.model.document
import frappe.utils import frappe.utils
import frappe.sessions import frappe.sessions
import frappe.model.utils import frappe.model.utils
@@ -28,6 +29,9 @@ def report_error(status_code):
return response return response


def build_response(response_type=None): def build_response(response_type=None):
if "docs" in frappe.local.response and not frappe.local.response.docs:
del frappe.local.response["docs"]
response_type_map = { response_type_map = {
'csv': as_csv, 'csv': as_csv,
'download': as_raw, 'download': as_raw,
@@ -54,7 +58,6 @@ def as_raw():


def as_json(): def as_json():
make_logs() make_logs()
cleanup_docs()
response = Response() response = Response()
response.headers["Content-Type"] = "application/json; charset: utf-8" response.headers["Content-Type"] = "application/json; charset: utf-8"
response = gzip(json.dumps(frappe.local.response, default=json_handler, separators=(',',':')), response = gzip(json.dumps(frappe.local.response, default=json_handler, separators=(',',':')),
@@ -68,14 +71,11 @@ def make_logs():
frappe.response['exc'] = json.dumps([frappe.utils.cstr(d) for d in frappe.local.error_log]) frappe.response['exc'] = json.dumps([frappe.utils.cstr(d) for d in frappe.local.error_log])


if frappe.local.message_log: if frappe.local.message_log:
frappe.response['_server_messages'] = json.dumps([frappe.utils.cstr(d) for d in frappe.local.message_log])
frappe.response['_server_messages'] = json.dumps([frappe.utils.cstr(d) for
d in frappe.local.message_log])
if frappe.debug_log and frappe.conf.get("logging") or False: if frappe.debug_log and frappe.conf.get("logging") or False:
frappe.response['_debug_messages'] = json.dumps(frappe.local.debug_log) frappe.response['_debug_messages'] = json.dumps(frappe.local.debug_log)

def cleanup_docs():
if frappe.response.get('docs') and type(frappe.response['docs'])!=dict:
frappe.response['docs'] = frappe.model.utils.compress(frappe.response['docs'])
def gzip(data, response): def gzip(data, response):
data = data.encode('utf-8') data = data.encode('utf-8')
@@ -107,8 +107,10 @@ def json_handler(obj):
return unicode(obj) return unicode(obj)
elif isinstance(obj, LocalProxy): elif isinstance(obj, LocalProxy):
return unicode(obj) return unicode(obj)
elif isinstance(obj, Document):
elif isinstance(obj, frappe.model.doc.Document):
return obj.fields return obj.fields
elif isinstance(obj, frappe.model.document.Document):
return obj.as_dict()
else: else:
raise TypeError, """Object of type %s with value of %s is not JSON serializable""" % \ raise TypeError, """Object of type %s with value of %s is not JSON serializable""" % \
(type(obj), repr(obj)) (type(obj), repr(obj))


+ 1
- 1
frappe/website/doctype/blog_post/blog_post.js View File

@@ -5,7 +5,7 @@ cur_frm.cscript.refresh = function(doc) {
if(!doc.__islocal && doc.published) { if(!doc.__islocal && doc.published) {
if(!doc.email_sent) { if(!doc.email_sent) {
cur_frm.add_custom_button('Email Subscribers', function() { cur_frm.add_custom_button('Email Subscribers', function() {
$c_obj(make_doclist(doc.doctype, doc.name), 'send_emails', '', function(r) {
$c_obj(doc, 'send_emails', '', function(r) {
cur_frm.refresh(); cur_frm.refresh();
}); });
}); });


+ 19
- 19
frappe/widgets/form/load.py View File

@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe, json import frappe, json
import frappe.model.doc import frappe.model.doc
import frappe.utils import frappe.utils
import frappe.widgets.form.meta


@frappe.whitelist() @frappe.whitelist()
def getdoc(doctype, name, user=None): def getdoc(doctype, name, user=None):
@@ -13,8 +14,6 @@ def getdoc(doctype, name, user=None):
Requries "doctype", "name" as form variables. Requries "doctype", "name" as form variables.
Will also call the "onload" method on the document. Will also call the "onload" method on the document.
""" """

import frappe
if not (doctype and name): if not (doctype and name):
raise Exception, 'doctype and name required!' raise Exception, 'doctype and name required!'
@@ -26,14 +25,12 @@ def getdoc(doctype, name, user=None):
return [] return []


try: try:
bean = frappe.bean(doctype, name)
bean.run_method("onload")
doc = frappe.get_doc(doctype, name)
doc.run_method("onload")
if not bean.has_read_perm():
if not doc.has_permission("read"):
raise frappe.PermissionError raise frappe.PermissionError


doclist = bean.doclist

# add file list # add file list
get_docinfo(doctype, name) get_docinfo(doctype, name)
@@ -42,35 +39,38 @@ def getdoc(doctype, name, user=None):
frappe.msgprint('Did not load.') frappe.msgprint('Did not load.')
raise raise


if bean and not name.startswith('_'):
if doc and not name.startswith('_'):
frappe.user.update_recent(doctype, name) frappe.user.update_recent(doctype, name)
frappe.response['docs'] = doclist
frappe.response.docs.append(doc)


@frappe.whitelist() @frappe.whitelist()
def getdoctype(doctype, with_parent=False, cached_timestamp=None): def getdoctype(doctype, with_parent=False, cached_timestamp=None):
"""load doctype""" """load doctype"""
import frappe.model.doctype
import frappe.model.meta
doclist = []
docs = []
# with parent (called from report builder) # with parent (called from report builder)
if with_parent: if with_parent:
parent_dt = frappe.model.meta.get_parent_dt(doctype) parent_dt = frappe.model.meta.get_parent_dt(doctype)
if parent_dt: if parent_dt:
doclist = frappe.model.doctype.get(parent_dt, processed=True)
docs = get_meta_bundle(parent_dt)
frappe.response['parent_dt'] = parent_dt frappe.response['parent_dt'] = parent_dt
if not doclist:
doclist = frappe.model.doctype.get(doctype, processed=True)
if not docs:
docs = get_meta_bundle(doctype)
frappe.response['restrictions'] = get_restrictions(doclist)
frappe.response['restrictions'] = get_restrictions(docs[0])
if cached_timestamp and doclist[0].modified==cached_timestamp:
if cached_timestamp and docs[0].modified==cached_timestamp:
return "use_cache" return "use_cache"
frappe.response['docs'] = doclist
frappe.response.docs.extend(docs)

def get_meta_bundle(doctype):
bundle = [frappe.widgets.form.meta.get_meta(doctype)]
for df in bundle[0].get_table_fields():
bundle.append(frappe.widgets.form.meta.get_meta(df.options))
return bundle


def get_docinfo(doctype, name): def get_docinfo(doctype, name):
frappe.response["docinfo"] = { frappe.response["docinfo"] = {


+ 157
- 0
frappe/widgets/form/meta.py View File

@@ -0,0 +1,157 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

# metadata

from __future__ import unicode_literals
import frappe, os
from frappe.utils import cstr, cint
from frappe.model.meta import Meta
from frappe.modules import scrub, get_module_path
from frappe.model.workflow import get_workflow_name

######

def get_meta(doctype, cached=True):
if cached:
meta = frappe.cache().get_value("form_meta:" + doctype, lambda: FormMeta(doctype))
else:
meta = FormMeta(doctype)

if frappe.local.lang != 'en':
meta.set("__messages", frappe.get_lang_dict("doctype", doctype))
return meta
class FormMeta(Meta):
def __init__(self, doctype):
super(FormMeta, self).__init__(doctype)
self.load_assets()
def load_assets(self):
self.expand_selects()
self.add_search_fields()

if not self.istable:
self.add_linked_with()
self.add_code()
self.load_print_formats()
self.load_workflows()
def as_dict(self):
d = super(FormMeta, self).as_dict()
for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js", "__linked_with", "__messages"):
d[k] = self.get(k)
for i, df in enumerate(d.get("fields")):
for k in ("link_doctype", "search_fields"):
df[k] = self.get("fields")[i].get(k)
return d
def add_code(self):
path = os.path.join(get_module_path(self.module), 'doctype', scrub(self.name))
def _get_path(fname):
return os.path.join(path, scrub(fname))
self._add_code(_get_path(self.name + '.js'), '__js')
self._add_code(_get_path(self.name + '.css'), "__css")
self._add_code(_get_path(self.name + '_list.js'), '__list_js')
self._add_code(_get_path(self.name + '_calendar.js'), '__calendar_js')
self._add_code(_get_path(self.name + '_map.js'), '__map_js')
self.add_custom_script()
self.add_code_via_hook("doctype_js", "__js")
def _add_code(self, path, fieldname):
js = frappe.read_file(path)
if js:
self.set(fieldname, (self.get(fieldname) or "") + "\n\n" + render_jinja(js))
def add_code_via_hook(self, hook, fieldname):
hook = "{}:{}".format(hook, self.name)
for app_name in frappe.get_installed_apps():
for file in frappe.get_hooks(hook, app_name=app_name):
path = frappe.get_app_path(app_name, *file.strip("/").split("/"))
self._add_code(path, fieldname)
def add_custom_script(self):
"""embed all require files"""
# custom script
custom = frappe.db.get_value("Custom Script", {"dt": self.name,
"script_type": "Client"}, "script") or ""
self.set("__js", (self.get('__js') or '') + "\n\n" + custom)
def render_jinja(content):
if "{% include" in content:
content = frappe.get_jenv().from_string(content).render()
return content

def expand_selects(self):
for df in self.get("fields", {"fieldtype": "Select"}):
if df.options and df.options.startswith("link:"):
df.link_doctype = df.options.split("\n")[0][5:]
df.options = '\n'.join([''] + [o.name for o in frappe.db.sql("""select
name from `tab%s` where docstatus<2 order by name asc""" % df.link_doctype, as_dict=1)])
def add_search_fields(self):
"""add search fields found in the doctypes indicated by link fields' options"""
for df in self.get("fields", {"fieldtype": "Link", "options":["!=", "[Select]"]}):
if df.options:
search_fields = frappe.get_meta(df.options).search_fields
if search_fields:
df.search_fields = map(lambda sf: sf.strip(), search_fields.split(","))

def add_linked_with(self):
"""add list of doctypes this doctype is 'linked' with"""
links = frappe.db.sql("""select parent, fieldname from tabDocField
where (fieldtype="Link" and options=%s)
or (fieldtype="Select" and options=%s)""", (self.name, "link:"+ self.name))
links += frappe.db.sql("""select dt as parent, fieldname from `tabCustom Field`
where (fieldtype="Link" and options=%s)
or (fieldtype="Select" and options=%s)""", (self.name, "link:"+ self.name))

links = dict(links)

if not links:
return {}

ret = {}

for dt in links:
ret[dt] = { "fieldname": links[dt] }

for grand_parent, options in frappe.db.sql("""select parent, options from tabDocField
where fieldtype="Table"
and options in (select name from tabDocType
where istable=1 and name in (%s))""" % ", ".join(["%s"] * len(links)) ,tuple(links)):

ret[grand_parent] = {"child_doctype": options, "fieldname": links[options] }
if options in ret:
del ret[options]
self.set("__linked_with", ret)
def load_print_formats(self):
frappe.response.docs.extend(frappe.db.sql("""select * FROM `tabPrint Format`
WHERE doc_type=%s AND docstatus<2""", (self.name,), as_dict=1, update={"doctype":"Print Format"}))
def load_workflows(self):
# get active workflow
workflow_name = get_workflow_name(self.name)

if workflow_name and frappe.db.exists("Workflow", workflow_name):
workflow = frappe.get_doc("Workflow", workflow_name)
frappe.response.docs.append(workflow)
for d in workflow.get("workflow_document_states"):
frappe.response.docs.append(frappe.get_doc("Workflow State", d.state))

def render_jinja(content):
if "{% include" in content:
content = frappe.get_jenv().from_string(content).render()
return content


+ 0
- 2
frappe/widgets/form/run_method.py View File

@@ -20,8 +20,6 @@ def runserverobj():
dt = frappe.form_dict.get('doctype') dt = frappe.form_dict.get('doctype')
dn = frappe.form_dict.get('docname') dn = frappe.form_dict.get('docname')
frappe.response["docs"] = []
if dt: # not called from a doctype (from a page) if dt: # not called from a doctype (from a page)
if not dn: dn = dt # single if not dn: dn = dt # single
so = frappe.model.code.get_obj(dt, dn) so = frappe.model.code.get_obj(dt, dn)


+ 0
- 2
frappe/widgets/form/utils.py View File

@@ -112,8 +112,6 @@ def get_linked_docs(doctype, name, metadata_loaded=None):
results[dt] = ret results[dt] = ret
if not dt in metadata_loaded: if not dt in metadata_loaded:
if not "docs" in frappe.local.response:
frappe.local.response.docs = []
frappe.local.response.docs += linkmeta frappe.local.response.docs += linkmeta
return results return results

+ 14
- 19
frappe/widgets/page.py View File

@@ -11,14 +11,14 @@ def get(name):
""" """
Return the :term:`doclist` of the `Page` specified by `name` Return the :term:`doclist` of the `Page` specified by `name`
""" """
page = frappe.bean("Page", name)
if has_permission(page.doclist):
page.run_method("get_from_files")
return page.doclist
page = frappe.get_doc('Page', name)
if has_permission(page):
page.load_assets()
return page
else: else:
frappe.response['403'] = 1 frappe.response['403'] = 1
raise frappe.PermissionError, 'No read permission for Page %s' % \ raise frappe.PermissionError, 'No read permission for Page %s' % \
(page.doclist[0].title or name,)
(page.title or name)


@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def getpage(): def getpage():
@@ -26,24 +26,19 @@ def getpage():
Load the page from `frappe.form` and send it via `frappe.response` Load the page from `frappe.form` and send it via `frappe.response`
""" """
page = frappe.form_dict.get('name') page = frappe.form_dict.get('name')
doclist = get(page)
doc = get(page)


if has_permission(doclist):
# load translations
if frappe.lang != "en":
frappe.response["__messages"] = frappe.get_lang_dict("page", page)
# load translations
if frappe.lang != "en":
frappe.response["__messages"] = frappe.get_lang_dict("page", page)


frappe.response['docs'] = doclist
else:
frappe.response['403'] = 1
raise frappe.PermissionError, 'No read permission for Page %s' % \
(doclist[0].title or page, )
def has_permission(page_doclist):
frappe.response.docs.append(doc)

def has_permission(page):
if frappe.user.name == "Administrator" or "System Manager" in frappe.user.get_roles(): if frappe.user.name == "Administrator" or "System Manager" in frappe.user.get_roles():
return True return True
page_roles = [d.role for d in page_doclist if d.fields.get("doctype")=="Page Role"]
page_roles = [d.role for d in page.get("roles")]
if page_roles: if page_roles:
if frappe.session.user == "Guest" and "Guest" not in page_roles: if frappe.session.user == "Guest" and "Guest" not in page_roles:
return False return False
@@ -51,7 +46,7 @@ def has_permission(page_doclist):
# check if roles match # check if roles match
return False return False
if not frappe.has_permission("Page", ptype="read", refdoc=page_doclist[0].name):
if not frappe.has_permission("Page", ptype="read", doc=page):
# check if there are any restrictions # check if there are any restrictions
return False return False
else: else:


Loading…
Cancel
Save