Rushabh Mehta преди 11 години
родител
ревизия
335c333bcc
променени са 9 файла, в които са добавени 329 реда и са изтрити 59 реда
  1. +4
    -3
      frappe/__init__.py
  2. +20
    -4
      frappe/cli.py
  3. +1
    -0
      frappe/model/__init__.py
  4. +3
    -1
      frappe/model/create_new.py
  5. +0
    -10
      frappe/model/doctype.py
  6. +180
    -39
      frappe/model/document.py
  7. +111
    -0
      frappe/model/meta.py
  8. +1
    -1
      frappe/model/naming.py
  9. +9
    -1
      frappe/tests/test_document.py

+ 4
- 3
frappe/__init__.py Целия файл

@@ -111,6 +111,7 @@ def init(site, sites_path=None):
local.test_objects = {}
local.jenv = None
local.jloader =None
local.meta = {}

setup_module_map()

@@ -349,9 +350,9 @@ def get_doctype(doctype, processed=False):
import frappe.model.doctype
return frappe.model.doctype.get(doctype, processed)
def get_meta(doctype, processed=False):
import frappe.model.doctype
return frappe.model.doctype.get_meta(doctype, processed)
def get_meta(doctype, cached=True):
import frappe.model.meta
return frappe.model.meta.get_meta(doctype, cached=cached)

def delete_doc(doctype=None, name=None, doclist = None, force=0, ignore_doctypes=None,
for_reload=False, ignore_permissions=False):


+ 20
- 4
frappe/cli.py Целия файл

@@ -660,11 +660,27 @@ def smtp_debug_server():
os.execv(python, [python, '-m', "smtpd", "-n", "-c", "DebuggingServer", "localhost:25"])

@cmd
def run_tests(app=None, module=None, doctype=None, verbose=False):
def run_tests(app=None, module=None, doctype=None, verbose=False, profile=False):
import frappe.test_runner
ret = frappe.test_runner.main(app and app[0], module and module[0], doctype and doctype[0], verbose)
if len(ret.failures) > 0 or len(ret.errors) > 0:
exit(1)

def _run():
ret = frappe.test_runner.main(app and app[0], module and module[0], doctype and doctype[0], verbose)
if len(ret.failures) > 0 or len(ret.errors) > 0:
exit(1)

if profile:
import cProfile, pstats, StringIO
pr = cProfile.Profile()
pr.enable()
_run()
pr.disable()
s = StringIO.StringIO()
sortby = 'cumulative'
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
print s.getvalue()
else:
_run()

@cmd
def serve(port=8000, profile=False, sites_path='.', site=None):


+ 1
- 0
frappe/model/__init__.py Целия файл

@@ -7,6 +7,7 @@ import frappe

no_value_fields = ['Section Break', 'Column Break', 'HTML', 'Table', 'Button', 'Image']
default_fields = ['doctype','name','owner','creation','modified','modified_by','parent','parentfield','parenttype','idx','docstatus']
integer_docfield_properties = ["reqd", "search_index", "in_list_view", "permlevel", "hidden", "read_only", "ignore_restrictions", "allow_on_submit", "report_hide", "in_filter", "no_copy", "print_hide"]

def insert(doclist):
if not isinstance(doclist, list):


+ 3
- 1
frappe/model/create_new.py Целия файл

@@ -29,8 +29,10 @@ def get_new_doc(doctype, parent_doc = None, parentfield = None):
if parentfield:
doc.parentfield = parentfield
defaults = frappe.defaults.get_defaults()
for d in meta.get({"doctype":"DocField", "parent": doctype}):
default = frappe.defaults.get_user_default(d.fieldname)
default = defaults.get(d.fieldname)
if (d.fieldtype=="Link") and d.ignore_restrictions != 1 and (d.options in restrictions)\
and len(restrictions[d.options])==1:


+ 0
- 10
frappe/model/doctype.py Целия файл

@@ -27,16 +27,6 @@ docfield_types = frappe.local('doctype_docfield_types')
# doctype_cache = {}
# docfield_types = None

def get_meta(doctype, processed=False, cached=True):
meta = []
for d in get(doctype=doctype, processed=processed, cached=cached):
if d.doctype=="DocType" and d.name==doctype:
meta.append(d)
elif d.parent and d.parent==doctype:
meta.append(d)
return DocTypeDocList(meta)

def get(doctype, processed=False, cached=True):
"""return doclist"""
if cached:


+ 180
- 39
frappe/model/document.py Целия файл

@@ -9,65 +9,81 @@ from frappe.model import default_fields
from frappe.model.db_schema import type_map
from frappe.model.naming import set_new_name

# validation - links, mandatory
# save / update
# once_only validation
# permissions
# methods
# timestamps and docstatus
# defaults (insert)

