@@ -346,6 +346,10 @@ def set_value(doctype, docname, fieldname, value): | |||||
def get_doclist(doctype, name=None): | def get_doclist(doctype, name=None): | ||||
return bean(doctype, name).doclist | return bean(doctype, name).doclist | ||||
def get_doc(arg1, arg2=None): | |||||
import frappe.model.document | |||||
return frappe.model.document.get_doc(arg1, arg2) | |||||
def get_doctype(doctype, processed=False): | def get_doctype(doctype, processed=False): | ||||
import frappe.model.doctype | import frappe.model.doctype | ||||
return frappe.model.doctype.get(doctype, processed) | return frappe.model.doctype.get(doctype, processed) | ||||
@@ -6,9 +6,15 @@ import frappe | |||||
from frappe.utils import getdate, cint, add_months, date_diff, add_days, nowdate | from frappe.utils import getdate, cint, add_months, date_diff, add_days, nowdate | ||||
from frappe.core.doctype.user.user import STANDARD_USERS | from frappe.core.doctype.user.user import STANDARD_USERS | ||||
from frappe.model.document import Document | |||||
weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] | weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] | ||||
class Event(Document): | |||||
def validate(self): | |||||
if self.starts_on and self.ends_on and self.starts_on > self.ends_on: | |||||
frappe.msgprint(frappe._("Event End must be after Start"), raise_exception=True) | |||||
class DocType: | class DocType: | ||||
def __init__(self, d, dl): | def __init__(self, d, dl): | ||||
self.doc, self.doclist = d, dl | self.doc, self.doclist = d, dl | ||||
@@ -0,0 +1,242 @@ | |||||
# 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 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 |
@@ -17,7 +17,7 @@ methods in following modules are imported for backward compatibility | |||||
""" | """ | ||||
import frappe | import frappe | ||||
from frappe.modules import get_doctype_module | |||||
from frappe.modules import get_doctype_module, load_doctype_module, get_module_name | |||||
import frappe.model.doc | import frappe.model.doc | ||||
def get_obj(dt = None, dn = None, doc=None, doclist=None, with_children = 0): | def get_obj(dt = None, dn = None, doc=None, doclist=None, with_children = 0): | ||||
@@ -43,18 +43,6 @@ def get_server_obj(doc, doclist = [], basedoctype = ''): | |||||
module = get_doctype_module(doc.doctype) | module = get_doctype_module(doc.doctype) | ||||
return load_doctype_module(doc.doctype, module).DocType(doc, doclist) | return load_doctype_module(doc.doctype, module).DocType(doc, doclist) | ||||
def load_doctype_module(doctype, module=None, prefix=""): | |||||
if not module: | |||||
module = get_doctype_module(doctype) | |||||
return frappe.get_module(get_module_name(doctype, module, prefix)) | |||||
def get_module_name(doctype, module, prefix=""): | |||||
from frappe.modules import scrub | |||||
return '{app}.{module}.doctype.{doctype}.{prefix}{doctype}'.format(\ | |||||
app = scrub(frappe.local.module_app[scrub(module)]), | |||||
module = scrub(module), doctype = scrub(doctype), prefix=prefix) | |||||
def run_server_obj(server_obj, method_name, arg=None): | def run_server_obj(server_obj, method_name, arg=None): | ||||
""" | """ | ||||
Executes a method (`method_name`) from the given object (`server_obj`) | Executes a method (`method_name`) from the given object (`server_obj`) | ||||
@@ -14,7 +14,6 @@ class DatabaseQuery(object): | |||||
def __init__(self, doctype): | def __init__(self, doctype): | ||||
self.doctype = doctype | self.doctype = doctype | ||||
self.tables = [] | self.tables = [] | ||||
self.meta = [] | |||||
self.conditions = [] | self.conditions = [] | ||||
self.ignore_permissions = False | self.ignore_permissions = False | ||||
self.fields = ["name"] | self.fields = ["name"] | ||||
@@ -53,7 +52,6 @@ class DatabaseQuery(object): | |||||
def prepare_args(self): | def prepare_args(self): | ||||
self.parse_args() | self.parse_args() | ||||
self.extract_tables() | self.extract_tables() | ||||
self.load_metadata() | |||||
self.remove_user_tags() | self.remove_user_tags() | ||||
self.build_conditions() | self.build_conditions() | ||||
@@ -111,20 +109,13 @@ class DatabaseQuery(object): | |||||
if not table_name[0]=='`': | if not table_name[0]=='`': | ||||
table_name = '`' + table_name + '`' | table_name = '`' + table_name + '`' | ||||
if not table_name in self.tables: | if not table_name in self.tables: | ||||
self.tables.append(table_name) | |||||
self.append_table(table_name) | |||||
def load_metadata(self): | |||||
"""load all doctypes and roles""" | |||||
self.meta = {} | |||||
for t in self.tables: | |||||
if t.startswith('`'): | |||||
doctype = t[4:-1] | |||||
if self.meta.get(doctype): | |||||
continue | |||||
if (not self.ignore_permissions) and (not frappe.has_permission(doctype)): | |||||
raise frappe.PermissionError, doctype | |||||
self.meta[doctype] = frappe.model.doctype.get(doctype) | |||||
def append_table(self, table_name): | |||||
self.tables.append(table_name) | |||||
doctype = table_name[4:-1] | |||||
if (not self.ignore_permissions) and (not frappe.has_permission(doctype)): | |||||
raise frappe.PermissionError, doctype | |||||
def remove_user_tags(self): | def remove_user_tags(self): | ||||
"""remove column _user_tags if not in table""" | """remove column _user_tags if not in table""" | ||||
@@ -170,10 +161,7 @@ class DatabaseQuery(object): | |||||
tname = ('`tab' + f[0] + '`') | tname = ('`tab' + f[0] + '`') | ||||
if not tname in self.tables: | if not tname in self.tables: | ||||
self.tables.append(tname) | |||||
if not tname in self.meta: | |||||
self.load_metadata() | |||||
self.append_table(tname) | |||||
# prepare in condition | # prepare in condition | ||||
if f[2] in ['in', 'not in']: | if f[2] in ['in', 'not in']: | ||||
@@ -184,7 +172,7 @@ class DatabaseQuery(object): | |||||
f[3] = "(" + ', '.join(opts) + ")" | f[3] = "(" + ', '.join(opts) + ")" | ||||
self.conditions.append('ifnull(' + tname + '.' + f[1] + ", '') " + f[2] + " " + f[3]) | self.conditions.append('ifnull(' + tname + '.' + f[1] + ", '') " + f[2] + " " + f[3]) | ||||
else: | else: | ||||
df = self.meta[f[0]].get({"doctype": "DocField", "fieldname": f[1]}) | |||||
df = frappe.get_meta(f[0]).get("fields", {"fieldname": f[1]}) | |||||
if f[2] == "like" or (isinstance(f[3], basestring) and | if f[2] == "like" or (isinstance(f[3], basestring) and | ||||
(not df or df[0].fieldtype not in ["Float", "Int", "Currency", "Percent"])): | (not df or df[0].fieldtype not in ["Float", "Int", "Currency", "Percent"])): | ||||
@@ -216,10 +204,9 @@ class DatabaseQuery(object): | |||||
self.or_conditions = [] | self.or_conditions = [] | ||||
if not self.tables: self.extract_tables() | if not self.tables: self.extract_tables() | ||||
if not self.meta: self.load_metadata() | |||||
# explict permissions | # explict permissions | ||||
restricted_by_user = frappe.permissions.get_user_perms(self.meta[self.doctype]).restricted | |||||
restricted_by_user = frappe.permissions.get_user_perms(frappe.get_meta(self.doctype)).restricted | |||||
# get restrictions | # get restrictions | ||||
restrictions = frappe.defaults.get_restrictions() | restrictions = frappe.defaults.get_restrictions() | ||||
@@ -238,7 +225,7 @@ class DatabaseQuery(object): | |||||
return self.match_filters | return self.match_filters | ||||
def add_restrictions(self, restrictions): | def add_restrictions(self, restrictions): | ||||
fields_to_check = self.meta[self.doctype].get_restricted_fields(restrictions.keys()) | |||||
fields_to_check = frappe.get_meta(self.doctype).get_restricted_fields(restrictions.keys()) | |||||
if self.doctype in restrictions: | if self.doctype in restrictions: | ||||
fields_to_check.append(frappe._dict({"fieldname":"name", "options":self.doctype})) | fields_to_check.append(frappe._dict({"fieldname":"name", "options":self.doctype})) | ||||
@@ -23,7 +23,7 @@ type_map = { | |||||
,'Code': ('text', '') | ,'Code': ('text', '') | ||||
,'Text Editor': ('text', '') | ,'Text Editor': ('text', '') | ||||
,'Date': ('date', '') | ,'Date': ('date', '') | ||||
,'Datetime': ('datetime', '') | |||||
,'Datetime': ('datetime', '6') | |||||
,'Time': ('time', '') | ,'Time': ('time', '') | ||||
,'Text': ('text', '') | ,'Text': ('text', '') | ||||
,'Data': ('varchar', '255') | ,'Data': ('varchar', '255') | ||||
@@ -78,8 +78,8 @@ class DbTable: | |||||
# create table | # create table | ||||
frappe.db.sql("""create table `%s` ( | frappe.db.sql("""create table `%s` ( | ||||
name varchar(255) not null primary key, | name varchar(255) not null primary key, | ||||
creation datetime, | |||||
modified datetime, | |||||
creation datetime(6), | |||||
modified datetime(6), | |||||
modified_by varchar(40), | modified_by varchar(40), | ||||
owner varchar(60), | owner varchar(60), | ||||
docstatus int(1) default '0', | docstatus int(1) default '0', | ||||
@@ -5,196 +5,27 @@ from __future__ import unicode_literals | |||||
import frappe | import frappe | ||||
from frappe import _, msgprint | from frappe import _, msgprint | ||||
from frappe.utils import cint, flt, cstr, now | 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 | |||||
from frappe.modules import load_doctype_module | |||||
from frappe.model.base_document import BaseDocument | |||||
# save / update | |||||
# once_only validation | # once_only validation | ||||
# permissions | |||||
# methods | # methods | ||||
# timestamps and docstatus | |||||
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_doc(arg1, arg2=None): | |||||
if isinstance(arg1, basestring): | |||||
doctype = arg1 | |||||
else: | |||||
doctype = arg1.get("doctype") | |||||
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 | |||||
module = load_doctype_module(doctype) | |||||
classname = doctype.replace(" ", "") | |||||
if hasattr(module, classname): | |||||
_class = getattr(module, classname) | |||||
if issubclass(_class, Document): | |||||
return getattr(module, classname)(arg1, arg2) | |||||
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 | |||||
return Document(arg1, arg2) | |||||
@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 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 | |||||
class Document(BaseDocument): | class Document(BaseDocument): | ||||
def __init__(self, arg1, arg2=None): | def __init__(self, arg1, arg2=None): | ||||
self.doctype = self.name = None | self.doctype = self.name = None | ||||
@@ -224,7 +55,7 @@ class Document(BaseDocument): | |||||
def load_from_db(self): | def load_from_db(self): | ||||
if not getattr(self, "_metaclass", False) and self.meta.issingle: | if not getattr(self, "_metaclass", False) and self.meta.issingle: | ||||
self.update(frappe.db.get_singles_dict(self.doctype)) | self.update(frappe.db.get_singles_dict(self.doctype)) | ||||
self.fix_numeric_types() | |||||
self._fix_numeric_types() | |||||
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) | ||||
@@ -241,13 +72,24 @@ class Document(BaseDocument): | |||||
def get_table_fields(self): | def get_table_fields(self): | ||||
return self.meta.get('fields', {"fieldtype":"Table"}) | return self.meta.get('fields', {"fieldtype":"Table"}) | ||||
def has_permission(self, permtype): | |||||
if getattr(self, "_ignore_permissions", False): | |||||
return True | |||||
return frappe.has_permission(self.doctype, permtype, self) | |||||
def insert(self): | def insert(self): | ||||
# check links | # check links | ||||
# check permissions | # check permissions | ||||
self.set("__islocal", True) | |||||
if not self.has_permission("create"): | |||||
raise frappe.PermissionError | |||||
self._set_defaults() | self._set_defaults() | ||||
self._set_docstatus_user_and_timestamp() | self._set_docstatus_user_and_timestamp() | ||||
self._check_if_latest() | |||||
self.run_method("before_insert") | |||||
self.run_before_save_methods() | |||||
self._validate() | self._validate() | ||||
# run validate, on update etc. | # run validate, on update etc. | ||||
@@ -262,13 +104,20 @@ class Document(BaseDocument): | |||||
for d in self.get_all_children(): | for d in self.get_all_children(): | ||||
d.parent = self.name | d.parent = self.name | ||||
d.db_insert() | d.db_insert() | ||||
self.run_method("after_insert") | |||||
self.run_post_save_methods() | |||||
def save(self): | def save(self): | ||||
if self.get("__islocal") or not self.get("name"): | if self.get("__islocal") or not self.get("name"): | ||||
self.insert() | self.insert() | ||||
return | return | ||||
if not self.has_permission("write"): | |||||
raise frappe.PermissionError | |||||
self._set_docstatus_user_and_timestamp() | self._set_docstatus_user_and_timestamp() | ||||
self._check_if_latest() | |||||
self.run_before_save_methods() | |||||
self._validate() | self._validate() | ||||
# parent | # parent | ||||
@@ -278,10 +127,14 @@ class Document(BaseDocument): | |||||
self.db_update() | self.db_update() | ||||
# children | # children | ||||
ignore_children_type = self.get("_ignore_children_type", []) | |||||
for d in self.get_all_children(): | for d in self.get_all_children(): | ||||
d.parent = self.name | |||||
d.db_update() | |||||
if d.doctype not in _ignore_children_type: | |||||
d.parent = self.name | |||||
d.db_update() | |||||
self.run_post_save_methods() | |||||
def update_single(self, d): | def update_single(self, d): | ||||
frappe.db.sql("""delete from tabSingles where doctype=%s""", self.doctype) | frappe.db.sql("""delete from tabSingles where doctype=%s""", self.doctype) | ||||
for field, value in d.iteritems(): | for field, value in d.iteritems(): | ||||
@@ -308,6 +161,14 @@ class Document(BaseDocument): | |||||
d.owner = self.owner | d.owner = self.owner | ||||
if not d.creation: | if not d.creation: | ||||
d.creation = self.creation | d.creation = self.creation | ||||
def _validate(self): | |||||
self._validate_mandatory() | |||||
self._validate_links() | |||||
self._validate_constants() | |||||
for d in self.get_all_children(): | |||||
d._validate_constants() | |||||
self._extract_images_from_text_editor() | |||||
def _set_defaults(self): | def _set_defaults(self): | ||||
if frappe.flags.in_import: | if frappe.flags.in_import: | ||||
@@ -323,16 +184,10 @@ class Document(BaseDocument): | |||||
if isinstance(value, list): | if isinstance(value, list): | ||||
for d in value: | for d in value: | ||||
d.set_missing_values(new_doc) | d.set_missing_values(new_doc) | ||||
def _validate(self): | |||||
self.check_if_latest() | |||||
self.validate_mandatory() | |||||
self.validate_links() | |||||
# check restrictions | |||||
def check_if_latest(self): | |||||
def _check_if_latest(self): | |||||
conflict = False | conflict = False | ||||
self._action = "save" | |||||
if not self.get('__islocal'): | if not self.get('__islocal'): | ||||
if self.meta.issingle: | if self.meta.issingle: | ||||
modified = frappe.db.get_value(self.doctype, self.name, "modified") | modified = frappe.db.get_value(self.doctype, self.name, "modified") | ||||
@@ -366,6 +221,8 @@ class Document(BaseDocument): | |||||
self._action = "save" | self._action = "save" | ||||
elif self.docstatus==1: | elif self.docstatus==1: | ||||
self._action = "submit" | self._action = "submit" | ||||
if not self.has_permission("submit"): | |||||
raise frappe.PermissionError | |||||
else: | else: | ||||
raise frappe.DocstatusTransitionError | raise frappe.DocstatusTransitionError | ||||
@@ -373,8 +230,12 @@ class Document(BaseDocument): | |||||
if self.docstatus==1: | if self.docstatus==1: | ||||
self._action = "update_after_submit" | self._action = "update_after_submit" | ||||
self.validate_update_after_submit() | self.validate_update_after_submit() | ||||
if not self.has_permission("submit"): | |||||
raise frappe.PermissionError | |||||
elif self.docstatus==2: | elif self.docstatus==2: | ||||
self._action = "cancel" | self._action = "cancel" | ||||
if not self.has_permission("cancel"): | |||||
raise frappe.PermissionError | |||||
else: | else: | ||||
raise frappe.DocstatusTransitionError | raise frappe.DocstatusTransitionError | ||||
@@ -385,13 +246,13 @@ class Document(BaseDocument): | |||||
# check only allowed values are updated | # check only allowed values are updated | ||||
pass | pass | ||||
def validate_mandatory(self): | |||||
def _validate_mandatory(self): | |||||
if self.get("ignore_mandatory"): | if self.get("ignore_mandatory"): | ||||
return | return | ||||
missing = self.get_missing_mandatory_fields() | |||||
missing = self._get_missing_mandatory_fields() | |||||
for d in self.get_all_children(): | for d in self.get_all_children(): | ||||
missing.extend(d.get_missing_mandatory_fields()) | |||||
missing.extend(d._get_missing_mandatory_fields()) | |||||
if not missing: | if not missing: | ||||
return | return | ||||
@@ -401,7 +262,7 @@ class Document(BaseDocument): | |||||
raise frappe.MandatoryError(", ".join((each[0] for each in missing))) | raise frappe.MandatoryError(", ".join((each[0] for each in missing))) | ||||
def validate_links(self): | |||||
def _validate_links(self): | |||||
if self.get("ignore_links"): | if self.get("ignore_links"): | ||||
return | return | ||||
@@ -423,40 +284,51 @@ class Document(BaseDocument): | |||||
if isinstance(value, list): | if isinstance(value, list): | ||||
ret.extend(value) | ret.extend(value) | ||||
return ret | return ret | ||||
def _extract_images_from_text_editor(self): | |||||
from frappe.utils.file_manager import extract_images_from_html | |||||
if self.doctype != "DocType": | |||||
for df in self.meta.get("fields", {"fieldtype":"Text Editor"}): | |||||
extract_images_from_html(self, df.fieldname) | |||||
def trigger(self, func, *args, **kwargs): | |||||
return | |||||
def run_method(self, method, *args, **kwargs): | |||||
"""run standard triggers, plus those in frappe""" | |||||
def add_to_response(out, new_response): | |||||
if isinstance(new_response, dict): | |||||
out.update(new_response) | |||||
if hasattr(self, method): | |||||
add_to_response(frappe.local.response, | |||||
getattr(self, method)(*args, **kwargs)) | |||||
args = [self, method] + list(args or []) | |||||
for handler in frappe.get_hooks("bean_event:" + self.doctype + ":" + method) \ | |||||
+ frappe.get_hooks("bean_event:*:" + method): | |||||
add_to_response(frappe.local.response, | |||||
frappe.call(frappe.get_attr(handler), *args, **kwargs)) | |||||
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) }""" | |||||
return frappe.local.response | |||||
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 | |||||
def run_before_save_methods(self): | |||||
if self._action=="save": | |||||
self.run_method("validate") | |||||
self.run_method("before_save") | |||||
elif self._action=="submit": | |||||
self.run_method("validate") | |||||
self.run_method("before_submit") | |||||
elif self._action=="cancel": | |||||
self.run_method("before_cancel") | |||||
elif self._action=="update_after_submit": | |||||
self.run_method("before_update_after_submit") | |||||
if add: | |||||
out.append(d) | |||||
if limit and (len(out)-1)==limit: | |||||
break | |||||
return out | |||||
def run_post_save_methods(self): | |||||
if self._action=="save": | |||||
self.run_method("on_update") | |||||
elif self._action=="submit": | |||||
self.run_method("on_update") | |||||
self.run_method("on_submit") | |||||
elif self._action=="cancel": | |||||
self.run_method("on_cancel") | |||||
elif self._action=="update_after_submit": | |||||
self.run_method("on_update_after_submit") |
@@ -115,6 +115,20 @@ class Meta(Document): | |||||
idx += 1 | idx += 1 | ||||
self.set("fields", newlist) | self.set("fields", newlist) | ||||
def get_restricted_fields(self, restricted_types): | |||||
restricted_fields = self.get("fields", { | |||||
"fieldtype":"Link", | |||||
"parent": self.name, | |||||
"ignore_restrictions":("!=", 1), | |||||
"options":("in", restricted_types) | |||||
}) | |||||
if self.name in restricted_types: | |||||
restricted_fields.append(frappe._dict({ | |||||
"label":"Name", "fieldname":"name", "options": self.name | |||||
})) | |||||
return restricted_fields | |||||
####### | ####### | ||||
@@ -44,3 +44,14 @@ def export_doc(doctype, name, module=None): | |||||
def get_doctype_module(doctype): | def get_doctype_module(doctype): | ||||
return frappe.db.get_value('DocType', doctype, 'module') or "core" | return frappe.db.get_value('DocType', doctype, 'module') or "core" | ||||
def load_doctype_module(doctype, module=None, prefix=""): | |||||
if not module: | |||||
module = get_doctype_module(doctype) | |||||
return frappe.get_module(get_module_name(doctype, module, prefix)) | |||||
def get_module_name(doctype, module, prefix=""): | |||||
from frappe.modules import scrub | |||||
return '{app}.{module}.doctype.{doctype}.{prefix}{doctype}'.format(\ | |||||
app = scrub(frappe.local.module_app[scrub(module)]), | |||||
module = scrub(module), doctype = scrub(doctype), prefix=prefix) |
@@ -22,3 +22,4 @@ frappe.patches.4_0.private_backups | |||||
frappe.patches.4_0.set_module_in_report | frappe.patches.4_0.set_module_in_report | ||||
frappe.patches.4_0.remove_old_parent | frappe.patches.4_0.remove_old_parent | ||||
frappe.patches.4_0.rename_profile_to_user | frappe.patches.4_0.rename_profile_to_user | ||||
frappe.patches.4_0.update_datetime |
@@ -0,0 +1,9 @@ | |||||
import frappe | |||||
def execute(): | |||||
for table in frappe.db.sql_list("show tables"): | |||||
for field in frappe.db.sql("desc `%s`" % table): | |||||
if field[1]=="datetime": | |||||
frappe.db.sql("alter table `%s` change `%s` `%s` datetime(6)" % \ | |||||
(table, field[0], field[0])) | |||||
@@ -19,12 +19,12 @@ def has_permission(doctype, ptype="read", refdoc=None, verbose=True): | |||||
if frappe.is_table(doctype): | if frappe.is_table(doctype): | ||||
return True | return True | ||||
meta = frappe.get_doctype(doctype) | |||||
meta = frappe.get_meta(doctype) | |||||
if ptype=="submit" and not cint(meta[0].is_submittable): | |||||
if ptype=="submit" and not cint(meta.is_submittable): | |||||
return False | return False | ||||
if ptype=="import" and not cint(meta[0].allow_import): | |||||
if ptype=="import" and not cint(meta.allow_import): | |||||
return False | return False | ||||
if frappe.session.user=="Administrator": | if frappe.session.user=="Administrator": | ||||
@@ -36,24 +36,26 @@ def has_permission(doctype, ptype="read", refdoc=None, verbose=True): | |||||
if refdoc: | if refdoc: | ||||
if isinstance(refdoc, basestring): | if isinstance(refdoc, basestring): | ||||
refdoc = frappe.doc(meta[0].name, refdoc) | |||||
refdoc = frappe.doc(meta.name, refdoc) | |||||
if not has_unrestricted_access(meta, refdoc, verbose=verbose): | if not has_unrestricted_access(meta, refdoc, verbose=verbose): | ||||
return False | return False | ||||
if not has_additional_permission(refdoc): | |||||
if not has_controller_permissions(refdoc): | |||||
return False | return False | ||||
return True | return True | ||||
def get_user_perms(meta, user=None): | def get_user_perms(meta, user=None): | ||||
cache_key = (meta[0].name, user) | |||||
if not user: | |||||
user = frappe.session.user | |||||
cache_key = (meta.name, user) | |||||
if not frappe.local.user_perms.get(cache_key): | if not frappe.local.user_perms.get(cache_key): | ||||
perms = frappe._dict() | perms = frappe._dict() | ||||
user_roles = frappe.get_roles(user) | user_roles = frappe.get_roles(user) | ||||
for p in meta.get({"doctype": "DocPerm"}): | |||||
if cint(p.permlevel)==0 and (p.role=="All" or p.role in user_roles): | |||||
for p in meta.permissions: | |||||
if cint(p.permlevel)==0 and (p.role in user_roles): | |||||
for ptype in rights: | for ptype in rights: | ||||
if ptype == "restricted": | if ptype == "restricted": | ||||
perms[ptype] = perms.get(ptype, 1) and cint(p.get(ptype)) | perms[ptype] = perms.get(ptype, 1) and cint(p.get(ptype)) | ||||
@@ -100,8 +102,8 @@ def has_unrestricted_access(meta, refdoc, verbose=True): | |||||
# check all restrictions before returning | # check all restrictions before returning | ||||
return False if has_restricted_data else True | return False if has_restricted_data else True | ||||
def has_additional_permission(doc): | |||||
if doc.fields.get("__islocal"): | |||||
def has_controller_permissions(doc): | |||||
if doc.get("__islocal"): | |||||
bean = frappe.bean([doc]) | bean = frappe.bean([doc]) | ||||
else: | else: | ||||
bean = frappe.bean(doc.doctype, doc.name) | bean = frappe.bean(doc.doctype, doc.name) | ||||
@@ -3,11 +3,9 @@ | |||||
import frappe, unittest, time | import frappe, unittest, time | ||||
from frappe.model.document import Document | |||||
class TestDocument(unittest.TestCase): | class TestDocument(unittest.TestCase): | ||||
def test_load(self): | def test_load(self): | ||||
d = Document("DocType", "User") | |||||
d = frappe.get_doc("DocType", "User") | |||||
self.assertEquals(d.doctype, "DocType") | self.assertEquals(d.doctype, "DocType") | ||||
self.assertEquals(d.name, "User") | self.assertEquals(d.name, "User") | ||||
self.assertEquals(d.allow_rename, 1) | self.assertEquals(d.allow_rename, 1) | ||||
@@ -16,13 +14,13 @@ class TestDocument(unittest.TestCase): | |||||
self.assertTrue(filter(lambda d: d.fieldname=="email", d.fields)) | self.assertTrue(filter(lambda d: d.fieldname=="email", d.fields)) | ||||
def test_load_single(self): | def test_load_single(self): | ||||
d = Document("Website Settings", "Website Settings") | |||||
d = frappe.get_doc("Website Settings", "Website Settings") | |||||
self.assertEquals(d.name, "Website Settings") | self.assertEquals(d.name, "Website Settings") | ||||
self.assertEquals(d.doctype, "Website Settings") | self.assertEquals(d.doctype, "Website Settings") | ||||
self.assertTrue(d.disable_signup in (0, 1)) | self.assertTrue(d.disable_signup in (0, 1)) | ||||
def test_insert(self): | def test_insert(self): | ||||
d = Document({ | |||||
d = frappe.get_doc({ | |||||
"doctype":"Event", | "doctype":"Event", | ||||
"subject":"_Test Event 1", | "subject":"_Test Event 1", | ||||
"starts_on": "2014-01-01", | "starts_on": "2014-01-01", | ||||
@@ -38,7 +36,7 @@ class TestDocument(unittest.TestCase): | |||||
return d | return d | ||||
def test_insert_with_child(self): | def test_insert_with_child(self): | ||||
d = Document({ | |||||
d = frappe.get_doc({ | |||||
"doctype":"Event", | "doctype":"Event", | ||||
"subject":"_Test Event 2", | "subject":"_Test Event 2", | ||||
"starts_on": "2014-01-01", | "starts_on": "2014-01-01", | ||||
@@ -54,7 +52,7 @@ class TestDocument(unittest.TestCase): | |||||
self.assertEquals(frappe.db.get_value("Event", d.name, "subject"), | self.assertEquals(frappe.db.get_value("Event", d.name, "subject"), | ||||
"_Test Event 2") | "_Test Event 2") | ||||
d1 = Document("Event", d.name) | |||||
d1 = frappe.get_doc("Event", d.name) | |||||
self.assertTrue(d1.event_individuals[0].person, "Administrator") | self.assertTrue(d1.event_individuals[0].person, "Administrator") | ||||
def test_update(self): | def test_update(self): | ||||
@@ -65,7 +63,7 @@ class TestDocument(unittest.TestCase): | |||||
self.assertEquals(frappe.db.get_value(d.doctype, d.name, "subject"), "subject changed") | self.assertEquals(frappe.db.get_value(d.doctype, d.name, "subject"), "subject changed") | ||||
def test_mandatory(self): | def test_mandatory(self): | ||||
d = Document({ | |||||
d = frappe.get_doc({ | |||||
"doctype": "User", | "doctype": "User", | ||||
"email": "test_mandatory@example.com", | "email": "test_mandatory@example.com", | ||||
}) | }) | ||||
@@ -77,20 +75,29 @@ class TestDocument(unittest.TestCase): | |||||
def test_confict_validation(self): | def test_confict_validation(self): | ||||
d1 = self.test_insert() | d1 = self.test_insert() | ||||
d2 = Document(d1.doctype, d1.name) | |||||
time.sleep(1) | |||||
d2 = frappe.get_doc(d1.doctype, d1.name) | |||||
d1.save() | d1.save() | ||||
self.assertRaises(frappe.TimestampMismatchError, d2.save) | self.assertRaises(frappe.TimestampMismatchError, d2.save) | ||||
def test_confict_validation_single(self): | def test_confict_validation_single(self): | ||||
d1 = Document("Website Settings", "Website Settings") | |||||
d2 = Document("Website Settings", "Website Settings") | |||||
time.sleep(1) | |||||
d1 = frappe.get_doc("Website Settings", "Website Settings") | |||||
d2 = frappe.get_doc("Website Settings", "Website Settings") | |||||
d1.save() | d1.save() | ||||
self.assertRaises(frappe.TimestampMismatchError, d2.save) | self.assertRaises(frappe.TimestampMismatchError, d2.save) | ||||
def test_permission(self): | |||||
frappe.set_user("Guest") | |||||
d = self.assertRaises(frappe.PermissionError, self.test_insert) | |||||
frappe.set_user("Administrator") | |||||
def test_permission_single(self): | |||||
frappe.set_user("Guest") | |||||
d = frappe.get_doc("Website Settings", "Website Settigns") | |||||
self.assertRaises(frappe.PermissionError, d.save) | |||||
frappe.set_user("Administrator") | |||||
def test_link_validation(self): | def test_link_validation(self): | ||||
d = Document({ | |||||
d = frappe.get_doc({ | |||||
"doctype": "User", | "doctype": "User", | ||||
"email": "test_link_validation@example.com", | "email": "test_link_validation@example.com", | ||||
"first_name": "Link Validation", | "first_name": "Link Validation", | ||||
@@ -106,4 +113,12 @@ class TestDocument(unittest.TestCase): | |||||
"role": "System Manager" | "role": "System Manager" | ||||
}) | }) | ||||
d.insert() | d.insert() | ||||
self.assertEquals(frappe.db.get_value("User", d.name), d.name) | |||||
self.assertEquals(frappe.db.get_value("User", d.name), d.name) | |||||
def test_validate(self): | |||||
d = self.test_insert() | |||||
d.starts_on = "2014-01-01" | |||||
d.ends_on = "2013-01-01" | |||||
self.assertRaises(frappe.ValidationError, d.validate) | |||||
self.assertRaises(frappe.ValidationError, d.save) | |||||
@@ -177,7 +177,7 @@ def now(): | |||||
return getdate(frappe.local.current_date).strftime("%Y-%m-%d") + " " + \ | return getdate(frappe.local.current_date).strftime("%Y-%m-%d") + " " + \ | ||||
now_datetime().strftime('%H:%M:%S') | now_datetime().strftime('%H:%M:%S') | ||||
else: | else: | ||||
return now_datetime().strftime('%Y-%m-%d %H:%M:%S') | |||||
return now_datetime().strftime('%Y-%m-%d %H:%M:%S.%f') | |||||
def nowdate(): | def nowdate(): | ||||
"""return current date as yyyy-mm-dd""" | """return current date as yyyy-mm-dd""" | ||||
@@ -217,7 +217,7 @@ def get_datetime(datetime_str): | |||||
if isinstance(datetime_str, datetime): | if isinstance(datetime_str, datetime): | ||||
return datetime_str.replace(microsecond=0, tzinfo=None) | return datetime_str.replace(microsecond=0, tzinfo=None) | ||||
return datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S') | |||||
return datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S.%f') | |||||
def get_datetime_str(datetime_obj): | def get_datetime_str(datetime_obj): | ||||
if isinstance(datetime_obj, basestring): | if isinstance(datetime_obj, basestring): | ||||