# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt # metadata from __future__ import unicode_literals import frappe, json from frappe.utils import cstr, cint from frappe.model import integer_docfield_properties, default_fields from frappe.model.document import Document from frappe.model.base_document import BaseDocument from frappe.model.db_schema import type_map ###### def get_meta(doctype, cached=True): if cached: return frappe.cache().get_value("meta:" + doctype, lambda: Meta(doctype)) else: return Meta(doctype) def get_table_columns(doctype): return frappe.cache().get_value("table_columns:" + doctype, lambda: frappe.db.get_table_columns(doctype)) def load_doctype_from_file(doctype): fname = frappe.scrub(doctype) with open(frappe.get_app_path("frappe", "core", "doctype", fname, fname + ".json"), "r") as f: txt = json.loads(f.read()) for d in txt.get("fields", []): d["doctype"] = "DocField" for d in txt.get("permissions", []): d["doctype"] = "DocPerm" txt["fields"] = [BaseDocument(d) for d in txt["fields"]] if "permissions" in txt: txt["permissions"] = [BaseDocument(d) for d in txt["permissions"]] return txt class Meta(Document): _metaclass = True default_fields = default_fields[1:] special_doctypes = ("DocField", "DocPerm", "Role", "DocType", "Module Def") def __init__(self, doctype): self._fields = {} super(Meta, self).__init__("DocType", doctype) self.process() def load_from_db(self): try: super(Meta, self).load_from_db() except frappe.DoesNotExistError: if self.doctype=="DocType" and self.name in self.special_doctypes: self.__dict__.update(load_doctype_from_file(self.name)) else: raise def get_link_fields(self): return self.get("fields", {"fieldtype": "Link", "options":["!=", "[Select]"]}) def get_select_fields(self): return self.get("fields", {"fieldtype": "Select", "options":["not in", ["[Select]", "Loading...", "attach_files:"]]}) def get_table_fields(self): if not hasattr(self, "_table_fields"): if self.name!="DocType": self._table_fields = self.get('fields', {"fieldtype":"Table"}) else: self._table_fields = doctype_table_fields return self._table_fields def get_valid_columns(self): if not hasattr(self, "_valid_columns"): if self.name in ("DocType", "DocField", "DocPerm", "Property Setter"): self._valid_columns = get_table_columns(self.name) else: self._valid_columns = self.default_fields + \ [df.fieldname for df in self.get("fields") if df.fieldtype in type_map] return self._valid_columns def get_table_field_doctype(self, fieldname): return { "fields": "DocField", "permissions": "DocPerm"}.get(fieldname) def get_field(self, fieldname): if not fieldname in self._fields: fields = self.get("fields", {"fieldname":fieldname}) self._fields[fieldname] = fields[0] if fields else frappe._dict() return self._fields[fieldname] def get_label(self, fieldname): return self.get_field(fieldname).label def get_options(self, fieldname): return self.get_field(fieldname).options def get_search_fields(self): search_fields = self.search_fields or "name" search_fields = [d.strip() for d in search_fields.split(",")] if "name" not in search_fields: search_fields.append("name") return search_fields def get_list_fields(self): list_fields = ["name"] + [d.fieldname \ for d in self.fields if (d.in_list_view and d.fieldtype in type_map)] if self.title_field and self.title_field not in list_fields: list_fields.append(self.title_field) return list_fields def process(self): # don't process for special doctypes # prevent's circular dependency if self.name in self.special_doctypes: return self.add_custom_fields() self.apply_property_setters() self.sort_fields() def add_custom_fields(self): try: self.extend("fields", frappe.db.sql("""SELECT * FROM `tabCustom Field` WHERE dt = %s AND docstatus < 2""", (self.name,), as_dict=1, update={"is_custom_field": True})) except Exception, e: if e.args[0]==1146: return else: raise def apply_property_setters(self): for ps in frappe.db.sql("""select * from `tabProperty Setter` where doc_type=%s""", (self.name,), as_dict=1): if ps.doctype_or_field=='DocType': if ps.property_type in ('Int', 'Check'): ps.value = cint(ps.value) self.set(ps.property, ps.value) else: docfield = self.get("fields", {"fieldname":ps.field_name}, limit=1) if docfield: docfield = docfield[0] else: continue if ps.property in integer_docfield_properties: ps.value = cint(ps.value) docfield.set(ps.property, ps.value) def sort_fields(self): """sort on basis of previous_field""" newlist = [] pending = self.get("fields") if self.get("_idx"): for fieldname in json.loads(self.get("_idx")): d = self.get("fields", {"fieldname": fieldname}, limit=1) if d: newlist.append(d[0]) pending.remove(d[0]) else: maxloops = 20 while (pending and maxloops>0): maxloops -= 1 for d in pending[:]: if d.get("previous_field"): # field already added for n in newlist: if n.fieldname==d.previous_field: newlist.insert(newlist.index(n)+1, d) pending.remove(d) break else: newlist.append(d) pending.remove(d) # recurring at end if pending: newlist += pending # renum idx = 1 for d in newlist: d.idx = idx idx += 1 self.set("fields", newlist) def get_fields_to_check_permissions(self, user_permission_doctypes): fields = self.get("fields", { "fieldtype":"Link", "parent": self.name, "ignore_user_permissions":("!=", 1), "options":("in", user_permission_doctypes) }) if self.name in user_permission_doctypes: fields.append(frappe._dict({ "label":"Name", "fieldname":"name", "options": self.name })) return fields def is_print_hide(self, fieldname): df = self.get_field(fieldname) return df.get("__print_hide") or df.print_hide doctype_table_fields = [ frappe._dict({"fieldname": "fields", "options": "DocField"}), frappe._dict({"fieldname": "permissions", "options": "DocPerm"}) ] ####### def is_single(doctype): try: return frappe.db.get_value("DocType", doctype, "issingle") except IndexError: raise Exception, 'Cannot determine whether %s is single' % doctype def get_parent_dt(dt): parent_dt = frappe.db.sql("""select parent from tabDocField where fieldtype="Table" and options=%s and (parent not like "old_parent:%%") limit 1""", dt) return parent_dt and parent_dt[0][0] or '' def set_fieldname(field_id, fieldname): frappe.db.set_value('DocField', field_id, 'fieldname', fieldname) def get_field_currency(df, doc): """get currency based on DocField options and fieldvalue in doc""" currency = None if not df.get("options"): return None if ":" in cstr(df.get("options")): split_opts = df.get("options").split(":") if len(split_opts)==3: currency = frappe.db.get_value(split_opts[0], doc.get(split_opts[1]), split_opts[2]) else: currency = doc.get(df.get("options")) return currency def get_field_precision(df, doc): """get precision based on DocField options and fieldvalue in doc""" from frappe.utils import get_number_format_info precision = cint(df.precision) or cint(frappe.db.get_default("float_precision")) or 3 if df.fieldtype == "Currency": number_format = None currency = get_field_currency(df, doc) if not currency: # use default currency currency = frappe.db.get_default("currency") if currency: number_format = frappe.db.get_value("Currency", currency, "number_format") if not number_format: number_format = frappe.db.get_default("number_format") or "#,###.##" decimal_str, comma_str, precision = get_number_format_info(number_format) return precision def clear_cache(doctype=None): def clear_single(dt): frappe.cache().delete_value("meta:" + dt) frappe.cache().delete_value("form_meta:" + dt) frappe.cache().delete_value("table_columns:" + dt) if doctype: clear_single(doctype) # clear all parent doctypes for dt in frappe.db.sql("""select parent from tabDocField where fieldtype="Table" and options=%s""", (doctype,)): clear_single(dt[0]) # clear all notifications from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for delete_notification_count_for(doctype) else: # clear all for dt in frappe.db.sql("""select name from tabDocType"""): clear_single(dt[0]) frappe.cache().delete_value("is_table")