class BaseDocument(object):
def __init__(self, d):
self.update(d)
def __init__(self, d, valid_columns=None):
self.update(d, valid_columns=valid_columns)

def __getattr__(self, key):
if self.__dict__.has_key(key):
return self.__dict__[key]

if key in self.get_table_columns():
if key!= "_valid_columns" and key in self.get_valid_columns():
return None

raise AttributeError(key)

def update(self, d):
def update(self, d, valid_columns=None):
if valid_columns:
self.__dict__["_valid_columns"] = valid_columns
if "doctype" in d:
self.set("doctype", d.get("doctype"))
for key, value in d.iteritems():
self.set(key, value)
def get(self, key=None, default=None):
def get(self, key=None, filters=None, limit=None, default=None):
if key:
return self.__dict__.get(key, default)
if filters:
return _filter(self.__dict__.get(key), filters, limit=limit)
else:
return self.__dict__.get(key, default)
else:
return self.__dict__
def set(self, key, value):
def set(self, key, value, valid_columns=None):
if isinstance(value, list):
tmp = []
for v in value:
tmp.append(self._init_child(v, key, valid_columns))
value = tmp

self.__dict__[key] = value
def append(self, key, value):
if isinstance(value, dict):
# appending
if not self.get(key):
self.__dict__[key] = []
self.get(key).append(self._init_child(value, key))
elif isinstance(value, list):
else:
raise ValueError
def extend(self, key, value):
if isinstance(value, list):
for v in value:
self.set(key, v)
self.append(v)
else:
self.__dict__[key] = value
raise ValueError
def _init_child(self, value, key):
def _init_child(self, value, key, valid_columns=None):
if not self.doctype:
return value
if not isinstance(value, BaseDocument):
if not value.get("doctype"):
value["doctype"] = self.meta.get({"fieldname":key})[0].options
value["doctype"] = self.get_table_field_doctype(key)
if not value.get("doctype"):
raise AttributeError, key
value = BaseDocument(value)
value = BaseDocument(value, valid_columns=valid_columns)
value.parent = self.name
value.parenttype = self.doctype
value.parentfield = key
if not value.idx:
value.idx = len(self.get(key))
value.idx = len(self.get(key) or []) + 1
return value

@@ -79,24 +95,31 @@ class BaseDocument(object):
def get_valid_dict(self):
d = {}
for fieldname in self.table_columns:
for fieldname in self.valid_columns:
d[fieldname] = self.get(fieldname)
return d
def get_for_save(self):
d = self.get_valid_dict()
@property
def table_columns(self):
return self.get_table_columns()
def valid_columns(self):
return self.get_valid_columns()

def get_table_columns(self):
if not hasattr(self, "_table_columns"):
def get_valid_columns(self):
if not hasattr(self, "_valid_columns"):
doctype = self.__dict__.get("doctype")
self._table_columns = default_fields[1:] + \
[df.fieldname for df in frappe.get_meta(doctype).get_docfields()
self._valid_columns = default_fields[1:] + \
[df.fieldname for df in frappe.get_meta(doctype).get("fields")
if df.fieldtype in type_map]
return self._table_columns
return self._valid_columns
def get_table_field_doctype(self, fieldname):
return self.meta.get("fields", {"fieldname":fieldname})[0].options
def insert_row(self):
def db_insert(self):
set_new_name(self)
d = self.get_valid_dict()
columns = d.keys()
@@ -106,9 +129,19 @@ class BaseDocument(object):
columns = ", ".join(["`"+c+"`" for c in columns]),
values = ", ".join(["%s"] * len(columns))
), d.values())
self.set("__islocal", False)

def db_update(self):
d = self.get_valid_dict()
columns = d.keys()
frappe.db.sql("""update `tab{doctype}`
set {values} where name=%s""".format(
doctype = self.doctype,
values = ", ".join(["`"+c+"`=%s" for c in columns])
), d.values() + [d.get("name")])
def fix_numeric_types(self):
for df in self.meta.get_docfields():
for df in self.meta.get("fields"):
if df.fieldtype in ("Int", "Check"):
self.set(df.fieldname, cint(self.get(df.fieldname)))
elif df.fieldtype in ("Float", "Currency"):
@@ -137,7 +170,7 @@ class BaseDocument(object):
missing = []
for df in self.meta.get({"doctype": "DocField", "reqd": 1}):
for df in self.meta.get("fields", {"reqd": 1}):
if self.get(df.fieldname) in (None, []):
missing.append((df.fieldname, get_msg(df)))
@@ -193,17 +226,25 @@ class Document(BaseDocument):
raise frappe.DataError("Document({0}, {1})".format(arg1, arg2))

