# 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.naming import set_new_name class BaseDocument(object): def __init__(self, d): self.update(d) def __getattr__(self, key): try: return super(BaseDocument, self).__getattr__(key) except AttributeError: if not key.startswith("__") and key not in ("doctype", "_meta", "meta") and key in self.meta.get_valid_columns(): return None else: raise @property def meta(self): if not hasattr(self, "_meta"): self._meta = frappe.get_meta(self.doctype) return self._meta def update(self, d): 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: if isinstance(filters, dict): value = _filter(self.__dict__.get(key), filters, limit=limit) else: default = filters filters = None value = self.__dict__.get(key, default) else: value = self.__dict__.get(key, default) if value is None and key!="_meta" and key in (d.fieldname for d in self.meta.get_table_fields()): self.set(key, []) value = self.__dict__.get(key) return value else: return self.__dict__ def set(self, key, value): if isinstance(value, list): tmp = [] for v in value: tmp.append(self._init_child(v, key)) value = tmp self.__dict__[key] = value def append(self, key, value=None): if value==None: value={} if isinstance(value, dict): if not self.get(key): self.__dict__[key] = [] value = self._init_child(value, key) self.get(key).append(value) return value else: raise ValueError def extend(self, key, value): if isinstance(value, list): for v in value: self.append(key, v) else: raise ValueError def _init_child(self, value, key): if not self.doctype: return value if not isinstance(value, BaseDocument): if not "doctype" in value: value["doctype"] = self.get_table_field_doctype(key) if not value["doctype"]: raise AttributeError, key value = BaseDocument(value) 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 def get_valid_dict(self): d = {} for fieldname in self.meta.get_valid_columns(): d[fieldname] = self.get(fieldname) return d def is_new(self): return self.get("__islocal") def as_dict(self): doc = self.get_valid_dict() doc["doctype"] = self.doctype for df in self.meta.get_table_fields(): doc[df.fieldname] = [d.as_dict() for d in (self.get(df.fieldname) or [])] return doc def get_table_field_doctype(self, fieldname): return self.meta.get_field(fieldname).options def get_parentfield_of_doctype(self, doctype): fieldname = [df.fieldname for df in self.meta.get_table_fields() if df.options==doctype] return fieldname[0] if fieldname else None 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): if self.get("__islocal") or not self.name: self.db_insert() return 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.get_valid_dict().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"), _(self.meta.get_label(fieldname))), 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