seitime-frappe/frappe/model/base_document.py

252 lines
6.9 KiB
Python

# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _, msgprint
from frappe.utils import cint, flt, cstr, now
from frappe.model import default_fields
from frappe.model.db_schema import type_map
from frappe.model.naming import set_new_name
class BaseDocument(object):
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!= "_valid_columns" and key in self.get_valid_columns():
return None
raise AttributeError(key)
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, filters=None, limit=None, default=None):
if key:
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, 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):
if not self.get(key):
self.__dict__[key] = []
self.get(key).append(self._init_child(value, key))
else:
raise ValueError
def extend(self, key, value):
if isinstance(value, list):
for v in value:
self.append(v)
else:
raise ValueError
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.get_table_field_doctype(key)
if not value.get("doctype"):
raise AttributeError, key
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) or []) + 1
return value
@property
def doc(self):
return self
@property
def meta(self):
if not self.get("_meta"):
self._meta = frappe.get_meta(self.doctype)
return self._meta
def get_valid_dict(self):
d = {}
for fieldname in self.valid_columns:
d[fieldname] = self.get(fieldname)
return d
@property
def valid_columns(self):
return self.get_valid_columns()
def get_valid_columns(self):
if not hasattr(self, "_valid_columns"):
doctype = self.__dict__.get("doctype")
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._valid_columns
def as_dict(self):
doc = self.get_valid_dict()
doc["doctype"] = self.doctype
for df in self.get_table_fields():
doc[df.fieldname] = [d.as_dict() for d in (self.get(df.fieldname) or [])]
return doc
def get_table_fields(self):
return self.meta.get('fields', {"fieldtype":"Table"})
def get_table_field_doctype(self, fieldname):
return self.meta.get("fields", {"fieldname":fieldname})[0].options
def db_insert(self):
set_new_name(self)
d = self.get_valid_dict()
columns = d.keys()
frappe.db.sql("""insert into `tab{doctype}`
({columns}) values ({values})""".format(
doctype = self.doctype,
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("fields"):
if df.fieldtype in ("Int", "Check"):
self.set(df.fieldname, cint(self.get(df.fieldname)))
elif df.fieldtype in ("Float", "Currency"):
self.set(df.fieldname, flt(self.get(df.fieldname)))
if self.docstatus is not None:
self.docstatus = cint(self.docstatus)
def set_missing_values(self, d):
for key, value in d.iteritems():
if self.get(key) is None:
self.set(key, value)
def _get_missing_mandatory_fields(self):
"""Get mandatory fields that do not have any values"""
def get_msg(df):
if df.fieldtype == "Table":
return "{}: {}: {}".format(_("Error"), _("Data missing in table"), _(df.label))
elif self.parentfield:
return "{}: {} #{}: {}: {}".format(_("Error"), _("Row"), self.idx,
_("Value missing for"), _(df.label))
else:
return "{}: {}: {}".format(_("Error"), _("Value missing for"), _(df.label))
missing = []
for df in self.meta.get("fields", {"reqd": 1}):
if self.get(df.fieldname) in (None, []):
missing.append((df.fieldname, get_msg(df)))
return missing
def get_invalid_links(self):
def get_msg(df, docname):
if self.parentfield:
return "{} #{}: {}: {}".format(_("Row"), self.idx, _(df.label), docname)
else:
return "{}: {}".format(_(df.label), docname)
invalid_links = []
for df in self.meta.get_link_fields():
doctype = df.options
if not doctype:
frappe.throw("Options not set for link field: {}".format(df.fieldname))
elif doctype.lower().startswith("link:"):
doctype = doctype[5:]
docname = self.get(df.fieldname)
if docname and not frappe.db.get_value(doctype, docname):
invalid_links.append((df.fieldname, docname, get_msg(df, docname)))
return invalid_links
def _validate_constants(self):
if frappe.flags.in_import:
return
constants = [d.fieldname for d in self.meta.get("fields", {"set_only_once": 1})]
if constants:
values = frappe.db.get_value(self.doctype, self.name, constants, as_dict=True)
for fieldname in constants:
if self.get(fieldname) != values.get(fieldname):
frappe.throw("{0}: {1}".format(_("Value cannot be changed for"),
_(meta.get("fields", {"fieldname":fieldname})[0].label)),
frappe.CannotChangeConstantError)
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