diff --git a/frappe/__init__.py b/frappe/__init__.py index 6889e6316f..2062004296 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -510,11 +510,10 @@ def make_property_setter(args): def get_application_home_page(user='Guest'): """get home page for user""" - roles = get_roles(user) hpl = db.sql("""select home_page from `tabDefault Home Page` where parent='Control Panel' - and role in (%s) order by idx asc limit 1""" % ", ".join(['%s']*len(roles)), roles) + and role in ('%s') order by idx asc limit 1""" % "', '".join(get_roles(user))) if hpl: return hpl[0][0] else: diff --git a/frappe/database.py b/frappe/database.py index 6ce6db3dcf..749dfee446 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -325,10 +325,8 @@ class Database: def get_values_from_single(self, fields, filters, doctype, as_dict=False, debug=False): if fields=="*" or isinstance(filters, dict): - r = self.sql("""select field, value from tabSingles where doctype=%s""", doctype) - # check if single doc matches with filters - values = frappe._dict(r) + values = self.get_singles_dict(doctype) if isinstance(filters, dict): for key, value in filters.items(): if values.get(key) != value: @@ -351,6 +349,11 @@ class Database: else: return r and [[i[1] for i in r]] or [] + def get_singles_dict(self, doctype): + return frappe._dict(self.sql("""select field, value from + tabSingles where doctype=%s""", doctype)) + + def get_values_from_table(self, fields, filters, doctype, as_dict, debug): fl = [] if isinstance(fields, (list, tuple)): diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index cc7de99745..1c41ddd77d 100644 --- a/frappe/model/db_schema.py +++ b/frappe/model/db_schema.py @@ -13,25 +13,25 @@ import frappe from frappe.utils import cstr type_map = { - 'currency': ('decimal', '18,6') - ,'int': ('int', '11') - ,'float': ('decimal', '18,6') - ,'percent': ('decimal', '18,6') - ,'check': ('int', '1') - ,'small text': ('text', '') - ,'long text': ('longtext', '') - ,'code': ('text', '') - ,'text editor': ('text', '') - ,'date': ('date', '') - ,'datetime': ('datetime', '') - ,'time': ('time', '') - ,'text': ('text', '') - ,'data': ('varchar', '255') - ,'link': ('varchar', '255') - ,'password': ('varchar', '255') - ,'select': ('varchar', '255') - ,'read only': ('varchar', '255') - ,'attach': ('varchar', '255') + 'Currency': ('decimal', '18,6') + ,'Int': ('int', '11') + ,'Float': ('decimal', '18,6') + ,'Percent': ('decimal', '18,6') + ,'Check': ('int', '1') + ,'Small Text': ('text', '') + ,'Long Text': ('longtext', '') + ,'Code': ('text', '') + ,'Text Editor': ('text', '') + ,'Date': ('date', '') + ,'Datetime': ('datetime', '') + ,'Time': ('time', '') + ,'Text': ('text', '') + ,'Data': ('varchar', '255') + ,'Link': ('varchar', '255') + ,'Password': ('varchar', '255') + ,'Select': ('varchar', '255') + ,'Read Only': ('varchar', '255') + ,'Attach': ('varchar', '255') } default_columns = ['name', 'creation', 'modified', 'modified_by', 'owner', 'docstatus', 'parent',\ @@ -130,7 +130,7 @@ class DbTable: def get_index_definitions(self): ret = [] for k in self.columns.keys(): - if type_map.get(self.columns[k].fieldtype) and type_map.get(self.columns[k].fieldtype.lower())[0] not in ('text', 'blob'): + if type_map.get(self.columns[k].fieldtype) and type_map.get(self.columns[k].fieldtype)[0] not in ('text', 'blob'): ret.append('index `' + k + '`(`' + k + '`)') return ret @@ -422,7 +422,7 @@ def remove_all_foreign_keys(): frappe.db.sql("alter table `tab%s` drop foreign key `%s`" % (t[0], f[1])) def get_definition(fieldtype): - d = type_map.get(fieldtype.lower()) + d = type_map.get(fieldtype) if not d: return diff --git a/frappe/model/doc.py b/frappe/model/doc.py index 5e14fa376f..144e3b6700 100755 --- a/frappe/model/doc.py +++ b/frappe/model/doc.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals Contains the Document class representing an object / record """ -_toc = ["frappe.model.doc.Document"] - import frappe from frappe import _ import frappe.model.meta diff --git a/frappe/model/document.py b/frappe/model/document.py new file mode 100644 index 0000000000..5b3269374b --- /dev/null +++ b/frappe/model/document.py @@ -0,0 +1,180 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import cint, flt +from frappe.model import default_fields +from frappe.model.db_schema import type_map +from frappe.model.naming import set_new_name + +# validation - links, mandatory +# once_only validation +# permissions +# methods +# timestamps and docstatus +# defaults (insert) + +class BaseDocument(object): + def __init__(self, d): + self.update(d) + + def __getattr__(self, key): + if self.__dict__.has_key(key): + return self.__dict__[key] + if key != "_table_columns" and key in self.get_table_columns(): + return None + raise AttributeError, key + + 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, default=None): + if key: + return self.__dict__.get(key, default) + else: + return self.__dict__ + + def set(self, key, value): + if isinstance(value, list): + for v in value: + self.set(key, v) + return + else: + if isinstance(value, dict): + # appending + if not self.get(key): + self.__dict__[key] = [] + self.get(key).append(self._init_child(value, key)) + return + + self.__dict__[key] = value + + def _init_child(self, value, key): + if not self.doctype: + return value + if not isinstance(value, BaseDocument): + if not value.get("doctype"): + value["doctype"] = self.meta.get({"fieldname":key})[0].options + if not value.get("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)) + + return value + + + @property + def meta(self): + if not self.get("_meta"): + self._meta = frappe.get_doctype(self.doctype) + return self._meta + + def get_valid_dict(self): + d = {} + for fieldname in self.table_columns: + d[fieldname] = self.get(fieldname) + return d + + @property + def table_columns(self): + return self.get_table_columns() + + def get_table_columns(self): + if not hasattr(self, "_table_columns"): + doctype = self.__dict__.get("doctype") + self._table_columns = default_fields[1:] + \ + [df.fieldname for df in frappe.get_doctype(doctype).get_docfields() + if df.fieldtype in type_map] + + return self._table_columns + + def insert_row(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()) + + def fix_numeric_types(self): + for df in self.meta.get_docfields(): + 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) + + +class Document(BaseDocument): + def __init__(self, arg1, arg2=None): + self.doctype = self.name = None + if isinstance(arg1, basestring) and not arg2: + # single + self.doctype = self.name = arg1 + if arg1 and isinstance(arg1, basestring) and arg2: + self.doctype = arg1 + if isinstance(arg2, dict): + # filter + self.name = frappe.db.get_value(arg1, arg2, "name") + if self.name is None: + raise frappe.DoesNotExistError + else: + self.name = arg2 + + self.load_from_db() + elif isinstance(arg1, dict): + super(Document, self).__init__(arg1) + + def load_from_db(self): + if self.meta[0].issingle: + self.update(frappe.db.get_singles_dict(self.doctype)) + self.fix_numeric_types() + else: + d = frappe.db.get_value(self.doctype, self.name, "*", as_dict=1) + for df in self.meta.get({"doctype":"DocField", "fieldtype":"Table"}): + d[df.fieldname] = frappe.db.get_values(df.options, + {"parent": self.name}, "*", as_dict=True) + self.update(d) + + def insert(self): + # check links + # check permissions + + # parent + if self.meta[0].issingle: + self.update_single(self.get_valid_dict()) + else: + self.insert_row() + + # children + for df in self.meta.get({"fieldtype":"Table"}): + value = self.get(df.fieldname) + if isinstance(value, list): + for d in value: + d.parent = self.name + print d.__dict__ + d.insert_row() + + def update_single(self, d): + frappe.db.sql("""delete from tabSingles where doctype=%s""", d.get("doctype")) + for field, value in d.iteritems(): + if field not in ("doctype"): + frappe.db.sql("""insert into tabSingles(doctype, field, value) + values (%s, %s, %s)""", (d.get("doctype", field, value))) + + + diff --git a/frappe/model/naming.py b/frappe/model/naming.py new file mode 100644 index 0000000000..7d7f6a88be --- /dev/null +++ b/frappe/model/naming.py @@ -0,0 +1,150 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +from frappe.utils import now_datetime, cint + +def set_new_name(doc): + if getattr(doc, "_new_name_set", False): + # already set by bean + return + + doc._new_name_set = True + autoname = frappe.get_doctype(doc.doctype)[0].autoname + doc.localname = doc.name # for passing back to client + + # amendments + if getattr(doc, "amended_from", None): + return doc._get_amended_name() + else: + if hasattr(doc, "autoname"): + doc.autoname() + if doc.name and doc.localname != doc.name: + return + + # based on a field + if autoname and autoname.startswith('field:'): + n = doc.get(autoname[6:]) + if not n: + raise Exception, 'Name is required' + doc.name = n.strip() + + elif autoname and autoname.startswith("naming_series:"): + if not doc.naming_series: + doc.naming_series = get_default_naming_series(doc.doctype) + + if not doc.naming_series: + frappe.msgprint(frappe._("Naming Series mandatory"), raise_exception=True) + doc.name = make_autoname(doc.naming_series+'.#####') + + # call the method! + elif autoname and autoname!='Prompt': + doc.name = make_autoname(autoname, doc.doctype) + + # given + elif doc.fields.get('__newname',''): + doc.name = doc.fields['__newname'] + + # default name for table + elif doc.meta.istable: + doc.name = make_autoname('#########', doc.doctype) + + # unable to determine a name, use global series + if not doc.name: + doc.name = make_autoname('#########', doc.doctype) + + validate_name(doc.doctype, doc.name) + +def make_autoname(key, doctype=''): + """ + Creates an autoname from the given key: + + **Autoname rules:** + + * The key is separated by '.' + * '####' represents a series. The string before this part becomes the prefix: + Example: ABC.#### creates a series ABC0001, ABC0002 etc + * 'MM' represents the current month + * 'YY' and 'YYYY' represent the current year + + + *Example:* + + * DE/./.YY./.MM./.##### will create a series like + DE/09/01/0001 where 09 is the year, 01 is the month and 0001 is the series + """ + if key=="hash": + return frappe.generate_hash(doctype) + + if not "#" in key: + key = key + ".#####" + + n = '' + l = key.split('.') + series_set = False + today = now_datetime() + + for e in l: + en = '' + if e.startswith('#'): + if not series_set: + digits = len(e) + en = getseries(n, digits, doctype) + series_set = True + elif e=='YY': + en = today.strftime('%y') + elif e=='MM': + en = today.strftime('%m') + elif e=='DD': + en = today.strftime("%d") + elif e=='YYYY': + en = today.strftime('%Y') + else: en = e + n+=en + return n + +def getseries(key, digits, doctype=''): + # series created ? + current = frappe.db.sql("select `current` from `tabSeries` where name=%s for update", (key,)) + if current and current[0][0] is not None: + current = current[0][0] + # yes, update it + frappe.db.sql("update tabSeries set current = current+1 where name=%s", (key,)) + current = cint(current) + 1 + else: + # no, create it + frappe.db.sql("insert into tabSeries (name, current) values (%s, 1)", (key,)) + current = 1 + return ('%0'+str(digits)+'d') % current + +def get_default_naming_series(doctype): + """get default value for `naming_series` property""" + from frappe.model.doctype import get_property + naming_series = get_property(doctype, "options", "naming_series") + if naming_series: + naming_series = naming_series.split("\n") + return naming_series[0] or naming_series[1] + else: + return None + +def validate_name(doctype, name, case=None, merge=False): + if not name: return 'No Name Specified for %s' % doctype + if name.startswith('New '+doctype): + raise NameError, 'There were some errors setting the name, please contact the administrator' + if case=='Title Case': name = name.title() + if case=='UPPER CASE': name = name.upper() + name = name.strip() + return name + +def _get_amended_name(doc): + am_id = 1 + am_prefix = doc.amended_from + if frappe.db.get_value(doc.doctype, doc.amended_from, "amended_from"): + am_id = cint(doc.amended_from.split('-')[-1]) + 1 + am_prefix = '-'.join(doc.amended_from.split('-')[:-1]) # except the last hyphen + + doc.name = am_prefix + '-' + str(am_id) + return doc.name + diff --git a/frappe/tests/test_document.py b/frappe/tests/test_document.py new file mode 100644 index 0000000000..2176637ec3 --- /dev/null +++ b/frappe/tests/test_document.py @@ -0,0 +1,54 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +import frappe, unittest + +from frappe.model.document import Document + +class TestDocument(unittest.TestCase): + def test_load(self): + d = Document("DocType", "User") + self.assertEquals(d.doctype, "DocType") + self.assertEquals(d.name, "User") + self.assertEquals(d.allow_rename, 1) + self.assertTrue(isinstance(d.fields, list)) + self.assertTrue(isinstance(d.permissions, list)) + self.assertTrue(filter(lambda d: d.fieldname=="email", d.fields)) + + def test_load_single(self): + d = Document("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({ + "doctype":"Event", + "subject":"_Test Event 1", + "starts_on": "2014-01-01", + "event_type": "Public" + }) + d.insert() + self.assertTrue(d.name.startswith("EV")) + self.assertEquals(frappe.db.get_value("Event", d.name, "subject"), + "_Test Event 1") + + def test_insert_with_child(self): + d = Document({ + "doctype":"Event", + "subject":"_Test Event 2", + "starts_on": "2014-01-01", + "event_type": "Public", + "event_individuals": [ + { + "person": "Administrator" + } + ] + }) + d.insert() + self.assertTrue(d.name.startswith("EV")) + self.assertEquals(frappe.db.get_value("Event", d.name, "subject"), + "_Test Event 2") + + d1 = Document("Event", d.name) + self.assertTrue(d1.event_individuals[0].person, "Administrator")