def load_from_db(self):
if self.meta[0].issingle:
if not getattr(self, "_metaclass", False) and self.meta.issingle:
self.update(frappe.db.get_singles_dict(self.doctype))
self.fix_numeric_types()
else:
d = frappe.db.get_value(self.doctype, self.name, "*", as_dict=1)
for df in self.meta.get({"doctype":"DocField", "fieldtype":"Table"}):
d[df.fieldname] = frappe.db.get_values(df.options,
self.update(d, valid_columns = d.keys())

for df in self.get_table_fields():
children = frappe.db.get_values(df.options,
{"parent": self.name, "parenttype": self.doctype, "parentfield": df.fieldname},
"*", as_dict=True)
self.update(d)
if children:
self.set(df.fieldname, children, children[0].keys())
else:
self.set(df.fieldname, [])
def get_table_fields(self):
return self.meta.get('fields', {"fieldtype":"Table"})
def insert(self):
# check links
@@ -215,16 +256,34 @@ class Document(BaseDocument):
# run validate, on update etc.
# parent
if self.meta[0].issingle:
if self.meta.issingle:
self.update_single(self.get_valid_dict())
else:
self.insert_row()
self.db_insert()
# children
for d in self.get_all_children():
d.parent = self.name
d.insert_row()
d.db_insert()

def save(self):
if self.get("__islocal") or not self.get("name"):
self.insert()
return

self._validate()

# parent
if self.meta.issingle:
self.update_single(self.get_valid_dict())
else:
self.db_update()

# children
for d in self.get_all_children():
d.parent = self.name
d.db_update()
def update_single(self, d):
frappe.db.sql("""delete from tabSingles where doctype=%s""", d.get("doctype"))
for field, value in d.iteritems():
@@ -241,7 +300,7 @@ class Document(BaseDocument):
self.set_missing_values(new_doc)

# children
for df in self.meta.get({"fieldtype":"Table"}):
for df in self.meta.get("fields", {"fieldtype":"Table"}):
new_doc = frappe.new_doc(df.options).fields
value = self.get(df.fieldname)
if isinstance(value, list):
@@ -249,12 +308,60 @@ class Document(BaseDocument):
d.set_missing_values(new_doc)

def _validate(self):
self.trigger("validate")
#self.check_if_latest()
self.validate_mandatory()
self.validate_links()
# check restrictions
def check_if_latest(self, method="save"):
conflict = False
if not self.get('__islocal'):
if self.meta.issingle:
modified = frappe.db.get_value(self.doctype, self.name, "modified")
if cstr(modified) and cstr(modified) != cstr(self.modified):
conflict = True
else:
tmp = frappe.db.sql("""select modified, docstatus from `tab%s`
where name=%s for update"""
% (self.doctype, '%s'), self.name, as_dict=True)

if not tmp:
frappe.msgprint("""This record does not exist. Please refresh.""", raise_exception=1)

modified = cstr(tmp[0].modified)
if modified and modified != cstr(self.modified):
conflict = True
self.check_docstatus_transition(tmp[0].docstatus, method)
if conflict:
frappe.msgprint(_("Error: Document has been modified after you have opened it") \
+ (" (%s, %s). " % (modified, self.modified)) \
+ _("Please refresh to get the latest document."), raise_exception=TimestampMismatchError)

def check_docstatus_transition(self, db_docstatus):
valid = {
"save": [0,0],
"submit": [0,1],
"cancel": [1,2],
"update_after_submit": [1,1]
}
labels = {
0: _("Draft"),
1: _("Submitted"),
2: _("Cancelled")
}
if not hasattr(self, "to_docstatus"):
self.to_docstatus = 0
if [db_docstatus, self.to_docstatus] != valid[method]:
frappe.msgprint(_("Cannot change from") + ": " + labels[db_docstatus] + " > " + \
labels[self.to_docstatus], raise_exception=DocstatusTransitionError)

def validate_mandatory(self):
if self.get("ignore_mandatory"):
return
@@ -288,11 +395,45 @@ class Document(BaseDocument):
def get_all_children(self):
ret = []
for df in self.meta.get({"fieldtype": "Table"}):
for df in self.meta.get("fields", {"fieldtype": "Table"}):
value = self.get(df.fieldname)
if isinstance(value, list):
ret.extend(value)
return ret
def trigger(self, func, *args, **kwargs):
return
return
def _filter(data, filters, limit=None):
"""pass filters as:
{"key": "val", "key": ["!=", "val"],
"key": ["in", "val"], "key": ["not in", "val"], "key": "^val",
"key" : True (exists), "key": False (does not exist) }"""

out = []
for d in data:
add = True
for f in filters:
fval = filters[f]
if fval is True:
fval = ["not None", fval]
elif fval is False:
fval = ["None", fval]
elif not isinstance(fval, (tuple, list)):
if isinstance(fval, basestring) and fval.startswith("^"):
fval = ["^", fval[1:]]
else:
fval = ["=", fval]
if not frappe.compare(d.get(f), fval[0], fval[1]):
add = False
break

if add:
out.append(d)
if limit and (len(out)-1)==limit:
break
return out

+ 111
- 0
frappe/model/meta.py Целия файл

@@ -6,7 +6,118 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import cstr, cint
from frappe.model import integer_docfield_properties
from frappe.model.document import Document

######

def get_meta(doctype, cached=True):
if cached:
if doctype not in frappe.local.meta:
frappe.local.meta[doctype] = frappe.cache().get_value("meta:" + doctype, lambda: Meta(doctype))
return frappe.local.meta.get(doctype)
else:
return Meta(doctype)

class Meta(Document):
_metaclass = True
def __init__(self, doctype):
super(Meta, self).__init__("DocType", doctype)
def get_link_fields(self):
tmp = self.get("fields", {"fieldtype":"Link"})
tmp.extend(self.get("fields", {"fieldtype":"Select", "options": "^link:"}))
return tmp
def get_table_fields(self):
return [
frappe._dict({"fieldname": "fields", "options": "DocField"}),
frappe._dict({"fieldname": "permissions", "options": "DocPerm"})
]
def get_valid_columns(self):
if not hasattr(self, "_valid_columns"):
doctype = self.__dict__.get("doctype")
self._valid_columns = frappe.db.get_table_columns(doctype)
return self._valid_columns
def get_table_field_doctype(self, fieldname):
return { "fields": "DocField", "permissions": "DocPerm"}.get(fieldname)
def process(self):
self.add_custom_fields()
self.apply_property_setters()
self.sort_fields()
def add_custom_fields(self):
try:
self.extend("fields", frappe.db.sql("""SELECT * FROM `tabCustom Field`
WHERE dt = %s AND docstatus < 2""", (doctype,), as_dict=1))
except Exception, e:
if e.args[0]==1146:
return
else:
raise
def apply_property_setters(self):
for ps in frappe.db.sql("""select * from `tabProperty Setter` where
doc_type=%s""", (doctype,), as_dict=1):
if ps['doctype_or_field']=='DocType':
if ps.get('property_type', None) in ('Int', 'Check'):
ps['value'] = cint(ps['value'])

self.set(ps["property"], ps["value"])
else:
docfield = self.get("fields", {"fieldname":ps["fieldname"]}, limit=1)[0]

if not docfield: continue
if ps["property"] in integer_docfield_properties:
ps['value'] = cint(ps['value'])

docfield.set(ps["property"], ps["value"])
def sort_fields(self):
"""sort on basis of previous_field"""
newlist = []
pending = self.get("fields")

if self.get("_idx"):
for fieldname in json.loads(self.get("_idx")):
d = self.get("fields", {"fieldname": fieldname}, limit=1)
if d:
newlist.append(d[0])
pending.remove(d[0])
else:
maxloops = 20
while (pending and maxloops>0):
maxloops -= 1
for d in pending[:]:
if d.previous_field:
# field already added
for n in newlist:
if n.fieldname==d.previous_field:
newlist.insert(newlist.index(n)+1, d)
pending.remove(d)
break
else:
newlist.append(d)
pending.remove(d)

# recurring at end
if pending:
newlist += pending

# renum
idx = 1
for d in newlist:
d.idx = idx
idx += 1

self.set("fields", newlist)

#######

def is_single(doctype):
try:
return frappe.db.get_value("DocType", doctype, "issingle")


+ 1
- 1
frappe/model/naming.py Целия файл

@@ -48,7 +48,7 @@ def set_new_name(doc):
doc.name = doc.get('__newname')

# default name for table
elif doc.meta[0].istable:
elif doc.meta.istable:
doc.name = make_autoname('#########', doc.doctype)
# unable to determine a name, use global series


+ 9
- 1
frappe/tests/test_document.py Целия файл

@@ -35,6 +35,7 @@ class TestDocument(unittest.TestCase):
# test if default values are added
self.assertEquals(d.send_reminder, 1)
return d
def test_insert_with_child(self):
d = Document({
@@ -55,7 +56,14 @@ class TestDocument(unittest.TestCase):
d1 = Document("Event", d.name)
self.assertTrue(d1.event_individuals[0].person, "Administrator")

def test_update(self):
d = self.test_insert()
d.subject = "subject changed"
d.save()
self.assertEquals(frappe.db.get_value(d.doctype, d.name, "subject"), "subject changed")
def test_mandatory(self):
d = Document({
"doctype": "User",


Зареждане…
Отказ
Запис