@@ -346,6 +346,10 @@ def set_value(doctype, docname, fieldname, value): | |||
def get_doclist(doctype, name=None): | |||
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): | |||
import frappe.model.doctype | |||
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.core.doctype.user.user import STANDARD_USERS | |||
from frappe.model.document import Document | |||
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: | |||
def __init__(self, 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 | |||
from frappe.modules import get_doctype_module | |||
from frappe.modules import get_doctype_module, load_doctype_module, get_module_name | |||
import frappe.model.doc | |||
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) | |||
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): | |||
""" | |||
Executes a method (`method_name`) from the given object (`server_obj`) | |||
@@ -14,7 +14,6 @@ class DatabaseQuery(object): | |||
def __init__(self, doctype): | |||
self.doctype = doctype | |||
self.tables = [] | |||
self.meta = [] | |||
self.conditions = [] | |||
self.ignore_permissions = False | |||
self.fields = ["name"] | |||
@@ -53,7 +52,6 @@ class DatabaseQuery(object): | |||
def prepare_args(self): | |||
self.parse_args() | |||
self.extract_tables() | |||
self.load_metadata() | |||
self.remove_user_tags() | |||
self.build_conditions() | |||
@@ -111,20 +109,13 @@ class DatabaseQuery(object): | |||
if not table_name[0]=='`': | |||
table_name = '`' + table_name + '`' | |||
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): | |||
"""remove column _user_tags if not in table""" | |||
@@ -170,10 +161,7 @@ class DatabaseQuery(object): | |||
tname = ('`tab' + f[0] + '`') | |||
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 | |||
if f[2] in ['in', 'not in']: | |||
@@ -184,7 +172,7 @@ class DatabaseQuery(object): | |||
f[3] = "(" + ', '.join(opts) + ")" | |||
self.conditions.append('ifnull(' + tname + '.' + f[1] + ", '') " + f[2] + " " + f[3]) | |||
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 | |||
(not df or df[0].fieldtype not in ["Float", "Int", "Currency", "Percent"])): | |||
@@ -216,10 +204,9 @@ class DatabaseQuery(object): | |||
self.or_conditions = [] | |||
if not self.tables: self.extract_tables() | |||
if not self.meta: self.load_metadata() | |||
# 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 | |||
restrictions = frappe.defaults.get_restrictions() | |||
@@ -238,7 +225,7 @@ class DatabaseQuery(object): | |||
return self.match_filters | |||
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: | |||
fields_to_check.append(frappe._dict({"fieldname":"name", "options":self.doctype})) | |||
@@ -23,7 +23,7 @@ type_map = { | |||
,'Code': ('text', '') | |||
,'Text Editor': ('text', '') | |||
,'Date': ('date', '') | |||
,'Datetime': ('datetime', '') | |||
,'Datetime': ('datetime', '6') | |||
,'Time': ('time', '') | |||
,'Text': ('text', '') | |||
,'Data': ('varchar', '255') | |||
@@ -78,8 +78,8 @@ class DbTable: | |||
# create table | |||
frappe.db.sql("""create table `%s` ( | |||
name varchar(255) not null primary key, | |||
creation datetime, | |||
modified datetime, | |||
creation datetime(6), | |||
modified datetime(6), | |||
modified_by varchar(40), | |||
owner varchar(60), | |||
docstatus int(1) default '0', | |||
@@ -5,196 +5,27 @@ 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 | |||
from frappe.modules import load_doctype_module | |||
from frappe.model.base_document import BaseDocument | |||
# save / update | |||
# once_only validation | |||
# permissions | |||
# 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): | |||
def __init__(self, arg1, arg2=None): | |||
self.doctype = self.name = None | |||
@@ -224,7 +55,7 @@ class Document(BaseDocument): | |||
def load_from_db(self): | |||
if not getattr(self, "_metaclass", False) and self.meta.issingle: | |||
self.update(frappe.db.get_singles_dict(self.doctype)) | |||
self.fix_numeric_types() | |||
self._fix_numeric_types() | |||
else: | |||
d = frappe.db.get_value(self.doctype, self.name, "*", as_dict=1) | |||
@@ -241,13 +72,24 @@ class Document(BaseDocument): | |||
def get_table_fields(self): | |||
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): | |||
# check links | |||
# check permissions | |||
self.set("__islocal", True) | |||
if not self.has_permission("create"): | |||
raise frappe.PermissionError | |||
self._set_defaults() | |||
self._set_docstatus_user_and_timestamp() | |||
self._check_if_latest() | |||
self.run_method("before_insert") | |||
self.run_before_save_methods() | |||
self._validate() | |||
# run validate, on update etc. | |||
@@ -262,13 +104,20 @@ class Document(BaseDocument): | |||
for d in self.get_all_children(): | |||
d.parent = self.name | |||
d.db_insert() | |||
self.run_method("after_insert") | |||
self.run_post_save_methods() | |||
def save(self): | |||
if self.get("__islocal") or not self.get("name"): | |||
self.insert() | |||
return | |||
if not self.has_permission("write"): | |||
raise frappe.PermissionError | |||
self._set_docstatus_user_and_timestamp() | |||
self._check_if_latest() | |||
self.run_before_save_methods() | |||
self._validate() | |||
# parent | |||
@@ -278,10 +127,14 @@ class Document(BaseDocument): | |||
self.db_update() | |||
# children | |||
ignore_children_type = self.get("_ignore_children_type", []) | |||
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): | |||
frappe.db.sql("""delete from tabSingles where doctype=%s""", self.doctype) | |||
for field, value in d.iteritems(): | |||
@@ -308,6 +161,14 @@ class Document(BaseDocument): | |||
d.owner = self.owner | |||
if not d.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): | |||
if frappe.flags.in_import: | |||
@@ -323,16 +184,10 @@ class Document(BaseDocument): | |||
if isinstance(value, list): | |||
for d in value: | |||
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 | |||
self._action = "save" | |||
if not self.get('__islocal'): | |||
if self.meta.issingle: | |||
modified = frappe.db.get_value(self.doctype, self.name, "modified") | |||
@@ -366,6 +221,8 @@ class Document(BaseDocument): | |||
self._action = "save" | |||
elif self.docstatus==1: | |||
self._action = "submit" | |||
if not self.has_permission("submit"): | |||
raise frappe.PermissionError | |||
else: | |||
raise frappe.DocstatusTransitionError | |||
@@ -373,8 +230,12 @@ class Document(BaseDocument): | |||
if self.docstatus==1: | |||
self._action = "update_after_submit" | |||
self.validate_update_after_submit() | |||
if not self.has_permission("submit"): | |||
raise frappe.PermissionError | |||
elif self.docstatus==2: | |||
self._action = "cancel" | |||
if not self.has_permission("cancel"): | |||
raise frappe.PermissionError | |||
else: | |||
raise frappe.DocstatusTransitionError | |||
@@ -385,13 +246,13 @@ class Document(BaseDocument): | |||
# check only allowed values are updated | |||
pass | |||
def validate_mandatory(self): | |||
def _validate_mandatory(self): | |||
if self.get("ignore_mandatory"): | |||
return | |||
missing = self.get_missing_mandatory_fields() | |||
missing = self._get_missing_mandatory_fields() | |||
for d in self.get_all_children(): | |||
missing.extend(d.get_missing_mandatory_fields()) | |||
missing.extend(d._get_missing_mandatory_fields()) | |||
if not missing: | |||
return | |||
@@ -401,7 +262,7 @@ class Document(BaseDocument): | |||
raise frappe.MandatoryError(", ".join((each[0] for each in missing))) | |||
def validate_links(self): | |||
def _validate_links(self): | |||
if self.get("ignore_links"): | |||
return | |||
@@ -423,40 +284,51 @@ class Document(BaseDocument): | |||
if isinstance(value, list): | |||
ret.extend(value) | |||
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 | |||
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): | |||
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.remove_old_parent | |||
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): | |||
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 | |||
if ptype=="import" and not cint(meta[0].allow_import): | |||
if ptype=="import" and not cint(meta.allow_import): | |||
return False | |||
if frappe.session.user=="Administrator": | |||
@@ -36,24 +36,26 @@ def has_permission(doctype, ptype="read", refdoc=None, verbose=True): | |||
if refdoc: | |||
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): | |||
return False | |||
if not has_additional_permission(refdoc): | |||
if not has_controller_permissions(refdoc): | |||
return False | |||
return True | |||
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): | |||
perms = frappe._dict() | |||
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: | |||
if ptype == "restricted": | |||
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 | |||
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]) | |||
else: | |||
bean = frappe.bean(doc.doctype, doc.name) | |||
@@ -3,11 +3,9 @@ | |||
import frappe, unittest, time | |||
from frappe.model.document import Document | |||
class TestDocument(unittest.TestCase): | |||
def test_load(self): | |||
d = Document("DocType", "User") | |||
d = frappe.get_doc("DocType", "User") | |||
self.assertEquals(d.doctype, "DocType") | |||
self.assertEquals(d.name, "User") | |||
self.assertEquals(d.allow_rename, 1) | |||
@@ -16,13 +14,13 @@ class TestDocument(unittest.TestCase): | |||
self.assertTrue(filter(lambda d: d.fieldname=="email", d.fields)) | |||
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.doctype, "Website Settings") | |||
self.assertTrue(d.disable_signup in (0, 1)) | |||
def test_insert(self): | |||
d = Document({ | |||
d = frappe.get_doc({ | |||
"doctype":"Event", | |||
"subject":"_Test Event 1", | |||
"starts_on": "2014-01-01", | |||
@@ -38,7 +36,7 @@ class TestDocument(unittest.TestCase): | |||
return d | |||
def test_insert_with_child(self): | |||
d = Document({ | |||
d = frappe.get_doc({ | |||
"doctype":"Event", | |||
"subject":"_Test Event 2", | |||
"starts_on": "2014-01-01", | |||
@@ -54,7 +52,7 @@ class TestDocument(unittest.TestCase): | |||
self.assertEquals(frappe.db.get_value("Event", d.name, "subject"), | |||
"_Test Event 2") | |||
d1 = Document("Event", d.name) | |||
d1 = frappe.get_doc("Event", d.name) | |||
self.assertTrue(d1.event_individuals[0].person, "Administrator") | |||
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") | |||
def test_mandatory(self): | |||
d = Document({ | |||
d = frappe.get_doc({ | |||
"doctype": "User", | |||
"email": "test_mandatory@example.com", | |||
}) | |||
@@ -77,20 +75,29 @@ class TestDocument(unittest.TestCase): | |||
def test_confict_validation(self): | |||
d1 = self.test_insert() | |||
d2 = Document(d1.doctype, d1.name) | |||
time.sleep(1) | |||
d2 = frappe.get_doc(d1.doctype, d1.name) | |||
d1.save() | |||
self.assertRaises(frappe.TimestampMismatchError, d2.save) | |||
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() | |||
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): | |||
d = Document({ | |||
d = frappe.get_doc({ | |||
"doctype": "User", | |||
"email": "test_link_validation@example.com", | |||
"first_name": "Link Validation", | |||
@@ -106,4 +113,12 @@ class TestDocument(unittest.TestCase): | |||
"role": "System Manager" | |||
}) | |||
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") + " " + \ | |||
now_datetime().strftime('%H:%M:%S') | |||
else: | |||
return now_datetime().strftime('%Y-%m-%d %H:%M:%S') | |||
return now_datetime().strftime('%Y-%m-%d %H:%M:%S.%f') | |||
def nowdate(): | |||
"""return current date as yyyy-mm-dd""" | |||
@@ -217,7 +217,7 @@ def get_datetime(datetime_str): | |||
if isinstance(datetime_str, datetime): | |||
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): | |||
if isinstance(datetime_obj, basestring): | |||