diff --git a/.gitignore b/.gitignore
index 117b892b34..1c6dbb5761 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
*.pyc
*.comp.js
*.DS_Store
+defs.py
diff --git a/cgi-bin/compilejs.py b/cgi-bin/compilejs.py
new file mode 100644
index 0000000000..9c2a8e1af8
--- /dev/null
+++ b/cgi-bin/compilejs.py
@@ -0,0 +1,73 @@
+class wnJSCompiler:
+ @staticmethod
+ def concate_files_in_dir(path,dest):
+ """
+ Concatenates all files in a directory
+ """
+ import os
+ allfiles = []
+ dirname = path
+ l = os.listdir(path)
+ for i in l:
+ if os.path.isfile(os.path.join(dirname,i)):
+ allfiles.append(os.path.join(dirname,i))
+ fout = open(dest,'w')
+ for filename in allfiles:
+ f = open(filename)
+ fout.write(f.read())
+ f.close
+ fout.close
+
+
+ @staticmethod
+ def getsubs(path):
+ """
+ gets all the sub directories of a directory (recursive)
+ """
+ import os
+ subs = []
+ for root, subd, files in os.walk(path):
+ for i in subd:
+ subs.append(os.path.join(root,i))
+ return subs
+ @staticmethod
+ def compilejs(path):
+ """
+ Compiles the js tree for ondemand import
+ """
+ if not wnJSCompiler.is_changed(path):
+ return
+
+ import os
+ import webnotes.utils.jsnamespace as jsn
+ subs = wnJSCompiler.getsubs(path)
+ for subdir in subs:
+ modname = jsn.jsNamespace.getmodname(subdir)
+ wnJSCompiler.concate_files_in_dir(subdir,os.path.join(subdir, modname))
+ wnJSCompiler.minifyjs(os.path.join(subdir, modname))
+
+ @staticmethod
+ def is_changed(path):
+ #compare new timestamps with the ones stored in file
+ from webnotes.utils import jstimestamp
+ try:
+ frm_file = jstimestamp.generateTimestamp.read_ts_from_file(path)
+ newts = jstimestamp.generateTimestamp.gents(path)
+ except IOError:
+ return True
+ if frm_file == newts:
+ return False
+ else:
+ return True
+
+
+ @staticmethod
+ def minifyjs(modpath):
+ """
+ Stub to minify js
+ """
+ pass
+
+if __name__=="__main__":
+ a = wnJSCompiler()
+ print a.compilejs('../js/wn')
diff --git a/cgi-bin/get_module_js.cgi b/cgi-bin/get_module_js.cgi
new file mode 100755
index 0000000000..2845026485
--- /dev/null
+++ b/cgi-bin/get_module_js.cgi
@@ -0,0 +1,98 @@
+#!/usr/bin/python
+
+import cgi
+import datetime
+import os
+
+try:
+
+ form = cgi.FieldStorage()
+ out = ''
+ out_buf, str_out = '', ''
+ jsdir='../js'
+ jsonout= {}
+
+ # Traceback
+ # ---------
+ def getTraceback():
+ import sys, traceback, string
+ type, value, tb = sys.exc_info()
+ body = "Traceback (innermost last):\n"
+ list = traceback.format_tb(tb, None) \
+ + traceback.format_exception_only(type, value)
+ body = body + "%-20s %s" % (string.join(list[:-1], ""), list[-1])
+ return body
+
+ def load_js_from_file(module_name):
+ global out
+ global jsonout
+ import webnotes.utils.jsnamespace as jsn
+ filename = jsn.jsNamespace.modname_to_filename(module_name,jsdir)
+ import os
+ try:
+ f = open(os.path.join(filename))
+ try:
+ out = f.read()
+ finally:
+ f.close()
+ except IOError,e:
+ out = "Not Found: %s" % filename
+ jsonout[module_name]=out
+
+ def load_js_module(module_name):
+ global jsonout
+ from webnotes import defs
+ devmode = getattr(defs,'developer_mode')
+ if devmode:
+ import compilejs
+ compilejs.wnJSCompiler.compilejs(jsdir)
+ if module_name not in jsonout:
+ dependent_mods = get_dependencies(module_name)
+ for module in dependent_mods:
+ load_js_from_file(module)
+ load_js_from_file(module_name)
+
+ def get_dependencies(module_name):
+ import webnotes.utils.jsdependency as jsd
+ ret = jsd.jsDependencyBuilder.build_dependency(jsdir,module_name)
+ return ret
+
+
+ def compress_string(buf):
+ import gzip, cStringIO
+ zbuf = cStringIO.StringIO()
+ zfile = gzip.GzipFile(mode = 'wb', fileobj = zbuf, compresslevel = 5)
+ zfile.write(buf)
+ zfile.close()
+ return zbuf.getvalue()
+
+ compress = 0
+ try:
+ if string.find(os.environ["HTTP_ACCEPT_ENCODING"], "gzip") != -1:
+ compress = 1
+ except:
+ pass
+
+ load_js_module(form.getvalue('module'))
+ #load_js_module('wn.modules')
+
+ if compress and len(out)>512:
+ out_buf = compress_string(str_out)
+ print "Content-Encoding: gzip"
+ print "Content-Length: %d" % (len(out_buf))
+
+ print "Content-Type: text/javascript"
+
+ # Headers end
+ print
+
+ if out_buf:
+ sys.stdout.write(out_buf)
+ elif out:
+ import json
+ print json.dumps(jsonout)
+
+except Exception, e:
+ print "Content-Type: text/javascript"
+ print
+ print getTraceback()#.replace('\n',' ')
diff --git a/cgi-bin/getjsfile.cgi b/cgi-bin/getjsfile.cgi
index 18a876cead..0e1594fc04 100755
--- a/cgi-bin/getjsfile.cgi
+++ b/cgi-bin/getjsfile.cgi
@@ -70,4 +70,3 @@ except Exception, e:
print "Content-Type: text/javascript"
print
print getTraceback().replace('\n',' ')
-
\ No newline at end of file
diff --git a/cgi-bin/webnotes/__init__.py b/cgi-bin/webnotes/__init__.py
index 8607425ffb..f96c91dbf1 100644
--- a/cgi-bin/webnotes/__init__.py
+++ b/cgi-bin/webnotes/__init__.py
@@ -125,6 +125,9 @@ def get_files_path():
return os.path.join(get_index_path(), 'user_files', conn.cur_db_name)
def create_folder(path):
+ """
+ Wrapper function for os.makedirs (does not throw exception if directory exists)
+ """
import os
try:
diff --git a/cgi-bin/webnotes/auth.py b/cgi-bin/webnotes/auth.py
index c83118ce80..a09e5237e0 100644
--- a/cgi-bin/webnotes/auth.py
+++ b/cgi-bin/webnotes/auth.py
@@ -170,7 +170,7 @@ class LoginManager:
# --------
def run_trigger(self, method='on_login'):
try:
- import event_handlers
+ from startup import event_handlers
if hasattr(event_handlers, method):
getattr(event_handlers, method)(self)
return
diff --git a/cgi-bin/webnotes/handler.py b/cgi-bin/webnotes/handler.py
index 933784ebad..760a96e8e4 100755
--- a/cgi-bin/webnotes/handler.py
+++ b/cgi-bin/webnotes/handler.py
@@ -26,9 +26,9 @@ def startup():
webnotes.response.update(webnotes.session_cache.get())
def cleanup_docs():
- import webnotes.model.doclist
+ import webnotes.model.utils
if webnotes.response.get('docs') and type(webnotes.response['docs'])!=dict:
- webnotes.response['docs'] = webnotes.model.doclist.compress(webnotes.response['docs'])
+ webnotes.response['docs'] = webnotes.model.utils.compress(webnotes.response['docs'])
# server calls
# ------------------------------------------------------------------------------------
@@ -45,22 +45,22 @@ def logout():
def dt_map():
import webnotes
- import webnotes.model.doclist
+ import webnotes.model.utils
from webnotes.model.code import get_obj
from webnotes.model.doc import Document
form_dict = webnotes.form_dict
- dt_list = webnotes.model.doclist.expand(form_dict.get('docs'))
+ dt_list = webnotes.model.utils.expand(form_dict.get('docs'))
from_doctype = form_dict.get('from_doctype')
to_doctype = form_dict.get('to_doctype')
from_docname = form_dict.get('from_docname')
from_to_list = form_dict.get('from_to_list')
dm = get_obj('DocType Mapper', from_doctype +'-' + to_doctype)
- doclist = dm.dt_map(from_doctype, to_doctype, from_docname, Document(fielddata = dt_list[0]), [], from_to_list)
+ dl = dm.dt_map(from_doctype, to_doctype, from_docname, Document(fielddata = dt_list[0]), [], from_to_list)
- webnotes.response['docs'] = doclist
+ webnotes.response['docs'] = dl
# Load Month Events
# ------------------------------------------------------------------------------------
diff --git a/cgi-bin/webnotes/model/code.py b/cgi-bin/webnotes/model/code.py
index dcebc823b9..dc0fe48146 100644
--- a/cgi-bin/webnotes/model/code.py
+++ b/cgi-bin/webnotes/model/code.py
@@ -18,7 +18,7 @@ import webnotes
from webnotes.utils import add_days, add_months, add_years, cint, cstr, date_diff, default_fields, flt, fmt_money, formatdate, generate_hash, getTraceback, get_defaults, get_first_day, get_last_day, getdate, has_common, month_name, now, nowdate, replace_newlines, sendmail, set_default, str_esc_quote, user_format, validate_email_add
from webnotes.model import db_exists
from webnotes.model.doc import Document, addchild, removechild, getchildren, make_autoname, SuperDocType
-from webnotes.model.doclist import getlist, copy_doclist
+from webnotes.model.utils import getlist
from webnotes.model.code import get_obj, get_server_obj, run_server_obj, updatedb, check_syntax
from webnotes import session, form, is_testing, msgprint, errprint
@@ -48,9 +48,9 @@ def execute(code, doc=None, doclist=[]):
# --------------------------------------------------
from webnotes.utils import add_days, add_months, add_years, cint, cstr, date_diff, default_fields, flt, fmt_money, formatdate, generate_hash, getTraceback, get_defaults, get_first_day, get_last_day, getdate, has_common, month_name, now, nowdate, replace_newlines, sendmail, set_default, str_esc_quote, user_format, validate_email_add
from webnotes.model import db_exists
- from webnotes.model.doc import Document, addchild, removechild, getchildren, make_autoname, SuperDocType
- from webnotes.model.doclist import getlist, copy_doclist
- from webnotes import session, form, is_testing, msgprint, errprint
+ from webnotes.model.doc import Document, addchild, removechild, getchildren
+ from webnotes.model.utils import getlist
+ from webnotes import session, form, msgprint, errprint
import webnotes
@@ -59,6 +59,7 @@ def execute(code, doc=None, doclist=[]):
get_value = webnotes.conn.get_value
in_transaction = webnotes.conn.in_transaction
convert_to_lists = webnotes.conn.convert_to_lists
+
if webnotes.user:
get_roles = webnotes.user.get_roles
locals().update({'get_obj':get_obj, 'get_server_obj':get_server_obj, 'run_server_obj':run_server_obj, 'updatedb':updatedb, 'check_syntax':check_syntax})
diff --git a/cgi-bin/webnotes/model/doc.py b/cgi-bin/webnotes/model/doc.py
index bb15ebb92f..a3fcbfd0d7 100755
--- a/cgi-bin/webnotes/model/doc.py
+++ b/cgi-bin/webnotes/model/doc.py
@@ -236,9 +236,6 @@ class Document:
# ---------------------------------------------------------------------------
def _makenew(self, autoname, istable, case='', make_autoname=1):
- # set owner
- if not self.owner: self.owner = webnotes.session['user']
-
# set name
if make_autoname:
self._set_name(autoname, istable)
@@ -247,7 +244,10 @@ class Document:
self._validate_name(case)
# insert!
- webnotes.conn.sql("""insert into `tab%s` (name, owner, creation, modified, modified_by) values ('%s', '%s', '%s', '%s', '%s')""" % (self.doctype, self.name, webnotes.session['user'], now(), now(), webnotes.session['user']))
+ self.owner = self.modified_by = webnotes.session['user']
+ self.creation = self.modified = now()
+ webnotes.conn.sql("""insert into `tab%(doctype)s` (name, owner, creation, modified, modified_by)
+ values ('%(name)s', '%(owner)s', '%(creation)s', '%(modified)s', '%(modified_by)s')""" % self.fields)
# Update Values
@@ -446,9 +446,9 @@ class Document:
"""
Clears the child records from the given `doclist` for a particular `tablefield`
"""
- import webnotes.model.doclist
+ from webnotes.model.utils import getlist
- for d in webnotes.model.doclist.getlist(doclist, tablefield):
+ for d in getlist(doclist, tablefield):
d.fields['__oldparent'] = d.parent
d.parent = 'old_parent:' + d.parent # for client to send it back while saving
d.docstatus = 2
diff --git a/cgi-bin/webnotes/model/doclist.py b/cgi-bin/webnotes/model/doclist.py
index aff1ebbc6c..64f5bb06d0 100644
--- a/cgi-bin/webnotes/model/doclist.py
+++ b/cgi-bin/webnotes/model/doclist.py
@@ -1,191 +1,237 @@
+"""
+Transactions are defined as collection of classes, a DocList represents collection of Document
+objects for a transaction with main and children.
+
+Group actions like save, etc are performed on doclists
+"""
+
import webnotes
-import webnotes.model
-import webnotes.model.doc
+from webnotes.utils import cint
-def xzip(a,b):
- d = {}
- for i in range(len(a)):
- d[a[i]] = b[i]
- return d
+class DocList:
+ """
+ Collection of Documents with one parent and multiple children
+ """
+ def __init__(self, dt=None, dn=None):
+ self.docs = []
+ self.obj = None
+ self.to_docstatus = 0
-def expand(docs):
- """
- Expand a doclist sent from the client side. (Internally used by the request handler)
- """
- from webnotes.utils import load_json
-
- docs = load_json(docs)
- clist = []
- for d in docs['_vl']:
- doc = xzip(docs['_kl'][d[0]], d);
- clist.append(doc)
- return clist
-
-def compress(doclist):
- """
- Compress a doclist before sending it to the client side. (Internally used by the request handler)
-
- """
- if doclist and hasattr(doclist[0],'fields'):
- docs = [d.fields for d in doclist]
- else:
- docs = doclist
+
+ def __iter__(self):
+ """
+ Make this iterable
+ """
+ return self.docs.__iter__()
+
+ def from_compressed(self, data, docname):
+ """
+ Expand called from client
+ """
+ from webnotes.model.utils import expand
+ self.docs = expand(data)
+ self.objectify(docname)
+
+ def objectify(self, docname=None):
+ """
+ Converts self.docs from a list of dicts to list of Documents
+ """
+ from webnotes.model.doc import Document
- kl, vl = {}, []
- for d in docs:
- dt = d['doctype']
- if not (dt in kl.keys()):
- fl = d.keys()
- forbidden = ['server_code_compiled']
- nl = ['doctype','localname','__oldparent','__unsaved']
-
- # add client script for doctype, doctype due to ambiguity
- if dt=='DocType': nl.append('__client_script')
-
- for f in fl:
- if not (f in nl) and not (f in forbidden):
- nl.append(f)
- kl[dt] = nl
+ self.docs = [Document(fielddata=d) for d in self.docs]
+ if not docname:
+ self.doc, self.children = self.docs[0], self.docs[1:]
- ## values
- fl = kl[dt]
- nl = []
- for f in fl:
- v = d.get(f)
-
- if type(v)==long:
- v=int(v)
- nl.append(v)
- vl.append(nl)
- #errprint(str({'_vl':vl,'_kl':kl}))
- return {'_vl':vl,'_kl':kl}
-
-# Get Children List (for scripts utility)
-# ---------------------------------------
-
-def getlist(doclist, field):
- """
- Filter a list of records for a specific field from the full doclist
-
- Example::
-
- # find all phone call details
- dl = getlist(self.doclist, 'contact_updates')
- pl = []
- for d in dl:
- if d.type=='Phone':
- pl.append(d)
- """
+ else:
+ self.children = []
+ for d in self.docs:
+ if d.name == docname:
+ self.doc = d
+ else:
+ self.children.append(d)
- l = []
- for d in doclist:
- if d.parent and (not d.parent.lower().startswith('old_parent:')) and d.parentfield == field:
- l.append(d)
- return l
+ def make_obj(self):
+ """
+ Create a DocType object
+ """
+ if self.obj: return self.obj
+
+ from webnotes.model.code import get_obj
+ self.obj = get_obj(doc=self.doc, doclist=self.children)
+ return self.obj
+
+ def next(self):
+ """
+ Next doc
+ """
+ return self.docs.next()
-# Copy doclist
-# ------------
+ def to_dict(self):
+ """
+ return as a list of dictionaries
+ """
+ return [d.fields for d in self.docs]
+ def check_if_latest(self):
+ """
+ Raises exception if the modified time is not the same as in the database
+ """
+ from webnotes.model.meta import is_single
+
+ if (not is_single(self.doc.doctype)) and (not self.doc.fields.get('__islocal')):
+ tmp = webnotes.conn.sql("""
+ SELECT modified FROM `tab%s` WHERE name="%s" for update"""
+ % (self.doc.doctype, self.doc.name))
+
+ if tmp and str(tmp[0][0]) != str(self.doc.modified):
+ webnotes.msgprint("""
+ Document has been modified after you have opened it.
+ To maintain the integrity of the data, you will not be able to save your changes.
+ Please refresh this document. [%s/%s]""" % (tmp[0][0], self.doc.modified), raise_exception=1)
+
+ def check_permission(self):
+ """
+ Raises exception if permission is not valid
+ """
+ if not self.doc.check_perm(verbose=1):
+ webnotes.msgprint("Not enough permission to save %s" % self.doc.doctype, raise_exception=1)
+
+ def check_links(self):
+ """
+ Checks integrity of links (throws exception if links are invalid)
+ """
+ ref, err_list = {}, []
+ for d in self.docs:
+ if not ref.get(d.doctype):
+ ref[d.doctype] = d.make_link_list()
+
+ err_list += d.validate_links(ref[d.doctype])
+
+ if err_list:
+ webnotes.msgprint("""[Link Validation] Could not find the following values: %s.
+ Please correct and resave. Document Not Saved.""" % ', '.join(err_list), raise_exception=1)
+
+ def update_timestamps(self):
+ """
+ Update owner, creation, modified_by, modified, docstatus
+ """
+ from webnotes.utils import now
+ ts = now()
+ user = webnotes.__dict__.get('session', {}).get('user') or 'Administrator'
+
+ for d in self.docs:
+ if self.doc.__islocal:
+ d.owner = user
+ d.creation = ts
+
+ d.modified_by = user
+ d.modified = ts
+ d.docstatus = self.to_docstatus
+
+ def prepare_for_save(self, check_links):
+ """
+ Set owner, modified etc before saving
+ """
+ self.check_if_latest()
+ self.check_permission()
+ if check_links:
+ self.check_links()
+ self.update_timestamps()
+
+ def run_method(self, method):
+ """
+ Run a method and custom_method
+ """
+ self.make_obj()
+ if hasattr(self.obj, method):
+ getattr(self.obj, method)()
+ if hasattr(self.obj, 'custom_' + method):
+ getattr(self.obj, 'custom_' + method)()
+
+ from webnotes.model.triggers import fire_event
+ fire_event(self.doc, method)
+
+ def save_main(self):
+ """
+ Save the main doc
+ """
+ try:
+ self.doc.save(cint(self.doc.__islocal))
+ except NameError, e:
+ webnotes.msgprint('%s "%s" already exists' % (doc.doctype, doc.name))
+
+ # prompt if cancelled
+ if webnotes.conn.get_value(doc.doctype, doc.name, 'docstatus')==2:
+ webnotes.msgprint('[%s "%s" has been cancelled]' % (doc.doctype, doc.name))
+ webnotes.errprint(webnotes.utils.getTraceback())
+ raise e
+
+ def save_children(self):
+ """
+ Save Children, with the new parent name
+ """
+ for d in self.children:
+ deleted, local = d.fields.get('__deleted',0), d.fields.get('__islocal',0)
+
+ if cint(local) and cint(deleted):
+ pass
+
+ elif d.fields.has_key('parent'):
+ if d.parent and (not d.parent.startswith('old_parent:')):
+ d.parent = self.doc.name # rename if reqd
+ d.parenttype = self.doc.doctype
+
+ d.save(new = cint(local))
+
+ def save(self, check_links=1):
+ """
+ Save the list
+ """
+ self.prepare_for_save(check_links)
+ self.run_method('validate')
+ self.save_main()
+ self.save_children()
+ self.run_method('on_update')
+
+ def submit(self):
+ """
+ Save & Submit - set docstatus = 1, run "on_submit"
+ """
+ self.to_docstatus = 1
+ self.save()
+ self.run_method('on_submit')
+
+ def cancel(self):
+ """
+ Cancel - set docstatus 2, run "on_cancel"
+ """
+ self.to_docstatus = 2
+ self.save_main()
+ self.save_children()
+ self.run_method('on_cancel')
+
+ def update_after_submit(self):
+ """
+ Update after submit - some values changed after submit
+ """
+ self.to_docstatus = 1
+ self.save_main()
+ self.save_children()
+ self.run_method('on_update_after_submit')
+
+
+# for bc
+def getlist(doclist, parentfield):
+ """
+ Return child records of a particular type
+ """
+ import webnotes.model.utils
+ return webnotes.model.utils.getlist(doclist, parentfield)
+
def copy_doclist(doclist, no_copy = []):
"""
- Save & return a copy of the given doclist
- Pass fields that are not to be copied in `no_copy`
+ Make a copy of the doclist
"""
- from webnotes.model.doc import Document
-
- cl = []
-
- # main doc
- c = Document(fielddata = doclist[0].fields.copy())
-
- # clear no_copy fields
- for f in no_copy:
- if c.fields.has_key(f):
- c.fields[f] = None
-
- c.name = None
- c.save(1)
- cl.append(c)
-
- # new parent name
- parent = c.name
-
- # children
- for d in doclist[1:]:
- c = Document(fielddata = d.fields.copy())
- c.name = None
-
- # clear no_copy fields
- for f in no_copy:
- if c.fields.has_key(f):
- c.fields[f] = None
-
- c.parent = parent
- c.save(1)
- cl.append(c)
-
- return cl
-
-# Validate Multiple Links
-# -----------------------
-
-def validate_links_doclist(doclist):
- """
- Validate link fields and return link fields that are not correct.
- Calls the `validate_links` function on the Document object
- """
- ref, err_list = {}, []
- for d in doclist:
- if not ref.get(d.doctype):
- ref[d.doctype] = d.make_link_list()
-
- err_list += d.validate_links(ref[d.doctype])
- return ', '.join(err_list)
-
-# Get list of field values
-# ------------------------
-
-def getvaluelist(doclist, fieldname):
- """
- Returns a list of values of a particualr fieldname from all Document object in a doclist
- """
- l = []
- for d in doclist:
- l.append(d.fields[fieldname])
- return l
-
-def _make_html(doc, link_list):
-
- from webnotes.utils import cstr
- out = '
'
- for k in doc.fields.keys():
- if k!='server_code_compiled':
- v = cstr(doc.fields[k])
-
- # link field
- if v and (k in link_list.keys()):
- dt = link_list[k]
- if type(dt)==str and dt.startswith('link:'):
- dt = dt[5:]
- v = '%s' % (dt, v, v)
-
- out += '\t
%s
%s
\n' % (cstr(k), v)
-
- out += '
'
- return out
-
-def to_html(doclist):
- """
-Return a simple HTML format of the doclist
- """
- out = ''
- link_lists = {}
-
- for d in doclist:
- if not link_lists.get(d.doctype):
- link_lists[d.doctype] = d.make_link_list()
-
- out += _make_html(d, link_lists[d.doctype])
-
- return out
+ import webnotes.model.utils
+ return webnotes.model.utils.copy_doclist(doclist, no_copy)
diff --git a/cgi-bin/webnotes/model/doctype.py b/cgi-bin/webnotes/model/doctype.py
index ce61b3c8fd..b6badd098a 100644
--- a/cgi-bin/webnotes/model/doctype.py
+++ b/cgi-bin/webnotes/model/doctype.py
@@ -131,15 +131,17 @@ class _DocType:
Returns a dictionary of DocFields by fieldname or label
"""
try:
- doclist = open(file_name, 'r').read()
+ txt = open(file_name, 'r').read()
except:
return
- doclist = eval(doclist)
+ from webnotes.model.utils import peval_doclist
+
+ doclist = peval_doclist(txt)
fields = {}
for d in doclist:
if d['doctype']=='DocField':
- if d['fieldname'] or d['label']:
- fields[d['fieldname'] or d['label']] = d
+ if d.get('fieldname') or d.get('label'):
+ fields[d.get('fieldname') or d.get('label')] = d
return fields
def _update_field_properties(self, doclist):
@@ -179,15 +181,16 @@ class _DocType:
# update the values
for field_to_update in update_fields:
- new_value = fields[key][field_to_update]
+ if field_to_update in fields[key] and fields[key][field_to_update] != d.fields[field_to_update]:
+ new_value = fields[key][field_to_update]
- # in doclist
- d.fields[field_to_update] = new_value
-
- # in database
- webnotes.conn.sql("update tabDocField set `%s` = %s where parent=%s and `%s`=%s" % \
- (field_to_update, '%s', '%s', (d.fieldname and 'fieldname' or 'label'), '%s'), \
- (new_value, doc.name, key))
+ # in doclist
+ d.fields[field_to_update] = new_value
+
+ # in database
+ webnotes.conn.sql("update tabDocField set `%s` = %s where parent=%s and `%s`=%s" % \
+ (field_to_update, '%s', '%s', (d.fieldname and 'fieldname' or 'label'), '%s'), \
+ (new_value, doc.name, key))
webnotes.conn.sql("update tabDocType set _last_update=%s where name=%s", (time_stamp, doc.name))
diff --git a/cgi-bin/webnotes/model/meta.py b/cgi-bin/webnotes/model/meta.py
index f9fa156e33..054a78ee5c 100644
--- a/cgi-bin/webnotes/model/meta.py
+++ b/cgi-bin/webnotes/model/meta.py
@@ -30,7 +30,15 @@ def set_fieldname(field_id, fieldname):
#=================================================================================
def get_link_fields(doctype):
- return webnotes.conn.sql("SELECT fieldname, options, label FROM tabDocField WHERE parent='%s' and (fieldtype='Link' or (fieldtype='Select' and `options` like 'link:%%'))" % (doctype))
+ """
+ Returns list of link fields for a doctype in tuple (fieldname, options, label)
+ """
+ return webnotes.conn.sql("""
+ SELECT fieldname, options, label
+ FROM tabDocField
+ WHERE parent='%s'
+ and (fieldtype='Link' or (fieldtype='Select' and `options` like 'link:%%'))
+ and fieldname!='owner'""" % (doctype))
#=================================================================================
diff --git a/cgi-bin/webnotes/model/utils.py b/cgi-bin/webnotes/model/utils.py
new file mode 100644
index 0000000000..2d019273f1
--- /dev/null
+++ b/cgi-bin/webnotes/model/utils.py
@@ -0,0 +1,272 @@
+"""
+Model utilities, unclassified functions
+"""
+
+def expand(docs):
+ """
+ Expand a doclist sent from the client side. (Internally used by the request handler)
+ """
+ def xzip(a,b):
+ d = {}
+ for i in range(len(a)):
+ d[a[i]] = b[i]
+ return d
+
+ from webnotes.utils import load_json
+
+ docs = load_json(docs)
+ clist = []
+ for d in docs['_vl']:
+ doc = xzip(docs['_kl'][d[0]], d);
+ clist.append(doc)
+ return clist
+
+def compress(doclist):
+ """
+ Compress a doclist before sending it to the client side. (Internally used by the request handler)
+
+ """
+ if doclist and hasattr(doclist[0],'fields'):
+ docs = [d.fields for d in doclist]
+ else:
+ docs = doclist
+
+ kl, vl = {}, []
+ for d in docs:
+ dt = d['doctype']
+ if not (dt in kl.keys()):
+ fl = d.keys()
+ forbidden = ['server_code_compiled']
+ nl = ['doctype','localname','__oldparent','__unsaved']
+
+ # add client script for doctype, doctype due to ambiguity
+ if dt=='DocType': nl.append('__client_script')
+
+ for f in fl:
+ if not (f in nl) and not (f in forbidden):
+ nl.append(f)
+ kl[dt] = nl
+
+ ## values
+ fl = kl[dt]
+ nl = []
+ for f in fl:
+ v = d.get(f)
+
+ if type(v)==long:
+ v=int(v)
+ nl.append(v)
+ vl.append(nl)
+ #errprint(str({'_vl':vl,'_kl':kl}))
+ return {'_vl':vl,'_kl':kl}
+
+
+def getlist(doclist, field):
+ """
+ Filter a list of records for a specific field from the full doclist
+
+ Example::
+
+ # find all phone call details
+ dl = getlist(self.doclist, 'contact_updates')
+ pl = []
+ for d in dl:
+ if d.type=='Phone':
+ pl.append(d)
+ """
+
+ l = []
+ for d in doclist:
+ if d.parent and (not d.parent.lower().startswith('old_parent:')) and d.parentfield == field:
+ l.append(d)
+ return l
+
+# Copy doclist
+# ------------
+
+def copy_doclist(doclist, no_copy = []):
+ """
+ Save & return a copy of the given doclist
+ Pass fields that are not to be copied in `no_copy`
+ """
+ from webnotes.model.doc import Document
+
+ cl = []
+
+ # main doc
+ c = Document(fielddata = doclist[0].fields.copy())
+
+ # clear no_copy fields
+ for f in no_copy:
+ if c.fields.has_key(f):
+ c.fields[f] = None
+
+ c.name = None
+ c.save(1)
+ cl.append(c)
+
+ # new parent name
+ parent = c.name
+
+ # children
+ for d in doclist[1:]:
+ c = Document(fielddata = d.fields.copy())
+ c.name = None
+
+ # clear no_copy fields
+ for f in no_copy:
+ if c.fields.has_key(f):
+ c.fields[f] = None
+
+ c.parent = parent
+ c.save(1)
+ cl.append(c)
+
+ return cl
+
+def getvaluelist(doclist, fieldname):
+ """
+ Returns a list of values of a particualr fieldname from all Document object in a doclist
+ """
+ l = []
+ for d in doclist:
+ l.append(d.fields[fieldname])
+ return l
+
+def _make_html(doc, link_list):
+
+ from webnotes.utils import cstr
+ out = '
'
+ for k in doc.fields.keys():
+ if k!='server_code_compiled':
+ v = cstr(doc.fields[k])
+
+ # link field
+ if v and (k in link_list.keys()):
+ dt = link_list[k]
+ if type(dt)==str and dt.startswith('link:'):
+ dt = dt[5:]
+ v = '%s' % (dt, v, v)
+
+ out += '\t
%s
%s
\n' % (cstr(k), v)
+
+ out += '
'
+ return out
+
+def to_html(doclist):
+ """
+ Return a simple HTML format of the doclist
+ """
+ out = ''
+ link_lists = {}
+
+ for d in doclist:
+ if not link_lists.get(d.doctype):
+ link_lists[d.doctype] = d.make_link_list()
+
+ out += _make_html(d, link_lists[d.doctype])
+
+ return out
+
+def commonify_doclist(doclist, with_comments=1):
+ """
+ Makes a doclist more readable by extracting common properties.
+ This is used for printing Documents in files
+ """
+ from webnotes.utils import get_common_dict, get_diff_dict
+
+ def make_common(doclist):
+ c = {}
+ if with_comments:
+ c['##comment'] = 'These values are common in all dictionaries'
+ for k in common_keys:
+ c[k] = doclist[0][k]
+ return c
+
+ def strip_common(d):
+ for k in common_keys:
+ if k in d: del d[k]
+ return d
+
+ def make_common_dicts(doclist):
+
+ common_dict = {} # one per doctype
+
+ # make common dicts for all records
+ for d in doclist:
+ if not d['doctype'] in common_dict:
+ d1 = d.copy()
+ del d1['name']
+ common_dict[d['doctype']] = d1
+ else:
+ common_dict[d['doctype']] = get_common_dict(common_dict[d['doctype']], d)
+ return common_dict
+
+ common_keys = ['owner','docstatus','creation','modified','modified_by']
+ common_dict = make_common_dicts(doclist)
+
+ # make docs
+ final = []
+ for d in doclist:
+ f = strip_common(get_diff_dict(common_dict[d['doctype']], d))
+ f['doctype'] = d['doctype'] # keep doctype!
+
+ # strip name for child records (only an auto generated number!)
+ if f['doctype'] != doclist[0]['doctype']:
+ del f['name']
+
+ if with_comments:
+ f['##comment'] = d['doctype'] + ('name' in f and (', ' + f['name']) or '')
+ final.append(f)
+
+ # add commons
+ commons = []
+ for d in common_dict.values():
+ d['name']='__common__'
+ if with_comments:
+ d['##comment'] = 'These values are common for all ' + d['doctype']
+ commons.append(strip_common(d))
+
+ common_values = make_common(doclist)
+ return [common_values]+commons+final
+
+def uncommonify_doclist(dl):
+ """
+ Expands an commonified doclist
+ """
+ common_values = dl[0]
+ common_dict = {}
+ final = []
+
+ for d in dl[1:]:
+ if 'name' in d and d['name']=='__common__':
+ del d['name']
+ common_dict[d['doctype']] = d
+ else:
+ d1 = common_values.copy()
+ d1.update(common_dict[d['doctype']])
+ d1.update(d)
+ final.append(d1)
+
+ return final
+
+def pprint_doclist(doclist, with_comments = 1):
+ """
+ Pretty Prints a doclist with common keys separated and comments
+ """
+ from webnotes.utils import pprint_dict
+
+ dictlist =[pprint_dict(d) for d in commonify_doclist(doclist, with_comments)]
+ title = '# '+doclist[0]['doctype']+', '+doclist[0]['name']
+ return title + '\n[\n' + ',\n'.join(dictlist) + '\n]'
+
+def peval_doclist(txt):
+ """
+ Restore a pretty printed doclist
+ """
+ if txt.startswith('#'):
+ return uncommonify_doclist(eval(txt))
+ else:
+ return eval(txt)
+
+ return uncommonify_doclist(eval(txt))
diff --git a/cgi-bin/webnotes/modules/__init__.py b/cgi-bin/webnotes/modules/__init__.py
index 62efefc36a..cbe182e97e 100644
--- a/cgi-bin/webnotes/modules/__init__.py
+++ b/cgi-bin/webnotes/modules/__init__.py
@@ -85,5 +85,3 @@ def switch_module(dt, dn, to, frm=None, export=None):
for ext in ('py','js','html','css'):
os.system('cp %s %s')
-
-
diff --git a/cgi-bin/webnotes/modules/export_module.py b/cgi-bin/webnotes/modules/export_module.py
index b1232bd316..3055fc5db9 100644
--- a/cgi-bin/webnotes/modules/export_module.py
+++ b/cgi-bin/webnotes/modules/export_module.py
@@ -1,166 +1,35 @@
-# ==============================================================================
-# export to files
-# ==============================================================================
-
-updated_modules = []
-
from webnotes.modules import scrub, get_module_path
-def export_to_files(modules = [], record_list=[], from_db=None, from_ac=None, verbose=1, record_module=None):
- # Multiple doctype and multiple modules export to be done
- # for Module Def, right now using a hack..should consider table update in the next version
- # all modules transfer not working, because source db not known
- # get the items
-
- global updated_modules
-
- if from_ac or from_db:
- init_db_login(from_ac, from_db)
-
- out = []
+def export_to_files(record_list=[], record_module=None, verbose=0):
+ """
+ Export record_list to files. record_list is a list of lists ([doctype],[docname] ) ,
+ """
import webnotes.model.doc
module_doclist =[]
if record_list:
for record in record_list:
- module_doclist.append([d.fields for d in webnotes.model.doc.get(record[0], record[1])])
-
- # build the doclist
- if modules:
- for m in modules:
- module_doclist +=get_module_doclist(m)
-
- # write files
- for doclist in module_doclist:
- if verbose:
- out.append("Writing for " + doclist[0]['doctype'] + " / " + doclist[0]['name'])
- write_document_file(doclist, record_module)
-
- # write out attachments
- for m in modules:
- write_attachments(m)
-
- return out
-
-# ==============================================================================
-# write module.info file with last updated timestamp
-# ==============================================================================
-
-def write_attachments(m):
- import webnotes, os
- from webnotes.utils.file_manager import get_file
-
- try:
- fl = webnotes.conn.sql("select name from `tabFile Data` where module=%s", m)
- except Exception, e:
- if e.args[0]==1054: # no field called module
- return
- else:
- raise e
-
- # write the files
- if fl:
- folder = os.path.join(webnotes.defs.modules_path, m, 'files')
- webnotes.create_folder(folder)
- for f in fl:
- file_det = get_file(f)
- file = open(os.path.join(folder, file_det[0]), 'w+')
- file.write(file_det[1])
- file.close()
-
-
-# ==============================================================================
-# write module.info file with last updated timestamp
-# ==============================================================================
-
-def write_module_info(mod):
- import webnotes.utils, os
-
- file = open(os.path.join(get_module_path(mod), 'module.info'), 'w')
- file.write(str({'update_date': webnotes.utils.now()}))
- file.close()
-
-# ==============================================================================
-# prepare a list of items in a module
-# ==============================================================================
-
-def get_module_items(mod):
- import webnotes
- from webnotes.modules import transfer_types
- from webnotes.modules import scrub
-
- dl = []
- for dt in transfer_types:
- try:
- dl2 = webnotes.conn.sql('select name, modified from `tab%s` where module="%s"' % (dt,mod))
- for e in dl2:
- dl += [dt + ',' + e[0] + ',0']
-
- if e[0] == 'Control Panel':
- dl += [e[0]+','+e[0]+',1']
- except:
- pass
- dl1 = webnotes.conn.sql('select doctype_list from `tabModule Def` where name=%s', mod)
- dl1 = dl1 and dl1[0][0] or ''
- if dl1:
- dl1 = dl1.split('\n')
- dl += [t+',1' for t in dl1]
- dl += ['Module Def,'+mod+',0']
- # build finally
- dl = [e.split(',') for e in dl]
- dl = [[e[0].strip(), e[1].strip(), e[2]] for e in dl] # remove blanks
- return dl
-
-
-# ==============================================================================
-# build a list of doclists of items in that module and send them
-# ==============================================================================
-
-def get_module_doclist(module):
- import webnotes
- import webnotes.model.doc
- item_list = get_module_items(module)
-
- # build the super_doclist
- super_doclist = []
- for i in item_list:
- dl = webnotes.model.doc.get(i[0], i[1])
- if i[2]=='1':
- dl[0].module = module
- # remove compiled code (if any)
- if dl[0].server_code_compiled:
- dl[0].server_code_compiled = None
-
- # add to super
- super_doclist.append([d.fields for d in dl])
-
- return super_doclist
-
-# ==============================================================================
-# Create __init__.py files
-# ==============================================================================
+ doclist = [d.fields for d in webnotes.model.doc.get(record[0], record[1])]
+ write_document_file(doclist, record_module)
def create_init_py(modules_path, dt, dn):
+ """
+ Creates __init__.py in the module directory structure
+ """
import os
- from webnotes.modules import scrub
+
+ def create_if_not_exists(path):
+ initpy = os.path.join(path, '__init__.py')
+ if not os.path.exists(initpy):
+ open(initpy, 'w').close()
+
+ create_if_not_exists(os.path.join(modules_path))
+ create_if_not_exists(os.path.join(modules_path, dt))
+ create_if_not_exists(os.path.join(modules_path, dt, dn))
- # in module
- if not '__init__.py' in os.listdir(modules_path):
- open(os.path.join(modules_path, '__init__.py'), 'w').close()
-
- # in type and name folders
- if dt in ['doctype', 'page', 'search_criteria']:
- if not '__init__.py' in os.listdir(os.path.join(modules_path, dt)):
- open(os.path.join(modules_path, dt, '__init__.py'), 'w').close()
-
- if not '__init__.py' in os.listdir(os.path.join(modules_path, dt, dn)):
- open(os.path.join(modules_path, dt, dn, '__init__.py'), 'w').close()
-
-
-# ==============================================================================
-# Create module folders
-# ==============================================================================
-
def create_folder(module, dt, dn):
+ """
+ Creates directories for module and their __init__.py
+ """
import webnotes, os
# get module path by importing the module
@@ -179,27 +48,30 @@ def create_folder(module, dt, dn):
return folder
-# ==============================================================================
-# Write doclist into file
-# ==============================================================================
-
-def write_document_file(doclist, record_module=None):
- import os
- from webnotes.utils import pprint_dict
-
- global updated_modules
-
+def get_module_name(doclist, record_module=None):
+ """
+ Returns the module-name of a doclist
+ """
# module name
if doclist[0]['doctype'] == 'Module Def':
module = doclist[0]['name']
elif doclist[0]['doctype']=='Control Panel':
- module = 'System'
+ module = 'Core'
elif record_module:
module = record_module
else:
module = doclist[0]['module']
- updated_modules.append(module)
+ return module
+
+def write_document_file(doclist, record_module=None):
+ """
+ Write a doclist to file, can optionally specify module name
+ """
+ import os
+ from webnotes.model.utils import pprint_doclist
+
+ module = get_module_name(doclist, record_module)
# create the folder
code_type = doclist[0]['doctype'] in ['DocType','Page','Search Criteria']
@@ -212,18 +84,17 @@ def write_document_file(doclist, record_module=None):
# write the data file
fname = (code_type and scrub(doclist[0]['name'])) or doclist[0]['name']
- dict_list = [pprint_dict(d) for d in doclist]
- txtfile = open(os.path.join(folder, fname +'.txt'),'w+')
- txtfile.write('[\n' + ',\n'.join(dict_list) + '\n]')
+ txtfile = open(os.path.join(folder, fname +'.txt'),'w+')
+ txtfile.write(pprint_doclist(doclist))
+ #dict_list = [pprint_dict(d) for d in doclist]
+ #txtfile.write('[\n' + ',\n'.join(dict_list) + '\n]')
txtfile.close()
-
-# ==============================================================================
-# Create seperate files for code
-# ==============================================================================
-
def clear_code_fields(doclist, folder, code_type):
+ """
+ Removes code from the doc
+ """
import os
import webnotes
@@ -234,4 +105,9 @@ def clear_code_fields(doclist, folder, code_type):
if doclist[0].get(code_field[0]):
doclist[0][code_field[0]] = None
-
+
+def to_sandbox(record_list=[], record_module='Sandbox'):
+ """
+ Export record_list to Sandbox. record_list is a list of lists ([doctype],[docname] ) ,
+ """
+ pass
diff --git a/cgi-bin/webnotes/modules/import_module.py b/cgi-bin/webnotes/modules/import_module.py
index d0cb048bdd..141299c4af 100644
--- a/cgi-bin/webnotes/modules/import_module.py
+++ b/cgi-bin/webnotes/modules/import_module.py
@@ -12,7 +12,7 @@ def import_module(module, verbose=0):
from webnotes.modules import get_module_path
import os
- not_module = ('startup', 'event_handlers', 'files', 'patches')
+ not_module = ('startup', 'files', 'patches')
if module in not_module:
if verbose: webnotes.msgprint('%s is not a module' % module)
return
@@ -37,12 +37,14 @@ def import_module(module, verbose=0):
def get_doclist(path, doctype, docname):
"returns a doclist (list of dictionaries) of multiple records for the given parameters"
import os
+ from webnotes.model.utils import peval_doclist
+
do_not_import = ('control_panel')
fname = os.path.join(path,doctype,docname,docname+'.txt')
if os.path.exists(fname) and (doctype not in do_not_import):
f = open(fname,'r')
- dl = eval(f.read())
+ dl = peval_doclist(f.read())
f.close()
return dl
else:
diff --git a/cgi-bin/webnotes/session_cache.py b/cgi-bin/webnotes/session_cache.py
index 70e539e91f..8ea8ce18a2 100644
--- a/cgi-bin/webnotes/session_cache.py
+++ b/cgi-bin/webnotes/session_cache.py
@@ -87,10 +87,10 @@ def make_cache_table():
def dump(sd, country):
import webnotes
- import webnotes.model.doclist
+ import webnotes.model.utils
if sd.get('docs'):
- sd['docs'] = webnotes.model.doclist.compress(sd['docs'])
+ sd['docs'] = webnotes.model.utils.compress(sd['docs'])
# delete earlier (?)
webnotes.conn.sql("delete from __SessionCache where user=%s and country=%s", (webnotes.session['user'], country))
diff --git a/cgi-bin/webnotes/settings/__init__.py b/cgi-bin/webnotes/settings/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/cgi-bin/webnotes/settings/account_map_template.py b/cgi-bin/webnotes/settings/account_map_template.py
deleted file mode 100644
index dd634cd2ba..0000000000
--- a/cgi-bin/webnotes/settings/account_map_template.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# Account/Domain Name to Database Mapping file
-# --------------------------------------------
-# last updated on: 2011-02-02 14:31:14
-
-default_db_name = "webnotesdb"
-
-db_name_map = {'wnframework':'webnotesdb'}
-#{'main_acc_name';'db_name'}
-domain_name_map = {'localhost':'webnotesdb'}
-
diff --git a/cgi-bin/webnotes/tests.py b/cgi-bin/webnotes/tests.py
index c452b7c9fe..1506ef7ec8 100644
--- a/cgi-bin/webnotes/tests.py
+++ b/cgi-bin/webnotes/tests.py
@@ -26,17 +26,16 @@ if webnotes.defs.__dict__.get('modules_path'):
def get_tests():
"""
- Returns list of test modules identified by "tests.py"
+ Returns list of test modules identified by "test*.py"
"""
ret = []
for walk_tuple in os.walk(webnotes.defs.modules_path):
- if 'tests.py' in walk_tuple[2]:
+ for test_file in filter(lambda x: x.startswith('test') and x.endswith('.py'), walk_tuple[2]):
dir_path = os.path.relpath(walk_tuple[0], webnotes.defs.modules_path)
if dir_path=='.':
- ret.append('tests')
+ ret.append(test_file[:-3])
else:
- ret.append(dir_path.replace('/', '.') + '.tests')
-
+ ret.append(dir_path.replace('/', '.') + '.' + test_file[:-3])
return ret
def setup():
diff --git a/cgi-bin/webnotes/utils/__init__.py b/cgi-bin/webnotes/utils/__init__.py
index c40b9cab24..c438a3c08f 100644
--- a/cgi-bin/webnotes/utils/__init__.py
+++ b/cgi-bin/webnotes/utils/__init__.py
@@ -43,10 +43,10 @@ def sendmail(recipients, sender='', msg='', subject='[No Subject]', parts=[], cc
def generate_hash():
"""
- Generates reandom hash for session id
+ Generates random hash for session id
"""
- import sha, time
- return sha.new(str(time.time())).hexdigest()
+ import hashlib, time
+ return hashlib.sha224(str(time.time())).hexdigest()
def db_exists(dt, dn):
return webnotes.conn.sql('select name from `tab%s` where name="%s"' % (dt, dn))
@@ -532,19 +532,89 @@ def send_error_report():
''' % (m, form.getvalue('msg') or '', form.getvalue('err_msg'))
sendmail([webnotes.conn.get_value('Control Panel',None,'support_email_id') or 'support@iwebnotes.com'], sender=webnotes.session['user'], msg=err_msg, subject='Error Report '+m)
-# pretty print a dict
+# Dictionary utils
# ==============================================================================
-def pprint_dict(d, level=1):
- indent = ''
- for i in range(0,level):
- indent += '\t'
- lines = []
+def remove_blanks(d):
+ """
+ Returns d with empty ('' or None) values stripped
+ """
+ empty_keys = []
+ for key in d:
+ if d[key]=='' or d[key]==None:
+ # del d[key] raises runtime exception, using a workaround
+ empty_keys.append(key)
+ for key in empty_keys:
+ del d[key]
+
+ return d
+
+def pprint_dict(d, level=1, no_blanks=True):
+ """
+ Pretty print a dictionary with indents
+ """
+ if no_blanks:
+ remove_blanks(d)
+
+ # make indent
+ indent, ret = '', ''
+ for i in range(0,level): indent += '\t'
+
+ # add lines
+ comment, lines = '', []
kl = d.keys()
kl.sort()
+
+ # make lines
for key in kl:
- tmp = {key: d[key]}
- lines.append(indent + str(tmp)[1:-1] )
- return indent + '{\n' \
- + indent + ',\n\t'.join(lines) \
- + '\n' + indent + '}'
+ if key != '##comment':
+ tmp = {key: d[key]}
+ lines.append(indent + str(tmp)[1:-1] )
+
+ # add comment string
+ if '##comment' in kl:
+ ret = ('\n' + indent) + '# ' + d['##comment'] + '\n'
+
+ # open
+ ret += indent + '{\n'
+
+ # lines
+ ret += indent + ',\n\t'.join(lines)
+
+ # close
+ ret += '\n' + indent + '}'
+
+ return ret
+
+def get_common(d1,d2):
+ """
+ returns (list of keys) the common part of two dicts
+ """
+ return [p for p in d1 if p in d2 and d1[p]==d2[p]]
+
+def get_common_dict(d1, d2):
+ """
+ return common dictionary of d1 and d2
+ """
+ ret = {}
+ for key in d1:
+ if key in d2 and d2[key]==d1[key]:
+ ret[key] = d1[key]
+ return ret
+
+def get_diff_dict(d1, d2):
+ """
+ return common dictionary of d1 and d2
+ """
+ diff_keys = set(d2.keys()).difference(set(d1.keys()))
+
+ ret = {}
+ for d in diff_keys: ret[d] = d2[d]
+ return ret
+
+
+
+
+
+
+
diff --git a/cgi-bin/webnotes/utils/jsdependency.py b/cgi-bin/webnotes/utils/jsdependency.py
new file mode 100644
index 0000000000..c9069f18dc
--- /dev/null
+++ b/cgi-bin/webnotes/utils/jsdependency.py
@@ -0,0 +1,31 @@
+class jsDependencyBuilder:
+ @staticmethod
+ def read_code(js_path):
+ try:
+ f = open(js_path)
+ try:
+ code = f.read()
+ finally:
+ f.close
+ except Exception, e:
+ raise e
+ return code
+ @staticmethod
+ def read_imports(code):
+ import re
+ p = re.compile('\$import\(\' (?P [^)]*)\' \)', re.VERBOSE)
+ return p.findall(code)
+ @staticmethod
+ def build_dependency(jsdir,modname,depends= set()):
+ import webnotes.utils.jsnamespace as jsn
+ js_path = jsn.jsNamespace.modname_to_filename(modname,jsdir)
+ code = jsDependencyBuilder.read_code(js_path)
+ curdepend = jsDependencyBuilder.read_imports(code)
+ for i in curdepend:
+ if i not in depends:
+ depends.add(i)
+ depends = depends.union( jsDependencyBuilder.build_dependency(jsdir,i,depends))
+ return depends
+ def build_dependency_from_file(modname,depends= set()):
+ # TODO STUB to read dependency from dependency tree stored in file
+ return jsDependencyBuilder.build_dependency(modname)
diff --git a/cgi-bin/webnotes/utils/jsnamespace.py b/cgi-bin/webnotes/utils/jsnamespace.py
new file mode 100644
index 0000000000..3a730a6dcb
--- /dev/null
+++ b/cgi-bin/webnotes/utils/jsnamespace.py
@@ -0,0 +1,43 @@
+class jsNamespace:
+ package_prefix = '_'
+ @staticmethod
+ def modname_to_filename(modname,jsdir, ext='.js'):
+ import os
+ path = modname.replace('.',os.sep)
+ path = os.path.join(jsdir,path)
+ if jsNamespace.is_package(modname,jsdir):
+ packagename = jsNamespace.getmodfilename(modname)
+ path = os.path.join(path,packagename)
+ elif jsNamespace.is_jsfile(modname,jsdir,ext):
+ path = path + ext
+ else:
+ path = 'notf' # TODO raise exception that it doesnt exist
+ return path
+ @staticmethod
+ def is_package(modname,jsdir):
+ import os
+ path = modname.replace('.',os.sep)
+ path = os.path.join(jsdir,path)
+ return os.path.isdir(path)
+ @staticmethod
+ def is_jsfile(modname,jsdir,ext='.js'):
+ import os
+ path = modname.replace('.',os.sep)
+ path = os.path.join(jsdir,path)
+ return os.path.isfile(path + ext)
+
+ @staticmethod
+ def getmodname(modpath,ext='.js'):
+ """
+ returns filename for the stiched file
+ """
+ import os
+ b = modpath.split(os.sep)
+ modname = jsNamespace.package_prefix + b[-1] + ext
+ return modname
+ @staticmethod
+ def getmodfilename(modname,ext='.js'):
+ ret = modname.split('.')
+ ret = ret[-1]
+ ret = jsNamespace.getmodname(ret)
+ return ret
diff --git a/cgi-bin/webnotes/utils/jstimestamp.py b/cgi-bin/webnotes/utils/jstimestamp.py
new file mode 100644
index 0000000000..db918a8a69
--- /dev/null
+++ b/cgi-bin/webnotes/utils/jstimestamp.py
@@ -0,0 +1,95 @@
+class generateTimestamp:
+ ts_filename = 'timestamp.js'
+ @staticmethod
+ def list_js_files(jsdir,ext='js'):
+ import os
+ all_files= []
+ nono = ['./tiny_mce','./jquery']
+ oldcwd = os.getcwd()
+ os.chdir(jsdir)
+ # TODO Sanitize the loop below
+ for root, subfolders, files in os.walk('.'):
+ if generateTimestamp.is_allowed(nono,root):
+ for filename in files:
+ if filename.endswith(ext):
+ all_files.append(os.path.join(root,filename))
+
+ os.chdir(oldcwd)
+ for i in nono:
+ for j in all_files:
+ if j.startswith(i):
+ all_files.remove(j)
+ return all_files
+
+ @staticmethod
+ def is_allowed(disallowed,item):
+ for i in disallowed:
+ if item.startswith(i):
+ return False
+ return True
+
+
+ @staticmethod
+ def get_timestamp_dict(jsdir,filelist):
+ tsdict={}
+ import os
+ import webnotes.modules as webmod
+ oldcwd = os.getcwd()
+ os.chdir(jsdir)
+ for filename in generateTimestamp.list_js_files('.'):
+ ts = webmod.get_file_timestamp(filename)
+ filename = filename.lstrip('./')
+ filename = filename.rstrip('.js')
+ filename = filename.replace('/','.')
+ if generateTimestamp.is_package(filename):
+ # Whoa its a package
+ # Remove _packagename from the end if file is a package
+ filename = generateTimestamp.convert_to_packagename(filename)
+ tsdict[filename] = ts
+ os.chdir(oldcwd)
+ return tsdict
+ @staticmethod
+ def is_package(filename):
+ from webnotes.utils import jsnamespace
+ p = jsnamespace.jsNamespace.package_prefix
+ return filename.split('.')[-1].startswith()
+
+ @staticmethod
+ def convert_to_packagename(filename):
+ t = []
+ for i in filename.split('.')[:-1]:
+ t.append(i)
+ t.append('.')
+ del t[-1]
+ filename = ''.join(t)
+ return filename
+
+ @staticmethod
+ def read_ts_from_file(jsdir):
+ import json
+ filename=generateTimestamp.ts_filename
+ f = open(generateTimestamp.ts_filename)
+ tsjson = eval(f.read())
+ f.close()
+ ret = json.loads(tsjson)
+ return ret
+
+ @staticmethod
+ def gents(jsdir):
+ fl=generateTimestamp.list_js_files(jsdir)
+ return generateTimestamp.get_timestamp_dict(jsdir,fl)
+
+ @staticmethod
+ def gentsfile(jsdir):
+ """
+ function to generate timestamps of all files in spath
+ dpath is the file in which the timestamps JSON is stored
+ """
+ import json
+ import os
+ tsdict = generateTimestamp.gents(jsdir)
+ f = open(os.path.join(jsdir,'wn',generateTimestamp.ts_filename),'w') #FIXME Hard coded!
+ f.write('wn={}\n')
+ f.write('wn.timestamp=')
+ f.write(json.dumps(tsdict))
+ f.close()
diff --git a/cgi-bin/webnotes/utils/sitemap.py b/cgi-bin/webnotes/utils/sitemap.py
index ffa0471b81..fd489f6d64 100644
--- a/cgi-bin/webnotes/utils/sitemap.py
+++ b/cgi-bin/webnotes/utils/sitemap.py
@@ -25,10 +25,10 @@ def generate_xml(conn, site_prefix):
# list of all Records that are viewable by guests (Blogs, Articles etc)
try:
- from event_handlers import get_sitemap_items
+ from startup.event_handlers import get_sitemap_items
for i in get_sitemap_items(site_prefix):
site_map += link_xml % (i[0], i[1])
except ImportError, e:
pass
- return frame_xml % site_map
\ No newline at end of file
+ return frame_xml % site_map
diff --git a/cgi-bin/webnotes/utils/webservice.py b/cgi-bin/webnotes/utils/webservice.py
index 8f72f75203..33b3d175ef 100644
--- a/cgi-bin/webnotes/utils/webservice.py
+++ b/cgi-bin/webnotes/utils/webservice.py
@@ -41,8 +41,8 @@ class FrameworkServer:
webnotes.msgprint(ret)
raise Exception, e
- if ret.get('message') and ret.get('message')!='Logged In':
- raise Exception, ret.get('message')
+ if 'message' in ret and ret['message']!='Logged In':
+ webnotes.msgprint(ret.get('server_messages'), raise_exception=1)
if ret.get('exc'):
raise Exception, ret.get('exc')
@@ -90,13 +90,14 @@ class FrameworkServer:
"""
Returns the response of a remote method called on a system object specified by `doctype` and `docname`
"""
+ import json
res = self.http_get_response('runserverobj', args = {
'doctype':doctype
,'docname':docname
,'method':method
,'arg':arg
})
- ret = eval(res.read())
+ ret = json.loads(res.read())
if ret.get('exc'):
raise Exception, ret.get('exc')
return ret
@@ -104,8 +105,15 @@ class FrameworkServer:
# -----------------------------------------------------------------------------------------
def run_method(self, method, args={}):
- res = self.http_get_response(method, args)
- ret = eval(res.read())
+ """
+ Run a method on the remote server
+ """
+ res = self.http_get_response(method, args).read()
+ import json
+ try:
+ ret = json.loads(res)
+ except Exception, e:
+ webnotes.msgprint('Bad Response: ' + res, raise_exception=1)
if ret.get('exc'):
raise Exception, ret.get('exc')
return ret
diff --git a/cgi-bin/webnotes/widgets/form.py b/cgi-bin/webnotes/widgets/form.py
index 9a25608608..df75f73297 100644
--- a/cgi-bin/webnotes/widgets/form.py
+++ b/cgi-bin/webnotes/widgets/form.py
@@ -174,15 +174,17 @@ def check_guest_access(doc):
#===========================================================================================
def runserverobj():
+ """
+ Run server objects
+ """
import webnotes.model.code
- import webnotes.model.doclist
+ from webnotes.model.doclist import DocList
from webnotes.utils import cint
form = webnotes.form
-
+ doclist = None
method = form.getvalue('method')
- doclist, clientlist = [], []
arg = form.getvalue('arg')
dt = form.getvalue('doctype')
dn = form.getvalue('docname')
@@ -192,46 +194,23 @@ def runserverobj():
so = webnotes.model.code.get_obj(dt, dn)
else:
- clientlist = webnotes.model.doclist.expand(form.getvalue('docs'))
-
- # find main doc
- for d in clientlist:
- if cint(d.get('docstatus')) != 2 and not d.get('parent'):
- main_doc = webnotes.model.doc.Document(fielddata = d)
-
- # find child docs
- for d in clientlist:
- doc = webnotes.model.doc.Document(fielddata = d)
- if doc.fields.get('parent'):
- doclist.append(doc)
-
- so = webnotes.model.code.get_server_obj(main_doc, doclist)
-
- # check integrity
- if not check_integrity(so.doc):
- return
+ doclist = DocList()
+ doclist.from_compressed(form.getvalue('docs'), dn)
+ so = doclist.make_obj()
check_guest_access(so.doc)
if so:
r = webnotes.model.code.run_server_obj(so, method, arg)
- doclist = so.doclist # reference back [in case of null]
- if r:
- try:
- if r['doclist']:
- clientlist += r['doclist']
- except:
- pass
-
+ if r:
#build output as csv
if cint(webnotes.form.getvalue('as_csv')):
make_csv_output(r, so.doc.doctype)
else:
webnotes.response['message'] = r
- if clientlist:
- doclist.append(main_doc)
- webnotes.response['docs'] = doclist
+ if doclist:
+ webnotes.response['docs'] = doclist.docs
def make_csv_output(res, dt):
import webnotes
@@ -252,169 +231,29 @@ def make_csv_output(res, dt):
webnotes.response['doctype'] = dt.replace(' ','')
-# Document Save
-#===========================================================================================
-
-def _get_doclist(clientlist):
- # converts doc dictionaries into Document objects
-
- from webnotes.model.doc import Document
- form = webnotes.form
-
- midx = 0
- for i in range(len(clientlist)):
- if clientlist[i]['name'] == form.getvalue('docname'):
- main_doc = Document(fielddata = clientlist[i])
- midx = i
- else:
- clientlist[i] = Document(fielddata = clientlist[i])
-
- del clientlist[midx]
- return main_doc, clientlist
-
-def _do_action(doc, doclist, so, method_name, docstatus=0):
-
- from webnotes.model.code import run_server_obj
- set = webnotes.conn.set
-
- if so and hasattr(so, method_name):
- errmethod = method_name
- run_server_obj(so, method_name)
- if hasattr(so, 'custom_'+method_name):
- run_server_obj(so, 'custom_'+method_name)
- errmethod = ''
-
- # fire triggers observers (if any)
- fire_event(doc, method_name)
-
- # set docstatus for all children records
- if docstatus:
- for d in [doc] + doclist:
- if int(d.docstatus or 0) != 2:
- set(d, 'docstatus', docstatus)
-
-def check_integrity(doc):
- import webnotes
-
- if (not webnotes.model.meta.is_single(doc.doctype)) and (not doc.fields.get('__islocal')):
- tmp = webnotes.conn.sql('SELECT modified FROM `tab%s` WHERE name="%s" for update' % (doc.doctype, doc.name))
- if tmp and str(tmp[0][0]) != str(doc.modified):
- webnotes.msgprint('Document has been modified after you have opened it. To maintain the integrity of the data, you will not be able to save your changes. Please refresh this document. [%s/%s]' % (tmp[0][0], doc.modified))
- return 0
-
- return 1
-
-#===========================================================================================
-
def savedocs():
- import webnotes.model.doclist
-
- from webnotes.model.code import get_server_obj
- from webnotes.model.code import run_server_obj
- import webnotes.utils
- from webnotes.widgets.auto_master import update_auto_masters
-
- from webnotes.utils import cint
-
- sql = webnotes.conn.sql
- form = webnotes.form
-
- # action
- action = form.getvalue('action')
-
- # get docs
- doc, doclist = _get_doclist(webnotes.model.doclist.expand(form.getvalue('docs')))
-
- # get server object
- server_obj = get_server_obj(doc, doclist)
-
- # check integrity
- if not check_integrity(doc):
- return
-
- if not doc.check_perm(verbose=1):
- webnotes.msgprint("Not enough permission to save %s" % doc.doctype)
- return
-
- # validate links
- ret = webnotes.model.doclist.validate_links_doclist([doc] + doclist)
- if ret:
- webnotes.msgprint("[Link Validation] Could not find the following values: %s. Please correct and resave. Document Not Saved." % ret)
- return
-
- # saving & post-saving
try:
- # validate befor saving and submitting
- if action in ('Save', 'Submit') and server_obj:
- if hasattr(server_obj, 'validate'):
- t = run_server_obj(server_obj, 'validate')
- if hasattr(server_obj, 'custom_validate'):
- t = run_server_obj(server_obj, 'custom_validate')
-
- # set owner and modified times
- is_new = cint(doc.fields.get('__islocal'))
- if is_new and not doc.owner:
- doc.owner = form.getvalue('user')
-
- doc.modified, doc.modified_by = webnotes.utils.now(), webnotes.session['user']
-
- # save main doc
- try:
- t = doc.save(is_new)
- update_auto_masters(doc)
- except NameError, e:
- webnotes.msgprint('%s "%s" already exists' % (doc.doctype, doc.name))
- if webnotes.conn.sql("select docstatus from `tab%s` where name=%s" % (doc.doctype, '%s'), doc.name)[0][0]==2:
- webnotes.msgprint('[%s "%s" has been cancelled]' % (doc.doctype, doc.name))
- webnotes.errprint(webnotes.utils.getTraceback())
- raise e
-
- # save child docs
- for d in doclist:
- deleted, local = d.fields.get('__deleted',0), d.fields.get('__islocal',0)
-
- if cint(local) and cint(deleted):
- pass
- elif d.fields.has_key('parent'):
- if d.parent and (not d.parent.startswith('old_parent:')):
- d.parent = doc.name # rename if reqd
- d.parenttype = doc.doctype
- d.modified, d.modified_by = webnotes.utils.now(), webnotes.session['user']
- d.save(new = cint(local))
- update_auto_masters(d)
-
- # on_update
- if action in ('Save','Submit') and server_obj:
- if hasattr(server_obj, 'on_update'):
- t = run_server_obj(server_obj, 'on_update')
- if t: webnotes.msgprint(t)
-
- if hasattr(server_obj, 'custom_on_update'):
- t = run_server_obj(server_obj, 'custom_on_update')
- if t: webnotes.msgprint(t)
+ from webnotes.model.doclist import DocList
+ form = webnotes.form_dict
- fire_event(doc, 'on_update')
-
- # on_submit
- if action == 'Submit':
- _do_action(doc, doclist, server_obj, 'on_submit', 1)
+ doclist = DocList()
+ doclist.from_compressed(form.get('docs'), form.get('docname'))
- # for allow_on_submit type
- if action == 'Update':
- _do_action(doc, doclist, server_obj, 'on_update_after_submit', 0)
-
- # on_cancel
- if action == 'Cancel':
- _do_action(doc, doclist, server_obj, 'on_cancel', 2)
+ # action
+ action = form.get('action')
+
+ if action=='Update': action='update_after_submit'
+
+ getattr(doclist, action.lower())()
# update recent documents
- webnotes.user.update_recent(doc.doctype, doc.name)
+ webnotes.user.update_recent(doclist.doc.doctype, doclist.doc.name)
# send updated docs
webnotes.response['saved'] = '1'
- webnotes.response['main_doc_name'] = doc.name
- webnotes.response['docname'] = doc.name
- webnotes.response['docs'] = [doc] + doclist
+ webnotes.response['main_doc_name'] = doclist.doc.name
+ webnotes.response['docname'] = doclist.doc.name
+ webnotes.response['docs'] = [doclist.doc] + doclist.children
except Exception, e:
webnotes.msgprint('Did not save')
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000000..62463aa35a
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,130 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+
+help:
+ @echo "Please use \`make ' where is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/WNFramework.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/WNFramework.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/WNFramework"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WNFramework"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ make -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/README b/docs/README
new file mode 100644
index 0000000000..fd1ffabefd
--- /dev/null
+++ b/docs/README
@@ -0,0 +1,33 @@
+Documentation Help:
+===================
+
+1. To rebuild documentation
+---------------------------
+
+make html
+
+2. Install
+-----------
+
+easy_install sphinx
+
+3. Build modules again (if you have added new modules)
+----------------------
+
+python generate_modules.py ../cgi-bin -d . -f
+
+help:
+
+NOTE: if you do this, all existing changes to the source files will be lost
+python generate_modules.py [source] -d [destination] [-f to rebuild]
+
+4. General Sphinx Help
+----------------------
+
+1. install sphinx
+2. create a docs folder
+3. in the docs folder, do sphinx-quickstart (say yes to autodocs and viewcode)
+4. generate module .txt files using generate_modules.py script by Thomas Waldmann
+5. add cool css and icons in _static folder
+6. update conf.py and add sys.path.append - change .rst to .txt
+7. run "make html"
diff --git a/docs/_build/doctrees/backupall.doctree b/docs/_build/doctrees/backupall.doctree
new file mode 100644
index 0000000000..b2715fa801
Binary files /dev/null and b/docs/_build/doctrees/backupall.doctree differ
diff --git a/docs/_build/doctrees/core.doctree b/docs/_build/doctrees/core.doctree
new file mode 100644
index 0000000000..dc6d0ebca2
Binary files /dev/null and b/docs/_build/doctrees/core.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.control_panel.doctree b/docs/_build/doctrees/core.doctype.control_panel.doctree
new file mode 100644
index 0000000000..200ba0a841
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.control_panel.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.custom_field.doctree b/docs/_build/doctrees/core.doctype.custom_field.doctree
new file mode 100644
index 0000000000..bdacb728f7
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.custom_field.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.doctree b/docs/_build/doctrees/core.doctype.doctree
new file mode 100644
index 0000000000..f135df0227
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.doctype.doctree b/docs/_build/doctrees/core.doctype.doctype.doctree
new file mode 100644
index 0000000000..4d7c9d5645
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.doctype.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.doctype_mapper.doctree b/docs/_build/doctrees/core.doctype.doctype_mapper.doctree
new file mode 100644
index 0000000000..fdea08687f
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.doctype_mapper.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.letter_head.doctree b/docs/_build/doctrees/core.doctype.letter_head.doctree
new file mode 100644
index 0000000000..efb6e1ea77
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.letter_head.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.module_def.doctree b/docs/_build/doctrees/core.doctype.module_def.doctree
new file mode 100644
index 0000000000..dd47cbce27
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.module_def.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.page.doctree b/docs/_build/doctrees/core.doctype.page.doctree
new file mode 100644
index 0000000000..a00a088773
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.page.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.page_template.doctree b/docs/_build/doctrees/core.doctype.page_template.doctree
new file mode 100644
index 0000000000..15f2560308
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.page_template.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.profile.doctree b/docs/_build/doctrees/core.doctype.profile.doctree
new file mode 100644
index 0000000000..1c5f0b0e99
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.profile.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.property_setter.doctree b/docs/_build/doctrees/core.doctype.property_setter.doctree
new file mode 100644
index 0000000000..e75fc2ada2
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.property_setter.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.search_criteria.doctree b/docs/_build/doctrees/core.doctype.search_criteria.doctree
new file mode 100644
index 0000000000..d86aa0cb50
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.search_criteria.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.stylesheet.doctree b/docs/_build/doctrees/core.doctype.stylesheet.doctree
new file mode 100644
index 0000000000..5aea29b77c
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.stylesheet.doctree differ
diff --git a/docs/_build/doctrees/core.doctype.system_console.doctree b/docs/_build/doctrees/core.doctype.system_console.doctree
new file mode 100644
index 0000000000..665ee83faa
Binary files /dev/null and b/docs/_build/doctrees/core.doctype.system_console.doctree differ
diff --git a/docs/_build/doctrees/core.page.doctree b/docs/_build/doctrees/core.page.doctree
new file mode 100644
index 0000000000..6a8051af36
Binary files /dev/null and b/docs/_build/doctrees/core.page.doctree differ
diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle
new file mode 100644
index 0000000000..7acff5f24f
Binary files /dev/null and b/docs/_build/doctrees/environment.pickle differ
diff --git a/docs/_build/doctrees/index.doctree b/docs/_build/doctrees/index.doctree
new file mode 100644
index 0000000000..92858ce913
Binary files /dev/null and b/docs/_build/doctrees/index.doctree differ
diff --git a/docs/_build/doctrees/modules.doctree b/docs/_build/doctrees/modules.doctree
new file mode 100644
index 0000000000..633c58eeb1
Binary files /dev/null and b/docs/_build/doctrees/modules.doctree differ
diff --git a/docs/_build/doctrees/pypi-setup.doctree b/docs/_build/doctrees/pypi-setup.doctree
new file mode 100644
index 0000000000..9b26f6cc54
Binary files /dev/null and b/docs/_build/doctrees/pypi-setup.doctree differ
diff --git a/docs/_build/doctrees/webnotes.doctree b/docs/_build/doctrees/webnotes.doctree
new file mode 100644
index 0000000000..916ce8cc6b
Binary files /dev/null and b/docs/_build/doctrees/webnotes.doctree differ
diff --git a/docs/_build/doctrees/webnotes.install_lib.doctree b/docs/_build/doctrees/webnotes.install_lib.doctree
new file mode 100644
index 0000000000..ff83d7d8b5
Binary files /dev/null and b/docs/_build/doctrees/webnotes.install_lib.doctree differ
diff --git a/docs/_build/doctrees/webnotes.model.doctree b/docs/_build/doctrees/webnotes.model.doctree
new file mode 100644
index 0000000000..b74db3bd24
Binary files /dev/null and b/docs/_build/doctrees/webnotes.model.doctree differ
diff --git a/docs/_build/doctrees/webnotes.modules.doctree b/docs/_build/doctrees/webnotes.modules.doctree
new file mode 100644
index 0000000000..68c9d425c2
Binary files /dev/null and b/docs/_build/doctrees/webnotes.modules.doctree differ
diff --git a/docs/_build/doctrees/webnotes.multi_tenant.doctree b/docs/_build/doctrees/webnotes.multi_tenant.doctree
new file mode 100644
index 0000000000..3a774037c6
Binary files /dev/null and b/docs/_build/doctrees/webnotes.multi_tenant.doctree differ
diff --git a/docs/_build/doctrees/webnotes.utils.doctree b/docs/_build/doctrees/webnotes.utils.doctree
new file mode 100644
index 0000000000..650260de95
Binary files /dev/null and b/docs/_build/doctrees/webnotes.utils.doctree differ
diff --git a/docs/_build/doctrees/webnotes.utils.email_lib.doctree b/docs/_build/doctrees/webnotes.utils.email_lib.doctree
new file mode 100644
index 0000000000..1138683b9f
Binary files /dev/null and b/docs/_build/doctrees/webnotes.utils.email_lib.doctree differ
diff --git a/docs/_build/doctrees/webnotes.widgets.doctree b/docs/_build/doctrees/webnotes.widgets.doctree
new file mode 100644
index 0000000000..41c52c7806
Binary files /dev/null and b/docs/_build/doctrees/webnotes.widgets.doctree differ
diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo
new file mode 100644
index 0000000000..d045baa95a
--- /dev/null
+++ b/docs/_build/html/.buildinfo
@@ -0,0 +1,4 @@
+# Sphinx build info version 1
+# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
+config: 544565fc4fe326492516ee681f9cf316
+tags: fbb0d17656682115ca4d033fb2f83ba1
diff --git a/docs/_build/html/_modules/core/doctype/page/page.html b/docs/_build/html/_modules/core/doctype/page/page.html
new file mode 100644
index 0000000000..3f6f8a6da0
--- /dev/null
+++ b/docs/_build/html/_modules/core/doctype/page/page.html
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+ core.doctype.page.page — WNFramework v1.8 documentation
+
+
+
+
+
+
+
+
+
+
+
[docs]defget_property_list(self,dt):
+ returnwebnotes.conn.sql("""select fieldname, label, fieldtype
+ from tabDocField
+ where parent=%s
+ and fieldtype not in ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Table')
+ and ifnull(fieldname, '') != ''
+ order by label asc""",dt,as_dict=1)
+
+
[docs]defget_setup_data(self):
+ return{
+ 'doctypes':[d[0]fordinwebnotes.conn.sql("select name from tabDocType")],
+ 'dt_properties':self.get_property_list('DocType'),
+ 'df_properties':self.get_property_list('DocField')
+ }
+
+
[docs]defget_field_ids(self):
+ returnwebnotes.conn.sql("select name, fieldtype, label, fieldname from tabDocField where parent=%s",self.doc.doc_type,as_dict=1)
+
+
[docs]defget_defaults(self):
+ ifself.doc.doc_type==self.doc.doc_name:
+ returnwebnotes.conn.sql("select * from `tabDocType` where name=%s",self.doc.doc_name,as_dict=1)[0]
+ else:
+ returnwebnotes.conn.sql("select * from `tabDocField` where name=%s",self.doc.doc_name,as_dict=1)[0]
+#
+# import modules path
+#
+importos,sys
+
+try:
+ importwebnotes.defs
+ m=getattr(webnotes.defs,'modules_path',None)
+ mandsys.path.append(m)
+exceptException,e:
+ raisee
+
+#
+# map for identifying which field values come from files
+#
+code_fields_dict={
+ 'Page':[('script','js'),('content','html'),('style','css'),('static_content','html'),('server_code','py')],
+ 'DocType':[('server_code_core','py'),('client_script_core','js')],
+ 'Search Criteria':[('report_script','js'),('server_script','py'),('custom_query','sql')],
+ 'Patch':[('patch_code','py')],
+ 'Stylesheet':['stylesheet','css'],
+ 'Page Template':['template','html'],
+ 'Control Panel':[('startup_code','js'),('startup_css','css')]
+}
+
+#
+# globals
+#
+#: "v170"
+version='v170'
+form_dict={}
+auth_obj=None
+
+#: The database connection :class:`webnotes.db.Database` setup by :mod:`auth`
+conn=None
+
+#: The cgi.FieldStorage() object (Dictionary representing the formdata from the URL)
+form=None
+
+session=None
+"""
+ Global session dictionary.
+
+ * session['user'] - Current user
+ * session['data'] - Returns a dictionary of the session cache
+"""
+
+user=None
+is_testing=None
+""" Flag to identify if system is in :term:`Testing Mode` """
+
+incoming_cookies={}
+add_cookies={}
+""" Dictionary of additional cookies appended by custom code """
+
+cookies={}
+auto_masters={}
+tenant_id=None
+
+#
+# Custom Class (no traceback)
+#
+
[docs]classValidationError(Exception):
+ pass
+
+#
+# HTTP standard response
+#
+response={'message':'','exc':''}
+"""
+ The JSON response object. Default is::
+
+ {'message':'', 'exc':''}
+"""
+
+#
+# the logs
+#
+debug_log=[]
+""" List of exceptions to be shown in the :term:`Error Console` """
+
+message_log=[]
+""" List of messages to be shown to the user in a popup box at the end of the request """
+
+
[docs]deferrprint(msg):
+ """
+ Append to the :data:`debug log`
+ """
+ debug_log.append(str(msgor''))
+
+
[docs]defmsgprint(msg,small=0,raise_exception=0):
+ """
+ Append to the :data:`message_log`
+ """
+ message_log.append((smalland'__small:'or'')+str(msgor''))
+ ifraise_exception:
+ raiseValidationError
+
+
[docs]defis_apache_user():
+ importos
+ ifos.environ.get('USER')=='apache':
+ returnTrue
+ else:
+ return(notos.environ.get('USER'))
+ # os.environ does not have user, so allows a security vulnerability,fixed now.
+
[docs]defcreate_folder(path):
+ """
+ Wrapper function for os.makedirs (does not throw exception if directory exists)
+ """
+ importos
+
+ try:
+ os.makedirs(path)
+ exceptException,e:
+ ife.args[0]==17:
+ pass
+ else:
+ raisee
+
+
+###############################################################################
+# BEGIN: TENTATIVE CODE FEELS LIKE A CLASS/TEMPLATE IS A BETTER IDEA FOR THESE VARIABLES.
+# Bad idea combining/using one function to set conn,user,session variables.
+# Need to split up.
+###############################################################################
+
[docs]defcheck_expired(self):
+ # in control panel?
+ exp_sec=webnotes.conn.get_value('Control Panel',None,'session_expiry')or'6:00:00'
+
+ # set sessions as expired
+ try:
+ webnotes.conn.sql("update from tabSessions where TIMEDIFF(NOW(), lastupdate) > %s SET `status`='Expired'",exp_sec)
+ exceptException,e:
+ ife.args[0]==1054:
+ self.add_status_column()
+
+ # clear out old sessions
+ webnotes.conn.sql("delete from tabSessions where TIMEDIFF(NOW(), lastupdate) > '72:00:00'")
+
+ # -----------------------------
+
[docs]defadd_status_column(self):
+ webnotes.conn.commit()
+ webnotes.conn.sql("alter table tabSessions add column `status` varchar(20)")
+ webnotes.conn.begin()
+
+
+ # Get IP Info from ipinfodb.com
+ # -----------------------------
[docs]classDatabase:
+ """
+ Open a database connection with the given parmeters, if use_default is True, use the
+ login details from `defs.py`. This is called by the request handler and is accessible using
+ the `conn` global variable. the `sql` method is also global to run queries
+ """
+ def__init__(self,host='',user='',password='',ac_name='',use_default=0):
+ self.host=hostor'localhost'
+ self.user=userorgetattr(defs,'default_db_name','')
+ self.password=passwordorgetattr(defs,'db_password','')
+
+ ifac_name:
+ self.user=self.get_db_login(ac_name)ordefs.default_db_name
+
+ ifuse_default:
+ self.user=defs.default_db_name
+
+ self.is_testing=0
+ self.in_transaction=0
+ self.transaction_writes=0
+ self.testing_tables=[]
+
+ self.connect()
+ ifself.user!='root':
+ self.use(self.user)
+
+ ifwebnotes.logger:
+ webnotes.logger.debug('Database object initialized for:%s',self.user)
+
+
[docs]defcheck_transaction_status(self,query):
+ """
+ Update *in_transaction* and check if "START TRANSACTION" is not called twice
+ """
+ ifself.in_transactionandqueryandquery.strip().split()[0].lower()in['start','alter','drop','create']:
+ raiseException,'This statement can cause implicit commit'
+
+ ifqueryandquery.strip().lower()=='start transaction':
+ self.in_transaction=1
+ self.transaction_writes=0
+
+ ifqueryandquery.strip().split()[0].lower()in['commit','rollback']:
+ self.in_transaction=0
+
+ ifself.in_transactionandquery[:6].lower()in['update','insert']:
+ self.transaction_writes+=1
+ ifself.transaction_writes>5000:
+ webnotes.msgprint('A very long query was encountered. If you are trying to import data, please do so using smaller files')
+ raiseException,'Bad Query!!! Too many writes'
+
[docs]defget_description(self):
+ """
+ Get metadata of the last query
+ """
+ returnself._cursor.description
+
+ # ======================================================================================
+
[docs]defconvert_to_lists(self,res,formatted=0):
+ """
+ Convert the given result set to a list of lists (with cleaned up dates and decimals)
+ """
+ nres=[]
+ forrinres:
+ nr=[]
+ forcinr:
+ nr.append(self.convert_to_simple_type(c,formatted))
+ nres.append(nr)
+ returnnres
+
+ # ======================================================================================
+
[docs]defget_testing_tables(self):
+ """
+ Get list of all tables for which `tab` is to be replaced by `test` before a query is executed
+ """
+ ifnotself.testing_tables:
+ testing_tables=['tab'+r[0]forrinself.sql('SELECT name from tabDocType where docstatus<2 and (issingle=0 or issingle is null)',allow_testing=0)]
+ testing_tables+=['tabSeries','tabSingles']# tabSessions is not included here
+ returnself.testing_tables
+
+ # ======================================================================================
+ # get a single value from a record
+
+
[docs]defget_value(self,doctype,docname,fieldname):
+ """
+ Get a single / multiple value from a record.
+
+ For Single DocType, let docname be = None
+ """
+
+ fl=fieldname
+ ifdocnameand(docname!=doctypeordocname=='DocType'):
+ iftype(fieldname)in(list,tuple):
+ fl='`, `'.join(fieldname)
+
+ r=self.sql("select `%s` from `tab%s` where name='%s'"%(fl,doctype,docname))
+ returnrand(len(r[0])>1andr[0]orr[0][0])orNone
+ else:
+ iftype(fieldname)in(list,tuple):
+ fl="', '".join(fieldname)
+
+ r=self.sql("select value from tabSingles where field in ('%s') and doctype='%s'"%(fieldname,doctype))
+ returnrand(len(r)>1and(i[0]foriinr)orr[0][0])orNone
+
+
[docs]defset_value(self,dt,dn,field,val):
+ fromwebnotes.utilsimportnow
+ ifdnanddt!=dn:
+ self.sql("update `tab"+dt+"` set `"+field+"`=%s, modified=%s where name=%s",(val,now(),dn))
+ else:
+ ifself.sql("select value from tabSingles where field=%s and doctype=%s",(field,dt)):
+ self.sql("update tabSingles set value=%s where field=%s and doctype=%s",(val,field,dt))
+ else:
+ self.sql("insert into tabSingles(doctype, field, value) values (%s, %s, %s)",(dt,field,val))
+
[docs]defset_global(self,key,val,user='__global'):
+ res=self.sql('select defkey from `tabDefaultValue` where defkey=%s and parent=%s',(key,user))
+ ifres:
+ self.sql('update `tabDefaultValue` set defvalue=%s where parent=%s and defkey=%s',(str(val),user,key))
+ else:
+ self.sql('insert into `tabDefaultValue` (name, defkey, defvalue, parent) values (%s,%s,%s,%s)',(user+'_'+key,key,str(val),user))
+
+
[docs]defget_global(self,key,user='__global'):
+ g=self.sql("select defvalue from tabDefaultValue where defkey=%s and parent=%s",(key,user))
+ returngandg[0][0]orNone
+
+ # ======================================================================================
+
[docs]deffield_exists(self,dt,fn):
+ """
+ Returns True if `fn` exists in `DocType` `dt`
+ """
+ returnself.sql("select name from tabDocField where fieldname=%s and parent=%s",(dt,fn))
+
+
[docs]defexists(self,dt,dn):
+ """
+ Returns true if the record exists
+ """
+ try:
+ returnself.sql('select name from `tab%s` where name=%s'%(dt,'%s'),dn)
+ except:
+ returnNone
+
+ # ======================================================================================
+
[docs]defclose(self):
+ """
+ Close my connection
+ """
+ ifself._conn:
+ self._conn.close()
[docs]defvalidate_cmd(cmd):
+ # check if there is no direct possibility of malicious script injection
+ ifcmd.startswith('webnotes.model.code'):
+ raiseException,'Cannot call any methods from webnotes.model.code directly from the handler'
+
+ ifcmd.startswith('webnotes.model.db_schema'):
+ raiseException,'Cannot call any methods from webnotes.model.db_schema directly from the handler'
+
+ ifcmd.startswith('webnotes.conn'):
+ raiseException,'Cannot call database connection method directly from the handler'
+
+# Execution Starts Here
+# ---------------------------------------------------------------------
+
[docs]defcreate_db_and_user(self):
+ importwebnotes.defs
+
+ # create user and db
+ self.conn.sql("CREATE USER '%s'@'localhost' IDENTIFIED BY '%s'"%(self.db_name,webnotes.defs.db_password))
+ self.conn.sql("CREATE DATABASE IF NOT EXISTS `%s` ;"%self.db_name)
+ self.conn.sql("GRANT ALL PRIVILEGES ON `%s` . * TO '%s'@'localhost';"%(self.db_name,self.db_name))
+ self.conn.sql("FLUSH PRIVILEGES")
+ self.conn.sql("SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;")
+ self.conn.sql("USE %s"%self.db_name)
+
+
+
+
+
+importos,sys
+
+cgi_bin_path=os.path.sep.join(__file__.split(os.path.sep)[:-3])
+
+sys.path.append(cgi_bin_path)
+
+
+
+#
+# make a copy of defs.py (if not exists)
+#
+
[docs]defcopy_defs():
+ globalcgi_bin_path
+ ifnotos.path.exists(os.path.join(cgi_bin_path,'webnotes','defs.py')):
+ ret=os.system('cp '+os.path.join(cgi_bin_path,'webnotes','defs_template.py')+\
+ ' '+os.path.join(cgi_bin_path,'webnotes','defs.py'))
+ print'Made copy of defs.py'
+
+#
+# Main Installer Class
+#
[docs]defimport_from_db(self,target,source_path='',password='admin',verbose=0):
+ """
+ a very simplified version, just for the time being..will eventually be deprecated once the framework stabilizes.
+ """
+ importwebnotes.defs
+
+ # delete user (if exists)
+ self.dbman.delete_user(target)
+
+ # create user and db
+ self.dbman.create_user(target,getattr(webnotes.defs,'db_password',None))
+ ifverbose:print"Created user %s"%target
+
+ # create a database
+ self.dbman.create_database(target)
+ ifverbose:print"Created database %s"%target
+
+ # grant privileges to user
+ self.dbman.grant_all_privileges(target,target)
+ ifverbose:print"Granted privileges to user %s and database %s"%(target,target)
+
+ # flush user privileges
+ self.dbman.flush_privileges()
+
+ self.conn.use(target)
+
+ # import in target
+ ifverbose:print"Starting database import..."
+
+ # get the path of the sql file to import
+ source_given=True
+ ifnotsource_path:
+ source_given=False
+ source_path=os.path.join(os.path.sep.join(os.path.abspath(webnotes.__file__).split(os.path.sep)[:-3]),'data','Framework.sql')
+
+ self.dbman.restore_database(target,source_path,self.root_password)
+ ifverbose:print"Imported from database %s"%source_path
+
+ ifnotsource_given:
+ ifverbose:print"Importing core module..."
+ self.import_core_module()
+ self.create_users()
+
+ # framework cleanups
+ self.framework_cleanups(target)
+ ifverbose:print"Ran framework startups on %s"%target
+
+ returntarget
+
+
[docs]defmake_scheduler(root_login,root_password,verbose):
+ """
+ Make the database where all scheduler events will be stored from multiple datbases
+ See webnotes.utils.scheduler for more information
+ """
+ conn=webnotes.db.Database(user=root_login,password=root_password)
+
+ fromwebnotes.model.db_schemaimportDbManager
+
+ dbman=DbManager(conn)
+
+ # delete user (if exists)
+ dbman.delete_user('master_scheduler')
+
+ # create user and db
+ dbman.create_user('master_scheduler',getattr(webnotes.defs,'db_password',None))
+ ifverbose:print"Created user master_scheduler"
+
+ # create a database
+ dbman.create_database('master_scheduler')
+ ifverbose:print"Created database master_scheduler"
+
+ # grant privileges to user
+ dbman.grant_all_privileges('master_scheduler','master_scheduler')
+
+ # flush user privileges
+ dbman.flush_privileges()
+
+ conn.use('master_scheduler')
+
+ # create events table
+ conn.sql("""create table Event(
+ `db_name` varchar(60),
+ `event` varchar(180),
+ `interval` int(20),
+ `next_execution` timestamp,
+ `recurring` int(1),
+ primary key (`db_name`, `event`),
+ index next_execution(next_execution)
+ )""")
+
+ conn.sql("""create table EventLog(
+ `db_name` varchar(180),
+ `event` varchar(180),
+ `executed_on` timestamp,
+ `log` text,
+ index executed_on(executed_on))
+ """)
+#
+# load the options
+#
+
[docs]defget_parser():
+ fromoptparseimportOptionParser
+
+ parser=OptionParser(usage="usage: %prog [options] ROOT_LOGIN ROOT_PASSWORD DBNAME")
+ parser.add_option("-x","--database-password",dest="password",default="admin",help="Optional: New password for the Framework Administrator, default 'admin'")
+ parser.add_option("-s","--source",dest="source_path",default=None,help="Optional: Path of the sql file from which you want to import the instance, default 'data/Framework.sql'")
+
+ returnparser
+
+
+#
+# execution here
+#
[docs]defcheck_if_doc_is_linked(dt,dn):
+ """
+ Raises excption if the given doc(dt, dn) is linked in another record.
+ """
+ sql=webnotes.conn.sql
+
+ ll=get_link_fields(dt)
+ forlinll:
+ link_dt,link_field=l
+ issingle=sql("select issingle from tabDocType where name = '%s'"%link_dt)
+
+ # no such doctype (?)
+ ifnotissingle:continue
+
+ ifissingle[0][0]:
+ item=sql("select doctype from `tabSingles` where field='%s' and value = '%s' and doctype = '%s' "%(link_field,dn,l[0]))
+ ifitem:
+ webnotes.msgprint("Cannot delete %s <b>%s</b> because it is linked in <b>%s</b>"%(dt,dn,item[0][0]),raise_exception=1)
+
+ else:
+ item=sql("select name from `tab%s` where `%s`='%s' and docstatus!=2 limit 1"%(link_dt,link_field,dn))
+ ifitem:
+ webnotes.msgprint("Cannot delete %s <b>%s</b> because it is linked in %s <b>%s</b>"%(dt,dn,link_dt,item[0][0]),raise_exception=1)
+
+
+
[docs]defdelete_doc(doctype=None,name=None,doclist=None):
+ """
+ Deletes a doc(dt, dn) and validates if it is not submitted and not linked in a live record
+ """
+ importwebnotes.model.meta
+ sql=webnotes.conn.sql
+
+ # get from form
+ ifnotdoctype:
+ doctype=webnotes.form_dict.get('dt')
+ name=webnotes.form_dict.get('dn')
+
+ ifnotdoctype:
+ webnotes.msgprint('Nothing to delete!',raise_exception=1)
+
+ # already deleted..?
+ ifnotwebnotes.conn.exists(doctype,name):
+ return
+
+ tablefields=webnotes.model.meta.get_table_fields(doctype)
+
+ # check if submitted
+ d=webnotes.conn.sql("select docstatus from `tab%s` where name=%s"%(doctype,'%s'),name)
+ ifdandint(d[0][0])==1:
+ webnotes.msgprint("Submitted Record '%s' '%s' cannot be deleted"%(doctype,name))
+ raiseException
+
+ # call on_trash if required
+ fromwebnotes.model.codeimportget_obj
+ ifdoclist:
+ obj=get_obj(doclist=doclist)
+ else:
+ obj=get_obj(doctype,name)
+
+ ifhasattr(obj,'on_trash'):
+ obj.on_trash()
+
+ # check if links exist
+ check_if_doc_is_linked(doctype,name)
+
+ try:
+ webnotes.conn.sql("delete from `tab%s` where name='%s' limit 1"%(doctype,name))
+ fortintablefields:
+ webnotes.conn.sql("delete from `tab%s` where parent = %s"%(t[0],'%s'),name)
+ exceptException,e:
+ ife.args[0]==1451:
+ webnotes.msgprint("Cannot delete %s '%s' as it is referenced in another record. You must delete the referred record first"%(doctype,name))
+
+ raisee
+
+ return'okay'
+
+#=================================================================================
+# new feature added 9-Jun-10 to allow doctypes to have labels
[docs]defget_search_criteria(dt):
+ importwebnotes.model.doc
+ # load search criteria for reports (all)
+ dl=[]
+ try:# bc
+ sc_list=webnotes.conn.sql("select name from `tabSearch Criteria` where doc_type = '%s' or parent_doc_type = '%s' and (disabled!=1 OR disabled IS NULL)"%(dt,dt))
+ forscinsc_list:
+ dl+=webnotes.model.doc.get('Search Criteria',sc[0])
+ exceptException,e:
+ pass# no search criteria
+ returndl
+
+
+# Rename Doc
+#=================================================================================
+
+
[docs]defget_link_fields(dt):
+ """
+ Returns linked fields for dt as a tuple of (linked_doctype, linked_field)
+ """
+ returnwebnotes.conn.sql("select parent, fieldname from tabDocField where parent not like 'old%%' and ((options = '%s' and fieldtype='Link') or (options = 'link:%s' and fieldtype='Select'))"%(dt,dt))
+
+
[docs]defrename(dt,old,new,is_doctype=0):
+ """
+ Renames a doc(dt, old) to doc(dt, new) and updates all linked fields of type "Link" or "Select" with "link:"
+ """
+ sql=webnotes.conn.sql
+
+ # rename doc
+ sql("update `tab%s` set name='%s' where name='%s'"%(dt,new,old))
+
+ # get child docs
+ ct=sql("select options from tabDocField where parent = '%s' and fieldtype='Table'"%dt)
+ forcinct:
+ sql("update `tab%s` set parent='%s' where parent='%s'"%(c[0],new,old))
+
+ # get links (link / select)
+ ll=get_link_fields(dt)
+ forlinll:
+ is_single=sql("select issingle from tabDocType where name = '%s'"%l[0])
+ is_single=is_singleandwebnotes.utils.cint(is_single[0][0])or0
+ ifis_single:
+ sql("update `tabSingles` set value='%s' where field='%s' and value = '%s' and doctype = '%s' "%(new,l[1],old,l[0]))
+ else:
+ sql("update `tab%s` set `%s`='%s' where `%s`='%s'"%(l[0],l[1],new,l[1],old))
+
+ # doctype
+ ifis_doctype:
+ sql("RENAME TABLE `tab%s` TO `tab%s`"%(old,new))
+
+ # get child docs (update parenttype)
+ ct=sql("select options from tabDocField where parent = '%s' and fieldtype='Table'"%new)
+ forcinct:
+ sql("update `tab%s` set parenttype='%s' where parenttype='%s'"%(c[0],new,old))
+
+#=================================================================================
+
+
[docs]defclear_recycle_bin():
+ """
+ Clears temporary records that have been deleted
+ """
+ sql=webnotes.conn.sql
+
+ tl=sql('show tables')
+ total_deleted=0
+ fortintl:
+ fl=[i[0]foriinsql('desc `%s`'%t[0])]
+
+ if'name'infl:
+ total_deleted+=sql("select count(*) from `%s` where name like '__overwritten:%%'"%t[0])[0][0]
+ sql("delete from `%s` where name like '__overwritten:%%'"%t[0])
+
+ if'parent'infl:
+ total_deleted+=sql("select count(*) from `%s` where parent like '__oldparent:%%'"%t[0])[0][0]
+ sql("delete from `%s` where parent like '__oldparent:%%'"%t[0])
+
+ total_deleted+=sql("select count(*) from `%s` where parent like 'oldparent:%%'"%t[0])[0][0]
+ sql("delete from `%s` where parent like 'oldparent:%%'"%t[0])
+
+ total_deleted+=sql("select count(*) from `%s` where parent like 'old_parent:%%'"%t[0])[0][0]
+ sql("delete from `%s` where parent like 'old_parent:%%'"%t[0])
+
+ webnotes.msgprint("%s records deleted"%str(int(total_deleted)))
+
+
+# Make Table Copy
+#=================================================================================
+
+"""
+This is where all the plug-in code is executed. The standard method for DocTypes is declaration of a
+standardized `DocType` class that has the methods of any DocType. When an object is instantiated using the
+`get_obj` method, it creates an instance of the `DocType` class of that particular DocType and sets the
+`doc` and `doclist` attributes that represent the fields (properties) of that record.
+
+methods in following modules are imported for backward compatibility
+
+ * webnotes.*
+ * webnotes.utils.*
+ * webnotes.model.doc.*
+ * webnotes.model.doclist.*
+"""
+custom_class='''
+# Please edit this list and import only required elements
+import webnotes
+
+from webnotes.utils import add_days, add_months, add_years, cint, cstr, date_diff, default_fields, flt, fmt_money, formatdate, generate_hash, getTraceback, get_defaults, get_first_day, get_last_day, getdate, has_common, month_name, now, nowdate, replace_newlines, sendmail, set_default, str_esc_quote, user_format, validate_email_add
+from webnotes.model import db_exists
+from webnotes.model.doc import Document, addchild, removechild, getchildren, make_autoname, SuperDocType
+from webnotes.model.doclist import getlist, copy_doclist
+from webnotes.model.code import get_obj, get_server_obj, run_server_obj, updatedb, check_syntax
+from webnotes import session, form, is_testing, msgprint, errprint
+
+set = webnotes.conn.set
+sql = webnotes.conn.sql
+get_value = webnotes.conn.get_value
+in_transaction = webnotes.conn.in_transaction
+convert_to_lists = webnotes.conn.convert_to_lists
+
+# -----------------------------------------------------------------------------------------
+
+class CustomDocType(DocType):
+ def __init__(self, doc, doclist):
+ DocType.__init__(self, doc, doclist)
+'''
+
+
+#=================================================================================
+# execute a script with a lot of globals - deprecated
+#=================================================================================
+
+
[docs]defexecute(code,doc=None,doclist=[]):
+ """
+ Execute the code, if doc is given, then return the instance of the `DocType` class created
+ """
+ # functions used in server script of DocTypes
+ # --------------------------------------------------
+ fromwebnotes.utilsimportadd_days,add_months,add_years,cint,cstr,date_diff,default_fields,flt,fmt_money,formatdate,generate_hash,getTraceback,get_defaults,get_first_day,get_last_day,getdate,has_common,month_name,now,nowdate,replace_newlines,sendmail,set_default,str_esc_quote,user_format,validate_email_add
+ fromwebnotes.modelimportdb_exists
+ fromwebnotes.model.docimportDocument,addchild,removechild,getchildren,make_autoname,SuperDocType
+ fromwebnotes.model.doclistimportgetlist,copy_doclist
+ fromwebnotesimportsession,form,is_testing,msgprint,errprint
+
+ importwebnotes
+
+ set=webnotes.conn.set
+ sql=webnotes.conn.sql
+ get_value=webnotes.conn.get_value
+ in_transaction=webnotes.conn.in_transaction
+ convert_to_lists=webnotes.conn.convert_to_lists
+ ifwebnotes.user:
+ get_roles=webnotes.user.get_roles
+ locals().update({'get_obj':get_obj,'get_server_obj':get_server_obj,'run_server_obj':run_server_obj,'updatedb':updatedb,'check_syntax':check_syntax})
+ version='v170'
+ NEWLINE='\n'
+ BACKSLASH='\\'
+
+ # execute it
+ # -----------------
+ execcodeinlocals()
+
+ # if doc
+ # -----------------
+ ifdoc:
+ d=DocType(doc,doclist)
+ returnd
+
+ iflocals().get('page_html'):
+ returnpage_html
+
+ iflocals().get('out'):
+ returnout
+
+#=================================================================================
+# load the DocType class from module & return an instance
+#=================================================================================
+
+
[docs]defget_custom_script(doctype,script_type):
+ """
+ Returns custom script if set in doctype `Custom Script`
+ """
+ importwebnotes
+ try:
+ custom_script=webnotes.conn.sql("select script from `tabCustom Script` where dt=%s and script_type=%s",(doctype,script_type))
+ exceptException,e:
+ ife.args[0]==1146:
+ returnNone
+ else:raisee
+
+ ifcustom_scriptandcustom_script[0][0]:
+ returncustom_script[0][0]
+
+
[docs]defget_server_obj(doc,doclist=[],basedoctype=''):
+ """
+ Returns the instantiated `DocType` object. Will also manage caching & compiling
+ """
+ # for test
+ importwebnotes
+
+
+ # get doctype details
+ module=webnotes.conn.get_value('DocType',doc.doctype,'module')
+
+ # no module specified (must be really old), can't get code so quit
+ ifnotmodule:
+ return
+
+ module=module.replace(' ','_').lower()
+ dt=doc.doctype.replace(' ','_').lower()
+
+ # import
+ try:
+ exec'from %s.doctype.%s.%s import DocType'%(module,dt,dt)
+ exceptImportError,e:
+ # declare it here
+ classDocType:
+ def__init__(self,d,dl):
+ self.doc,self.doclist=d,dl
+
+ # custom?
+ custom_script=get_custom_script(doc.doctype,'Server')
+ ifcustom_script:
+ globalcustom_class
+
+ execcustom_class+custom_script.replace('\t',' ')inlocals()
+
+ returnCustomDocType(doc,doclist)
+ else:
+ returnDocType(doc,doclist)
+
+#=================================================================================
+# get object (from dt and/or dn or doclist)
+#=================================================================================
+
+
[docs]defget_obj(dt=None,dn=None,doc=None,doclist=[],with_children=0):
+ """
+ Returns the instantiated `DocType` object. Here you can pass the DocType and name (ID) to get the object.
+ If with_children is true, then all child records will be laoded and added in the doclist.
+ """
+ ifdt:
+ importwebnotes.model.doc
+
+ ifnotdn:
+ dn=dt
+ ifwith_children:
+ doclist=webnotes.model.doc.get(dt,dn,from_get_obj=1)
+ else:
+ doclist=webnotes.model.doc.get(dt,dn,with_children=0,from_get_obj=1)
+ returnget_server_obj(doclist[0],doclist)
+ else:
+ returnget_server_obj(doc,doclist)
+
+#=================================================================================
+# get object and run method
+#=================================================================================
+
+
[docs]defrun_server_obj(server_obj,method_name,arg=None):
+ """
+ Executes a method (`method_name`) from the given object (`server_obj`)
+ """
+ ifserver_objandhasattr(server_obj,method_name):
+ ifarg:
+ returngetattr(server_obj,method_name)(arg)
+ else:
+ returngetattr(server_obj,method_name)()
+ else:
+ raiseException,'No method %s'%method_name
+
+#=================================================================================
+# deprecated methods to keep v160 apps happy
+#=================================================================================
+
[docs]classDbManager:
+ """
+ Basically, a wrapper for oft-used mysql commands. like show tables,databases, variables etc...
+
+ #TODO:
+ 0. Simplify / create settings for the restore database source folder
+ 0a. Merge restore database and extract_sql(from webnotes_server_tools).
+ 1. Setter and getter for different mysql variables.
+ 2. Setter and getter for mysql variables at global level??
+ """
+ def__init__(self,conn):
+ """
+ Pass root_conn here for access to all databases.
+ """
+ ifconn:
+ self.conn=conn
+
+
+
[docs]defget_variables(self,regex):
+ """
+ Get variables that match the passed pattern regex
+ """
+ returnlist(self.conn.sql("SHOW VARIABLES LIKE '%s'"%regex))
+
+
+
[docs]defdrop_all_databases(self):
+ """
+ Danger: will delete all databases except test,mysql.
+ """
+ db_list=self.get_database_list()
+ fordbindb_list:
+ self.drop_database(db)
+
+
[docs]defget_table_schema(self,table):
+ """
+ Just returns the output of Desc tables.
+ """
+ returnlist(self.conn.sql("DESC %s"%table))
+
+
[docs]defset_transaction_isolation_level(self,scope='SESSION',level='READ COMMITTED'):
+ #Sets the transaction isolation level. scope = global/session
+ try:
+ self.conn.sql("SET %s TRANSACTION ISOLATION LEVEL %s"%(scope,level))
+ exceptException,e:
+ raisee
+
+
+
+# -------------------------------------------------
+# validate column name to be code-friendly
+# -------------------------------------------------
+
+
[docs]defvalidate_column_name(n):
+ n=n.replace(' ','_').strip().lower()
+ importre
+ ifnotre.match("[a-zA-Z_][a-zA-Z0-9_]*$",n):
+ webnotes.msgprint('err:%s is not a valid fieldname.<br>A valid name must contain letters / numbers / spaces.<br><b>Tip: </b>You can change the Label after the fieldname has been set'%n)
+ raiseException
+ returnn
+
+# -------------------------------------------------
+# sync table - called from form.py
+# -------------------------------------------------
+
+
[docs]defupdatedb(dt,archive=0):
+ """
+ Syncs a `DocType` to the table
+ * creates if required
+ * updates columns
+ * updates indices
+ """
+ res=webnotes.conn.sql("select ifnull(issingle, 0) from tabDocType where name=%s",dt)
+ ifnotres:
+ raiseException,'Wrong doctype "%s" in updatedb'%dt
+
+ ifnotres[0][0]:
+ webnotes.conn.commit()
+ tab=DbTable(dt,archiveand'arc'or'tab')
+ tab.sync()
+ webnotes.conn.begin()
+
+# patch to remove foreign keys
+# ----------------------------
+
+"""
+Contains the Document class representing an object / record
+"""
+
+importwebnotes
+importwebnotes.model.meta
+
+fromwebnotes.utilsimport*
+
+# actually should be "BaseDocType" - deprecated. Only for v160
+
[docs]classDocument:
+ """
+ The wn(meta-data)framework equivalent of a Database Record.
+ Stores,Retrieves,Updates the record in the corresponding table.
+ Runs the triggers required.
+
+ The `Document` class represents the basic Object-Relational Mapper (ORM). The object type is defined by
+ `DocType` and the object ID is represented by `name`::
+
+ Please note the anamoly in the Web Notes Framework that `ID` is always called as `name`
+
+ If both `doctype` and `name` are specified in the constructor, then the object is loaded from the database.
+ If only `doctype` is given, then the object is not loaded
+ If `fielddata` is specfied, then the object is created from the given dictionary.
+
+ **Note 1:**
+
+ The getter and setter of the object are overloaded to map to the fields of the object that
+ are loaded when it is instantiated.
+
+ For example: doc.name will be the `name` field and doc.owner will be the `owner` field
+
+ **Note 2 - Standard Fields:**
+
+ * `name`: ID / primary key
+ * `owner`: creator of the record
+ * `creation`: datetime of creation
+ * `modified`: datetime of last modification
+ * `modified_by` : last updating user
+ * `docstatus` : Status 0 - Saved, 1 - Submitted, 2- Cancelled
+ * `parent` : if child (table) record, this represents the parent record
+ * `parenttype` : type of parent record (if any)
+ * `parentfield` : table fieldname of parent record (if any)
+ * `idx` : Index (sequence) of the child record
+ """
+
+ def__init__(self,doctype='',name='',fielddata={},prefix='tab'):
+ self._roles=[]
+ self._perms=[]
+ self._user_defaults={}
+ self._prefix=prefix
+
+ iffielddata:
+ self.fields=fielddata
+ else:
+ self.fields={}
+
+ ifnotself.fields.has_key('name'):
+ self.fields['name']=''# required on save
+ ifnotself.fields.has_key('doctype'):
+ self.fields['doctype']=''# required on save
+ ifnotself.fields.has_key('owner'):
+ self.fields['owner']=''# required on save
+
+ ifdoctype:
+ self.fields['doctype']=doctype
+ ifname:
+ self.fields['name']=name
+ self.__initialized=1
+
+ if(doctypeandname):
+ self._loadfromdb(doctype,name)
+
+ def__nonzero__(self):
+ returnTrue
+
+ def__str__(self):
+ returnstr(self.fields)
+
+ # Load Document
+ # ---------------------------------------------------------------------------
+
+ def_loadfromdb(self,doctype=None,name=None):
+ ifname:self.name=name
+ ifdoctype:self.doctype=doctype
+
+ ifwebnotes.model.meta.is_single(self.doctype):
+ self._loadsingle()
+ else:
+ dataset=webnotes.conn.sql('select * from `%s%s` where name="%s"'%(self._prefix,self.doctype,self.name.replace('"','\"')))
+ ifnotdataset:
+ webnotes.msgprint('%s%s does not exist'%(self.doctype,self.name),1)
+ raiseException,'[WNF] %s%s does not exist'%(self.doctype,self.name)
+ self._load_values(dataset[0],webnotes.conn.get_description())
+
+ # Load Fields from dataset
+ # ---------------------------------------------------------------------------
+
+ def_load_values(self,data,description):
+ foriinrange(len(description)):
+ v=data[i]
+ self.fields[description[i][0]]=webnotes.conn.convert_to_simple_type(v)
+
+ def_merge_values(self,data,description):
+ foriinrange(len(description)):
+ v=data[i]
+ ifv:# only if value, over-write
+ self.fields[description[i][0]]=webnotes.conn.convert_to_simple_type(v)
+
+ # Load Single Type
+ # ---------------------------------------------------------------------------
+
+ def_loadsingle(self):
+ self.name=self.doctype
+ dataset=webnotes.conn.sql("select field, value from tabSingles where doctype='%s'"%self.doctype)
+ fordindataset:self.fields[d[0]]=d[1]
+
+ # Setter
+ # ---------------------------------------------------------------------------
+
+ def__setattr__(self,name,value):
+ # normal attribute
+ ifnotself.__dict__.has_key('_Document__initialized'):
+ self.__dict__[name]=value
+ elifself.__dict__.has_key(name):
+ self.__dict__[name]=value
+ else:
+ # field attribute
+ f=self.__dict__['fields']
+ f[name]=value
+
+ # Getter
+ # ---------------------------------------------------------------------------
+
+ def__getattr__(self,name):
+ ifself.__dict__.has_key(name):
+ returnself.__dict__[name]
+ elifself.fields.has_key(name):
+ returnself.fields[name]
+ else:
+ return''
+
+ # Get Amendement number
+ # ---------------------------------------------------------------------------
+
+ def_get_amended_name(self):
+ am_id=1
+ am_prefix=self.amended_from
+ ifwebnotes.conn.sql('select amended_from from `tab%s` where name = "%s"'%(self.doctype,self.amended_from))[0][0]or'':
+ am_id=cint(self.amended_from.split('-')[-1])+1
+ am_prefix='-'.join(self.amended_from.split('-')[:-1])# except the last hyphen
+
+ self.name=am_prefix+'-'+str(am_id)
+
+ # Set Name
+ # ---------------------------------------------------------------------------
+
+ def_set_name(self,autoname,istable):
+ self.localname=self.name
+
+ # get my object
+ importwebnotes.model.code
+ so=webnotes.model.code.get_server_obj(self,[])
+
+ # amendments
+ ifself.amended_from:
+ self._get_amended_name()
+
+ # by method
+ elifsoandhasattr(so,'autoname'):
+ r=webnotes.model.code.run_server_obj(so,'autoname')
+ ifr:returnr
+
+ # based on a field
+ elifautonameandautoname.startswith('field:'):
+ n=self.fields[autoname[6:]]
+ ifnotn:
+ raiseException,'Name is required'
+ self.name=n.strip()
+
+ # based on expression
+ elifautonameandautoname.startswith('eval:'):
+ doc=self# for setting
+ self.name=eval(autoname[5:])
+
+ # call the method!
+ elifautonameandautoname!='Prompt':
+ self.name=make_autoname(autoname,self.doctype)
+
+ # given
+ elifself.fields.get('__newname',''):
+ self.name=self.fields['__newname']
+
+ # default name for table
+ elifistable:
+ self.name=make_autoname('#########',self.doctype)
+
+ # Validate Name
+ # ---------------------------------------------------------------------------
+
+ def_validate_name(self,case):
+
+ ifwebnotes.conn.sql('select name from `tab%s` where name=%s'%(self.doctype,'%s'),self.name):
+ raiseNameError,'Name %s already exists'%self.name
+
+ # no name
+ ifnotself.name:return'No Name Specified for %s'%self.doctype
+
+ # new..
+ ifself.name.startswith('New '+self.doctype):
+ return'There were some errors setting the name, please contact the administrator'
+
+ ifcase=='Title Case':self.name=self.name.title()
+ ifcase=='UPPER CASE':self.name=self.name.upper()
+
+ self.name=self.name.strip()# no leading and trailing blanks
+
+ forbidden=['%',"'",'"','#','*','?','`']
+ forfinforbidden:
+ iffinself.name:
+ webnotes.msgprint('%s not allowed in ID (name)'%f,raise_exception=1)
+
+ # Insert
+ # ---------------------------------------------------------------------------
+
+ def_makenew(self,autoname,istable,case='',make_autoname=1):
+ # set owner
+ ifnotself.owner:self.owner=webnotes.session['user']
+
+ # set name
+ ifmake_autoname:
+ self._set_name(autoname,istable)
+
+ # validate name
+ self._validate_name(case)
+
+ # insert!
+ webnotes.conn.sql("""insert into `tab%s` (name, owner, creation, modified, modified_by) values ('%s', '%s', '%s', '%s', '%s')"""%(self.doctype,self.name,webnotes.session['user'],now(),now(),webnotes.session['user']))
+
+
+ # Update Values
+ # ---------------------------------------------------------------------------
+
+ def_update_single(self,link_list):
+ update_str=["(%s, 'modified', %s)",]
+ values=[self.doctype,now()]
+
+ webnotes.conn.sql("delete from tabSingles where doctype='%s'"%self.doctype)
+ forfinself.fields.keys():
+ ifnot(fin('modified','doctype','name','perm','localname','creation'))\
+ and(notf.startswith('__')):# fields not saved
+
+ # validate links
+ iflink_listandlink_list.get(f):
+ self.fields[f]=self._validate_link(link_list[f][0],self.fields[f])
+
+ ifself.fields[f]==None:
+ update_str.append("(%s,%s,NULL)")
+ values.append(self.doctype)
+ values.append(f)
+ else:
+ update_str.append("(%s,%s,%s)")
+ values.append(self.doctype)
+ values.append(f)
+ values.append(self.fields[f])
+ webnotes.conn.sql("insert into tabSingles(doctype, field, value) values %s"%(', '.join(update_str)),values)
+
+ # Validate Links
+ # ---------------------------------------------------------------------------
+
+
+ def_validate_link(self,dt,dn):
+ ifnotdt:returndn
+ ifnotdn:returnNone
+ ifdt.lower().startswith('link:'):
+ dt=dt[5:]
+ if'\n'indt:
+ dt=dt.split('\n')[0]
+ tmp=webnotes.conn.sql("""SELECT name FROM `tab%s` WHERE name = %s"""%(dt,'%s'),dn)
+ returntmpandtmp[0][0]or''# match case
+
+ # Update query
+ # ---------------------------------------------------------------------------
+
+ def_update_values(self,issingle,link_list,ignore_fields=0):
+ ifissingle:
+ self._update_single(link_list)
+ else:
+ update_str,values=[],[]
+ # set modified timestamp
+ self.modified=now()
+ self.modified_by=webnotes.session['user']
+ forfinself.fields.keys():
+ if(not(fin('doctype','name','perm','localname','creation','_user_tags'))) \
+ and(notf.startswith('__')):# fields not saved
+
+ # validate links
+ iflink_listandlink_list.get(f):
+ self.fields[f]=self._validate_link(link_list[f][0],self.fields[f])
+
+ ifself.fields[f]==Noneorself.fields[f]=='':
+ update_str.append("`%s`=NULL"%f)
+ ifignore_fields:
+ try:r=webnotes.conn.sql("update `tab%s` set `%s`=NULL where name=%s"%(self.doctype,f,'%s'),self.name)
+ except:pass
+ else:
+ values.append(self.fields[f])
+ update_str.append("`%s`=%s"%(f,'%s'))
+ ifignore_fields:
+ try:r=webnotes.conn.sql("update `tab%s` set `%s`=%s where name=%s"%(self.doctype,f,'%s','%s'),(self.fields[f],self.name))
+ except:pass
+ ifvalues:
+ ifnotignore_fields:
+ # update all in one query
+ r=webnotes.conn.sql("update `tab%s` set %s where name='%s'"%(self.doctype,', '.join(update_str),self.name),values)
+
+ # Save values
+ # ---------------------------------------------------------------------------
+
+
[docs]defsave(self,new=0,check_links=1,ignore_fields=0,make_autoname=1):
+ """
+ Saves the current record in the database. If new = 1, creates a new instance of the record.
+ Also clears temperory fields starting with `__`
+
+ * if check_links is set, it validates all `Link` fields
+ * if ignore_fields is sets, it does not throw an exception for any field that does not exist in the
+ database table
+ """
+ res=webnotes.model.meta.get_dt_values(self.doctype,'autoname, issingle, istable, name_case',as_dict=1)
+ res=resandres[0]or{}
+
+ # if required, make new
+ ifnewor(notnewandself.fields.get('__islocal'))and(notres.get('issingle')):
+ r=self._makenew(res.get('autoname'),res.get('istable'),res.get('name_case'),make_autoname)
+ ifr:
+ returnr
+
+ # save the values
+ self._update_values(res.get('issingle'),check_linksandself.make_link_list()or{},ignore_fields)
+ self._clear_temp_fields()
+
+ # check permissions
+ # ---------------------------------------------------------------------------
+
+ def_get_perms(self):
+ ifnotself._perms:
+ self._perms=webnotes.conn.sql("select role, `match` from tabDocPerm where parent=%s and ifnull(`read`,0) = 1 and ifnull(permlevel,0)=0",self.doctype)
+
+ def_get_roles(self):
+ # check if roles match/
+ ifnotself._roles:
+ ifwebnotes.user:
+ self._roles=webnotes.user.get_roles()
+ else:
+ self._roles=['Guest']
+
+ def_get_user_defaults(self):
+ ifnotself._user_defaults:
+ self._user_defaults=webnotes.user.get_defaults()
+
+
[docs]defcheck_perm(self,verbose=0):
+ importwebnotes
+
+ # find roles with read access for this record at 0
+ self._get_perms()
+ self._get_roles()
+ self._get_user_defaults()
+
+ has_perm,match=0,[]
+
+ # loop through everything to find if there is a match
+ forrinself._perms:
+ ifr[0]inself._roles:
+ has_perm=1
+ ifr[1]andmatch!=-1:
+ match.append(r[1])# add to match check
+ else:
+ match=-1# has permission and no match, so match not required!
+
+ ifhas_permandmatchandmatch!=-1:
+ forminmatch:
+ ifself.fields.get(m,'no value')inself._user_defaults.get(m,'no default'):
+ has_perm=1
+ break# permission found! break
+ else:
+ has_perm=0
+ ifverbose:
+ webnotes.msgprint("Value not allowed: '%s' for '%s'"%(self.fields.get(m,'no value'),m))
+
+
+ # check for access key
+ ifwebnotes.formandwebnotes.form.has_key('akey'):
+ importwebnotes.utils.encrypt
+ ifwebnotes.utils.encrypt.decrypt(webnotes.form.getvalue('akey'))==self.name:
+ has_perm=1
+ webnotes.response['print_access']=1
+
+ returnhas_perm
+
+ # Cleanup
+ # ---------------------------------------------------------------------------
+
[docs]defclear_table(self,doclist,tablefield,save=0):
+ """
+ Clears the child records from the given `doclist` for a particular `tablefield`
+ """
+ importwebnotes.model.doclist
+
+ fordinwebnotes.model.doclist.getlist(doclist,tablefield):
+ d.fields['__oldparent']=d.parent
+ d.parent='old_parent:'+d.parent# for client to send it back while saving
+ d.docstatus=2
+ ifsaveandnotd.fields.get('__islocal'):
+ d.save()
+ self.fields['__unsaved']=1
+
+
[docs]defaddchild(self,fieldname,childtype='',local=0,doclist=None):
+ """
+ Returns a child record of the give `childtype`.
+
+ * if local is set, it does not save the record
+ * if doclist is passed, it append the record to the doclist
+ """
+ ifnotchildtype:
+ childtype=db_getchildtype(self.doctype,fieldname)
+
+ d=Document()
+ d.parent=self.name
+ d.parenttype=self.doctype
+ d.parentfield=fieldname
+ d.doctype=childtype
+ d.docstatus=0;
+ d.name=''
+ d.owner=webnotes.session['user']
+
+ iflocal:
+ d.fields['__islocal']='1'# for Client to identify unsaved doc
+ else:
+ d.save(new=1)
+
+ ifdoclist!=None:
+ doclist.append(d)
+
+ returnd
+
+
[docs]defaddchild(parent,fieldname,childtype='',local=0,doclist=None):
+ """
+
+ Create a child record to the parent doc.
+
+ Example::
+
+ c = Document('Contact','ABC')
+ d = addchild(c, 'contact_updates', 'Contact Update', local = 1)
+ d.last_updated = 'Phone call'
+ d.save(1)
+ """
+ returnparent.addchild(fieldname,childtype,local,doclist)
+
+# Remove Child
+# ------------
+
+
[docs]defremovechild(d,is_local=0):
+ """
+ Sets the docstatus of the object d to 2 (deleted) and appends an 'old_parent:' to the parent name
+ """
+ ifnotis_local:
+ set(d,'docstatus',2)
+ set(d,'parent','old_parent:'+d.parent)
+ else:
+ d.parent='old_parent:'+d.parent
+ d.docstatus=2
+
+# Naming
+# ------
+
[docs]defmake_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
+ """
+ n=''
+ l=key.split('.')
+ foreinl:
+ en=''
+ ife.startswith('#'):
+ digits=len(e)
+ en=getseries(n,digits,doctype)
+ elife=='YY':
+ importtime
+ en=time.strftime('%y')
+ elife=='MM':
+ importtime
+ en=time.strftime('%m')
+ elife=='YYYY':
+ importtime
+ en=time.strftime('%Y')
+ else:en=e
+ n+=en
+ returnn
+
+# Get Series for Autoname
+# -----------------------
+
[docs]defgetseries(key,digits,doctype=''):
+ # series created ?
+ ifwebnotes.conn.sql("select name from tabSeries where name='%s'"%key):
+
+ # yes, update it
+ webnotes.conn.sql("update tabSeries set current = current+1 where name='%s'"%key)
+
+ # find the series counter
+ r=webnotes.conn.sql("select current from tabSeries where name='%s'"%key)
+ n=r[0][0]
+ else:
+
+ # no, create it
+ webnotes.conn.sql("insert into tabSeries (name, current) values ('%s', 1)"%key)
+ n=1
+ return('%0'+str(digits)+'d')%n
+
+
+# Get Children
+# ------------
+
+
[docs]defgetchildren(name,childtype,field='',parenttype='',from_doctype=0,prefix='tab'):
+ importwebnotes
+
+ tmp=''
+
+ iffield:
+ tmp=' and parentfield="%s" '%field
+ ifparenttype:
+ tmp=' and parenttype="%s" '%parenttype
+
+ try:
+ dataset=webnotes.conn.sql("select * from `%s%s` where parent='%s' %s order by idx"%(prefix,childtype,name,tmp))
+ desc=webnotes.conn.get_description()
+ exceptException,e:
+ ifprefix=='arc'ande.args[0]==1146:
+ return[]
+ else:
+ raisee
+
+ l=[]
+
+ foriindataset:
+ d=Document()
+ d.doctype=childtype
+ d._load_values(i,desc)
+ l.append(d)
+
+ returnl
+
+# Check if "Guest" is allowed to view this page
+# ---------------------------------------------
+
+
[docs]defcheck_page_perm(doc):
+ ifdoc.name=='Login Page':
+ return
+ ifdoc.publish:
+ return
+
+ ifnotwebnotes.conn.sql("select name from `tabPage Role` where parent=%s and role='Guest'",doc.name):
+ webnotes.response['exc_type']='PermissionError'
+ raiseException,'[WNF] No read permission for %s%s'%('Page',doc.name)
+
+
[docs]defget_report_builder_code(doc):
+ ifdoc.doctype=='Search Criteria':
+ fromwebnotes.model.codeimportget_code
+
+ ifdoc.standard!='No':
+ doc.report_script=get_code(doc.module,'Search Criteria',doc.name,'js')
+ doc.custom_query=get_code(doc.module,'Search Criteria',doc.name,'sql')
+
+
+# called from everywhere
+# load a record and its child records and bundle it in a list - doclist
+# ---------------------------------------------------------------------
+
+
[docs]defget(dt,dn='',with_children=1,from_get_obj=0,prefix='tab'):
+ """
+ Returns a doclist containing the main record and all child records
+ """
+ importwebnotes
+ importwebnotes.model
+ importwebnotes.defs
+
+ dn=dnordt
+
+ # load the main doc
+ doc=Document(dt,dn,prefix=prefix)
+
+ # check permission - for doctypes, pages
+ if(dtin('DocType','Page','Control Panel','Search Criteria'))or(from_get_objandwebnotes.session.get('user')!='Guest'):
+ ifdt=='Page'andwebnotes.session['user']=='Guest':
+ check_page_perm(doc)
+ else:
+ ifnotdoc.check_perm():
+ webnotes.response['exc_type']='PermissionError'
+ raisewebnotes.ValidationError,'[WNF] No read permission for %s%s'%(dt,dn)
+
+ # import report_builder code
+ get_report_builder_code(doc)
+
+ ifnotwith_children:
+ # done
+ return[doc,]
+
+ # get all children types
+ tablefields=webnotes.model.meta.get_table_fields(dt)
+
+ # load chilren
+ doclist=[doc,]
+ fortintablefields:
+ doclist+=getchildren(doc.name,t[0],t[1],dt,prefix=prefix)
+
+ returndoclist
[docs]defexpand(docs):
+ """
+ Expand a doclist sent from the client side. (Internally used by the request handler)
+ """
+ fromwebnotes.utilsimportload_json
+
+ docs=load_json(docs)
+ clist=[]
+ fordindocs['_vl']:
+ doc=xzip(docs['_kl'][d[0]],d);
+ clist.append(doc)
+ returnclist
+
+
[docs]defcompress(doclist):
+ """
+ Compress a doclist before sending it to the client side. (Internally used by the request handler)
+
+ """
+ ifdoclistandhasattr(doclist[0],'fields'):
+ docs=[d.fieldsfordindoclist]
+ else:
+ docs=doclist
+
+ kl,vl={},[]
+ fordindocs:
+ dt=d['doctype']
+ ifnot(dtinkl.keys()):
+ fl=d.keys()
+ forbidden=['server_code_compiled']
+ nl=['doctype','localname','__oldparent','__unsaved']
+
+ # add client script for doctype, doctype due to ambiguity
+ ifdt=='DocType':nl.append('__client_script')
+
+ forfinfl:
+ ifnot(finnl)andnot(finforbidden):
+ nl.append(f)
+ kl[dt]=nl
+
+ ## values
+ fl=kl[dt]
+ nl=[]
+ forfinfl:
+ v=d.get(f)
+
+ iftype(v)==long:
+ v=int(v)
+ nl.append(v)
+ vl.append(nl)
+ #errprint(str({'_vl':vl,'_kl':kl}))
+ return{'_vl':vl,'_kl':kl}
+
+# Get Children List (for scripts utility)
+# ---------------------------------------
+
+
[docs]defgetlist(doclist,field):
+ """
+ Filter a list of records for a specific field from the full doclist
+
+ Example::
+
+ # find all phone call details
+ dl = getlist(self.doclist, 'contact_updates')
+ pl = []
+ for d in dl:
+ if d.type=='Phone':
+ pl.append(d)
+ """
+
+ l=[]
+ fordindoclist:
+ ifd.parentand(notd.parent.lower().startswith('old_parent:'))andd.parentfield==field:
+ l.append(d)
+ returnl
+
+# Copy doclist
+# ------------
+
+
[docs]defcopy_doclist(doclist,no_copy=[]):
+ """
+ Save & return a copy of the given doclist
+ Pass fields that are not to be copied in `no_copy`
+ """
+ fromwebnotes.model.docimportDocument
+
+ cl=[]
+
+ # main doc
+ c=Document(fielddata=doclist[0].fields.copy())
+
+ # clear no_copy fields
+ forfinno_copy:
+ ifc.fields.has_key(f):
+ c.fields[f]=None
+
+ c.name=None
+ c.save(1)
+ cl.append(c)
+
+ # new parent name
+ parent=c.name
+
+ # children
+ fordindoclist[1:]:
+ c=Document(fielddata=d.fields.copy())
+ c.name=None
+
+ # clear no_copy fields
+ forfinno_copy:
+ ifc.fields.has_key(f):
+ c.fields[f]=None
+
+ c.parent=parent
+ c.save(1)
+ cl.append(c)
+
+ returncl
+
+# Validate Multiple Links
+# -----------------------
+
+
[docs]defvalidate_links_doclist(doclist):
+ """
+ Validate link fields and return link fields that are not correct.
+ Calls the `validate_links` function on the Document object
+ """
+ ref,err_list={},[]
+ fordindoclist:
+ ifnotref.get(d.doctype):
+ ref[d.doctype]=d.make_link_list()
+
+ err_list+=d.validate_links(ref[d.doctype])
+ return', '.join(err_list)
+
+# Get list of field values
+# ------------------------
+
+
[docs]defgetvaluelist(doclist,fieldname):
+ """
+ Returns a list of values of a particualr fieldname from all Document object in a doclist
+ """
+ l=[]
+ fordindoclist:
+ l.append(d.fields[fieldname])
+ returnl
+
+"""
+ DocType module
+ ==============
+
+ This module has the DocType class that represents a "DocType" as metadata.
+ This is usually called by the form builder or report builder.
+
+ Key functions:
+ * manage cache - read / write
+ * merge client-side scripts
+ * update properties from the modules .txt files
+
+ Cache management:
+ * Cache is stored in __DocTypeCache
+"""
+
+importwebnotes
+importwebnotes.model
+importwebnotes.model.doclist
+importwebnotes.model.doc
+
+fromwebnotes.utilsimportcstr
+
+class_DocType:
+ """
+ The _DocType object is created internally using the module's `get` method.
+ """
+ def__init__(self,name):
+ self.name=name
+
+ # is cache modified ?
+ # =================================================================
+
+ defis_modified(self):
+ """
+ Returns true if modified
+ """
+ try:
+ # doctype modified
+ modified=webnotes.conn.sql("select modified from tabDocType where name=%s",self.name)[0][0]
+
+ # cache modified
+ cache_modified=webnotes.conn.sql("SELECT modified from `__DocTypeCache` where name='%s'"%self.name)[0][0]
+
+ exceptIndexError,e:
+ return1
+
+ returncache_modified!=modified
+
+ # write to cache
+ # =================================================================
+
+ def_update_cache(self,doclist):
+ importzlib
+
+ ifwebnotes.conn.sql("SELECT name FROM __DocTypeCache WHERE name=%s",self.name):
+ webnotes.conn.sql("UPDATE `__DocTypeCache` SET `modified`=%s, `content`=%s WHERE name=%s",(doclist[0].modified,zlib.compress(str([d.fieldsfordindoclist]),2),self.name))
+ else:
+ webnotes.conn.sql("INSERT INTO `__DocTypeCache` (`name`, `modified`, `content`) VALUES (%s, %s, %s)",(self.name,doclist[0].modified,zlib.compress(str([d.fieldsfordindoclist]))))
+
+ # read from cache
+ # =================================================================
+
+ def_load_from_cache(self):
+ importzlib
+
+ doclist=eval(zlib.decompress(webnotes.conn.sql("SELECT content from `__DocTypeCache` where name=%s",self.name)[0][0]))
+ return[webnotes.model.doc.Document(fielddata=d)fordindoclist]
+
+
+ # load options for "link:" type 'Select' fields
+ # =================================================================
+
+ def_load_select_options(self,doclist):
+ fordindoclist:
+ ifd.doctype=='DocField'andd.fieldtype=='Select'andd.optionsandd.options[:5].lower()=='link:':
+ op=d.options.split('\n')
+ iflen(op)>1andop[1][:4].lower()=='sql:':
+ ol=webnotes.conn.sql(op[1][4:].replace('__user',webnotes.session['user']))
+ else:
+ t=op[0][5:].strip()
+ op=op[1:]
+ op=[oc.replace('__user',webnotes.session['user'])forocinop]
+
+ try:
+ # select options will always come from the user db
+ ol=webnotes.conn.sql("select name from `tab%s` where %s docstatus!=2 order by name asc"%(t,opand(' AND '.join(op)+' AND ')or''))
+ except:
+ ol=[]
+ ol=['']+[o[0]or''foroinol]
+ d.options='\n'.join(ol)
+
+ # clear un-necessary code from going to the client side
+ # =================================================================
+
+ def_clear_code(self,doclist):
+ ifself.name!='DocType':
+ ifdoclist[0].server_code:doclist[0].server_code=None
+ ifdoclist[0].server_code_core:doclist[0].server_code_core=None
+ ifdoclist[0].client_script:doclist[0].client_script=None
+ ifdoclist[0].client_script_core:doclist[0].client_script_core=None
+ doclist[0].server_code_compiled=None
+
+ # build a list of all the records required for the DocType
+ # =================================================================
+
+ def_get_last_update(self,dt):
+ """
+ Returns last update timestamp
+ """
+ try:
+ last_update=webnotes.conn.sql("select _last_update from tabDocType where name=%s",dt)[0][0]
+ exceptException,e:
+ ife.args[0]==1054:
+ self._setup_last_update(dt)
+ last_update=None
+
+ returnlast_update
+
+ def_setup_last_update(self,doctype):
+ """
+ Adds _last_update column to tabDocType
+
+ """
+ webnotes.conn.commit()
+ webnotes.conn.sql("alter table `tabDocType` add column _last_update varchar(32)")
+ webnotes.conn.begin()
+
+ def_get_fields(self,file_name):
+ """
+ Returns a dictionary of DocFields by fieldname or label
+ """
+ try:
+ doclist=open(file_name,'r').read()
+ except:
+ return
+ doclist=eval(doclist)
+ fields={}
+ fordindoclist:
+ ifd['doctype']=='DocField':
+ ifd['fieldname']ord['label']:
+ fields[d['fieldname']ord['label']]=d
+ returnfields
+
+ def_update_field_properties(self,doclist):
+ """
+ Updates properties like description, depends on from the database based on the timestamp
+ of the .txt file. Adds a column _last_updated if not exists in the database and uses
+ it to update the file..
+
+ This feature is built because description is changed / updated quite often and is tedious to
+ write a patch every time. Can be extended to cover more updates
+ """
+
+ update_fields=('description','depends_on')
+
+ fromwebnotes.modulesimportget_file_timestamp,get_item_file
+
+ doc=doclist[0]# main doc
+ file_name=get_item_file(doc.module,'DocType',doc.name)
+ time_stamp=get_file_timestamp(file_name)
+ last_update=self._get_last_update(doc.name)
+
+ # this is confusing because we are updating the fields of fields
+
+ iflast_update!=time_stamp:
+
+ # there are updates!
+ fields=self._get_fields(file_name)
+ iffields:
+ fordindoclist:
+
+ # for each field in teh outgoing doclist
+ ifd.doctype=='DocField':
+ key=d.fieldnameord.label
+
+ # if it has a fieldname or label
+ ifkeyandkeyinfields:
+
+ # update the values
+ forfield_to_updateinupdate_fields:
+ new_value=fields[key][field_to_update]
+
+ # in doclist
+ d.fields[field_to_update]=new_value
+
+ # in database
+ webnotes.conn.sql("update tabDocField set `%s` = %s where parent=%s and `%s`=%s"% \
+ (field_to_update,'%s','%s',(d.fieldnameand'fieldname'or'label'),'%s'), \
+ (new_value,doc.name,key))
+
+ webnotes.conn.sql("update tabDocType set _last_update=%s where name=%s",(time_stamp,doc.name))
+
+ def_override_field_properties(self,doclist):
+ """
+ Override field properties that are updated by "Property Setter"
+ The term "property" is used to define a fieldname of a field to avoid confusing
+ terminology
+ """
+ # load field properties and add them to a dictionary
+ property_dict={}
+ dt=doclist[0].name
+ try:
+ forfinwebnotes.conn.sql("select doc_name, property, property_type, value from `tabProperty Setter` where doc_type=%s",dt,as_dict=1):
+ ifnotf['doc_name']inproperty_dict:
+ property_dict[f['doc_name']]=[]
+ property_dict[f['doc_name']].append(f)
+ exceptException,e:
+ ife.args[0]==1146:
+ # no override table
+ pass
+ else:
+ raisee
+
+ # loop over fields and override property
+ fordindoclist:
+ ifd.doctype=='DocField'andd.nameinproperty_dict:
+ forpinproperty_dict[d.name]:
+ ifp['property_type']=='Check':
+ d.fields[p['property']]=int(p['value'])
+ else:
+ d.fields[p['property']]=p['value']
+
+ # override properties in the main doctype
+ ifdtinproperty_dict:
+ forpinproperty_dict[self.name]:
+ doclist[0].fields[p['property']]=p['value']
+
+
+ defmake_doclist(self):
+ """
+ returns the :term:`doclist` for consumption by the client
+
+ * it cleans up the server code
+ * executes all `$import` tags in client code
+ * replaces `link:` in the `Select` fields
+ * loads all related `Search Criteria`
+ * updates the cache
+ """
+ fromwebnotes.modulesimportcompress
+
+ tablefields=webnotes.model.meta.get_table_fields(self.name)
+
+ ifself.is_modified():
+ # yes
+ doclist=webnotes.model.doc.get('DocType',self.name,1)
+
+ self._update_field_properties(doclist)
+ self._override_field_properties(doclist)
+
+ # table doctypes
+ fortintablefields:
+ table_doclist=webnotes.model.doc.get('DocType',t[0],1)
+
+ self._override_field_properties(table_doclist)
+ doclist+=table_doclist
+
+ # don't save compiled server code
+
+ else:
+ doclist=self._load_from_cache()
+
+ doclist[0].fields['__client_script']=compress.get_doctype_js(self.name)
+ self._load_select_options(doclist)
+ self._clear_code(doclist)
+
+ returndoclist
+
+
[docs]defclear_cache():
+ webnotes.conn.sql("delete from __DocTypeCache")
+
+# Load "DocType" - called by form builder, report buider and from code.py (when there is no cache)
+#=================================================================================================
+
+
[docs]defget(dt):
+ """
+ Load "DocType" - called by form builder, report buider and from code.py (when there is no cache)
+ """
+ doclist=_DocType(dt).make_doclist()
+
+ returndoclist
+
[docs]defvalidate_doctype(self,dt_list):
+ cl,tables,self.dt_list,self.prompt_autoname_flag=0,[t[0]fortinsql("show tables")],[],0
+ self.msg.append('<p><b>Identifying Documents</b></p>')
+ dtd=sql("select name, istable, autoname from `tabDocType` where name = '%s' "%dt_list[0])
+ ifdtdanddtd[0][0]:
+ self.msg.append('<div style="color: GREEN">Identified Document: '+dt_list[0]+'</div>')
+ self.dt_list.append(dt_list[0])
+ ifdtd[0][2]and'Prompt'indtd[0][2]:self.prompt_autoname_flag=1
+ ifflt(dtd[0][1]):
+ res1=sql("select parent, fieldname from tabDocField where options='%s' and fieldtype='Table' and docstatus!=2"%self.dt_list[0])
+ ifres1andres1[0][0]==dt_list[1]:
+ self.msg.append('<div style="color: GREEN">Identified Document: '+dt_list[1]+'</div>')
+ self.dt_list.append(dt_list[1])
+ else:
+ self.msg.append('<div style="color:RED"> Error: At Row 1, Column 2 => %s is not a valid Document </div>'%dt_list[1])
+ self.validate_success=0
+
+ ifres1andres1[0][1]==dt_list[2]:
+ self.msg.append('<div style="color: GREEN" >Identified Document Fieldname: '+dt_list[2]+'</div>')
+ self.dt_list.append(dt_list[2])
+ else:
+ self.msg.append('<div style="color:RED"> Error: At Row 1, Column 3 => %s is not a valid Fieldname </div>'%dt_list[2])
+ self.validate_success=0
+ elifdt_list[1]:
+ self.msg.append('<div style="color:RED"> Error: At Row 1, Column 1 => %s is not a Table. </div>'%dt_list[0])
+ self.validate_success=0
+ else:
+ self.msg.append('<div style="color:RED"> Error: At Row 1, Column 1 => %s is not a valid Document </div>'%dt_list[0])
+ self.validate_success=0
+
+
+
[docs]defvalidate_fields(self,lb_list):
+ self.msg.append('<p><b>Checking fieldnames for %s</b></p>'%self.dt_list[0])
+ iflen(self.dt_list)>1andself.overwrite:
+ self.msg.append('<div style="color:RED"> Error: Overwrite is not possible for Document %s </div>'%self.dt_list[0])
+ self.validate_success=0
+ return
+
+ elifself.overwriteand'Name'!=lb_list[0]:
+ self.msg.append('<div style="color:RED"> Error : At Row 4 and Column 1: To Overwrite fieldname should be Name </div>')
+ self.validate_success=0
+ return
+ # labelnames
+ res=self.validate_successand[d[0]fordinsql("select label from tabDocField where parent='%s' and docstatus!=2 and ifnull(hidden,'') in ('',0)"%self.dt_list[0])]or[]
+
+ iflen(self.dt_list)>1andself.dt_list[1]:
+ self.fields.append('parent')
+ lb_list.pop(lb_list.index(self.dt_list[1]))
+
+ dtd=sql("select autoname from `tabDocType` where name = '%s' "%self.dt_list[0])[0][0]
+ ifself.prompt_autoname_flagorself.overwrite:
+ self.fields.append('name')
+ res.append('Name')
+ lb_list.pop(lb_list.index('Name'))
+
+ cl=1
+ forlinlb_list:
+ try:
+ ifl:
+ ifnot(linres):
+ self.msg.append('<div style="color: RED">Error : At Row 4 and Column %s Field %s is not present in %s</div>'%(cl,l,self.dt_list[0]))
+ self.validate_success=0
+ # this condition is for child doctype
+
+ else:self.fields.append(sql("select fieldname from tabDocField where parent ='%s' and label = '%s' and ifnull(fieldname,'') !='' "%(self.dt_list[0],l))[0][0]or'')
+ exceptException,e:
+ self.msg.append('<div style="color: RED"> At Row 4 and Column %s : =>ERROR: %s </div>'%(cl,e))
+ self.validate_success=0
+ cl=cl+1
+
+ ifnotself.overwrite:
+ # get_reqd_fields
+ reqd_list=[d[0]fordinsql("select label from `tabDocField` where parent = '%s' and ifnull(reqd,'') not in ('', 0) and docstatus !=2"%self.dt_list[0])ifd[0]notinlb_list]or[]
+
+ # Check if Reqd field not present in self.fields
+ ifreqd_list:
+ self.msg.append('<div style="color: RED"> Error : At Row 4 Mandatory Fields %s of Document %s are Required. </div>'%(reqd_list,self.dt_list[0]))
+ self.validate_success=0
+ ifself.validate_success:
+ self.msg.append('<div style="color: GREEN">Fields OK for %s</div>'%self.dt_list[0])
+
[docs]defparse_date(self,r,c,d):
+ out=''
+ try:
+ ifself.import_date_format=='yyyy-mm-dd':
+ tmpd=d.split('-')
+
+ iflen(tmpd)==3:
+ out=tmpd[0]+'-'+tmpd[1]+'-'+tmpd[2]
+
+ elifdandself.import_date_format=='dd-mm-yyyy':
+ tmpd=d.split('-')
+
+ iflen(tmpd)==3:
+ out=tmpd[2]+'-'+tmpd[1]+'-'+tmpd[0]
+
+ elifdandself.import_date_format=='mm/dd/yyyy':
+ tmpd=d.split('/')
+
+ iflen(tmpd)==3:
+ out=tmpd[2]+'-'+tmpd[0]+'-'+tmpd[1]
+
+ elifdandself.import_date_format=='mm/dd/yy':
+ tmpd=d.split('/')
+
+ iflen(tmpd)==3:
+ out='20'+tmpd[2]+'-'+tmpd[0]+'-'+tmpd[1]
+
+ elifdandself.import_date_format=='dd/mm/yyyy':
+ tmpd=d.split('/')
+ iflen(tmpd)==3:
+ out=tmpd[2]+'-'+tmpd[1]+'-'+tmpd[0]
+
+ elifdandself.import_date_format=='dd/mm/yy':
+ tmpd=d.split('/')
+ iflen(tmpd)==3:
+ out='20'+tmpd[2]+'-'+tmpd[1]+'-'+tmpd[0]
+
+ iflen(tmpd)!=3:
+ self.msg.append('<div style="color: RED"> At Row %s and Column %s : => Date Format selected as %s does not match with Date Format in File</div>'%(r,c,str(self.import_date_format)))
+ self.validate_success=0
+ else:
+
+ importdatetime
+ dt=out.split('-')
+ datetime.date(int(dt[0]),int(dt[1]),int(dt[2]))
+ exceptException,e:
+ self.msg.append('<div style="color: RED"> At Row %s and Column %s : =>ERROR: %s </div>'%(r,c,e))
+ self.validate_success=0
+ self.msg.append(out)
+ returnout
+
+
[docs]defcheck_select_link_data(self,r,c,f,d,s='',l=''):
+ options=''
+ try:
+ ifdandf:
+ dt=sql("select options, label from `tabDocField` where fieldname ='%s' and parent = '%s' "%(f,self.dt_list[0]))
+ dt,lbl=(dtanddt[0][0]anddt[0][0].strip()orNone),dtanddt[0][1].strip()
+ ifdt:
+ options=landdtand[n[0]forninsql("select name from `tab%s` "%(('link:'indtanddt[5:])ordt))]orsanddt.split('\n')or''
+ ifoptionsanddnotinoptions:
+ msg='<div style="color: RED">At Row '+str(r)+' and Column '+str(c)+' : => Data "'+str(d)+'" in field ['+str(lbl)+'] Not Found in '
+ msg=msg.__add__(sandstr('Select Options ['+str(dt.replace('\n',','))+']')orstr('Master '+str('link:'indtanddt[5:]ordt)))
+ msg=msg.__add__('</div>\n')
+ self.msg.append(msg)
+
+ self.validate_success=0
+ exceptException,e:
+ self.msg.append('<div style="color: RED"> ERROR: %s </div>'%(str(webnotes.utils.getTraceback())))
+ self.validate_success=0
+ returnd
+
+
+
[docs]defget_field_type_list(self):
+ # get_date_fields
+ date_list=[d[0]fordinsql("select fieldname from `tabDocField` where parent = '%s' and fieldtype='Date' and docstatus !=2"%self.dt_list[0])]
+
+ # get_link_fields
+ link_list=[d[0]fordinsql("select fieldname from `tabDocField` where parent = '%s' and ((fieldtype='Link' and ifnull(options,'') != '') or (fieldtype='Select' and ifnull(options,'') like '%%link:%%')) and docstatus !=2 "%self.dt_list[0])]
+
+ # get_select_fileds
+ select_list=[d[0]fordinsql("select fieldname from `tabDocField` where parent = '%s' and fieldtype='Select' and ifnull(options,'') not like '%%link:%%' and docstatus !=2"%self.dt_list[0])]
+
+ # get_reqd_fields
+ reqd_list=self.overwriteand['name']or[d[0]fordinsql("select fieldname from `tabDocField` where parent = '%s' and ifnull(reqd,'') not in ('', 0) and docstatus !=2"%self.dt_list[0])]
+
+ iflen(self.dt_list)>1and'parent'notinreqd_list:reqd_list.append('parent')
+
+ ifself.prompt_autoname_flagand'name'notinreqd_list:reqd_list.append('name')
+
+ returndate_list,link_list,select_list,reqd_list
+
+
+
[docs]defvalidate_data(self):
+ self.msg.append('<p><b>Checking Data for %s</b></p>'%self.dt_list[0])
+ date_list,link_list,select_list,reqd_list=self.get_field_type_list()
+
+
+ # load data
+ row=5
+ fordinself.data:
+ self.validate_success,fd,col=1,{},1
+ self.msg.append('<p><b>Checking Row %s </b></p>'%(row))
+ foriinrange(len(d)):
+ ifi<len(self.fields):
+ f=self.fields[i]
+ try:
+ # Check Reqd Fields
+ if(finreqd_list)andnotd[i]:
+ self.msg.append('<div style="color: RED">Error: At Row %s and Column %s, Field %s is Mandatory.</div>'%(row,col,f))
+ self.validate_success=0
+
+ # Check Date Fields
+ ifd[i]andfandfindate_list:fd[f]=self.parse_date(row,col,d[i])
+
+ # Check Link Fields
+ elifd[i]andfinlink_list:
+ fd[f]=self.check_select_link_data(row,col,f,d[i],l='Link')
+
+ # Check Select Fields
+ elifd[i]andfinselect_list:
+ fd[f]=self.check_select_link_data(row,col,f,d[i],s='Select')
+
+ # Need To Perform Check For Other Data Type Too
+ else:fd[f]=d[i]
+
+ exceptException:
+ self.msg.append('<div style="color: RED"> ERROR: %sData:%s and %s and %s and %s</div>'%(str(webnotes.utils.getTraceback())+'\n',str(d),str(f),str(date_list),str(link_list)))
+ self.validate_success=0
+ elifd[i]:
+ self.validate_success=0
+ self.msg.append('<div style="color: RED">At Row %s and Column %s</div>'%(row,col))
+ self.msg.append('<div style="color: ORANGE">Ignored</div>')
+ col=col+1
+ ifself.validate_success:
+ self.msg.append('<div style="color: GREEN">At Row %s and Column %s, Data Verification Completed </div>'%(row,col))
+ self.update_data(fd,row)
+ row=row+1
+
[docs]defget_dt_values(doctype,fields,as_dict=0):
+ returnwebnotes.conn.sql('SELECT %s FROM tabDocType WHERE name="%s"'%(fields,doctype),as_dict=as_dict)
+
[docs]defget_parent_dt(dt):
+ parent_dt=webnotes.conn.sql('select parent from tabDocField where fieldtype="Table" and options="%s" and (parent not like "old_parent:%%") limit 1'%dt)
+ returnparent_dtandparent_dt[0][0]or''
+
+#=================================================================================
+
[docs]defget_link_fields(doctype):
+ returnwebnotes.conn.sql("SELECT fieldname, options, label FROM tabDocField WHERE parent='%s' and (fieldtype='Link' or (fieldtype='Select' and `options` like 'link:%%'))"%(doctype))
+
+#=================================================================================
+
+
[docs]defget_table_fields(doctype):
+ returnwebnotes.conn.sql("select options, fieldname from tabDocField where parent='%s' and fieldtype='Table'"%doctype)
+
+#=================================================================================
+
+
[docs]defget_search_criteria(dt):
+ importwebnotes.model.doc
+ # load search criteria for reports (all)
+ dl=[]
+ sc_list=webnotes.conn.sql("select name from `tabSearch Criteria` where doc_type = '%s' or parent_doc_type = '%s' and (disabled!=1 OR disabled IS NULL)"%(dt,dt))
+ forscinsc_list:
+ ifsc[0]:
+ dl+=webnotes.model.doc.get('Search Criteria',sc[0])
+ returndl
+
+#=================================================================================
+
+
[docs]defget_print_format_html(name):
+ html=webnotes.conn.sql('select html from `tabPrint Format` where name="%s"'%name)
+ returnhtmlandhtml[0][0]or''
+
[docs]defadd_trigger(doctype,docname,event_name,method):
+ """Add a trigger to an event on a doctype, docname. The specified method will be called.
+ Wild card '*' is allowed in doctype, docname and/or event_name"""
+
+ try:
+ ifnottrigger_exists(doctype,docname,event_name,method):
+ insert_trigger(doctype,docname,event_name,method)
+ exceptException,e:
+ ife.args[0]==1146:
+ setup()
+ insert_trigger(doctype,docname,event_name,method)
+ else:raisee
+
+
[docs]deftrigger_exists(doctype,docname,event_name,method):
+ "returns true if trigger exists"
+ returnwebnotes.conn.sql("select name from tabDocTrigger where doc_name=%s and doc_type=%s and event_name=%s and method=%s", \
+ (doctype,docname,event_name,method))
+
+
[docs]defremove_trigger(doctype,docname,event_name,method):
+ "Remove a trigger on an event"
+ webnotes.conn.sql("delete from tabDocTrigger where doc_name=%s and doc_type=%s and event_name=%s and method=%s", \
+ (doctype,docname,event_name,method))
+
+
[docs]deffire_event(doc,event_name):
+ "Fire all triggers on an event and passes the doc to the trigger"
+ try:
+ tl=webnotes.conn.sql("""select method from tabDocTrigger
+ where (doc_type=%s or doc_type='*')
+ and (doc_name=%s or doc_name='*')
+ and (event_name=%s or event_name='*')""",(doc.doctype,doc.name,event_name))
+ exceptException,e:
+ ife.args[0]==1146:
+ setup()
+ tl=[]
+ else:raisee
+
+ fortintl:
+ module,method='.'.join(t[0].split('.')[:-1]),t[0].split('.')[-1]
+ exec'from %s import %s'%(module,method)inlocals()
+ locals()[method](doc)
+#
+# setup
+#
+
[docs]defsetup():
+ "creates the DocTrigger table from core"
+ webnotes.conn.commit()
+ fromwebnotes.modules.module_managerimportreload_doc
+ reload_doc('core','doctype','doctrigger')
+ webnotes.conn.begin()
+
[docs]defscrub_dt_and_dn(dt,dn):
+ """
+ Returns in lowercase and code friendly names of doctype and name for certain types
+ """
+ ndt,ndn=dt,dn
+ ifdt.lower()in('doctype','search criteria','page'):
+ ndt,ndn=scrub(dt),scrub(dn)
+
+ returnndt,ndn
+
+
[docs]defget_item_file(module,dt,dn):
+ """
+ Returns the path of the item file
+ """
+ importos
+ ndt,ndn=scrub_dt_and_dn(dt,dn)
+
+ returnos.path.join(get_module_path(module),ndt,ndn,ndn+'.txt')
+
+
[docs]defget_item_timestamp(module,dt,dn):
+ """
+ Return ths timestamp of the given item (if exists)
+ """
+ returnget_file_timestamp(get_item_file(module,dt,dn))
+
[docs]defget_module_path(module):
+ """
+ Returns path of the given module (imports it and reads it from __file__)
+ """
+ importwebnotes.defs,os,os.path
+
+ try:
+ exec('import '+scrub(module))inlocals()
+ modules_path=eval(scrub(module)+'.__file__')
+
+ modules_path=os.path.sep.join(modules_path.split(os.path.sep)[:-1])
+ exceptImportError,e:
+ # get module path by importing the module
+ modules_path=os.path.join(webnotes.defs.modules_path,scrub(module))
+
+ returnmodules_path
+
+
[docs]defswitch_module(dt,dn,to,frm=None,export=None):
+ """
+ Change the module of the given doctype, if export is true, then also export txt and copy
+ code files from src
+ """
+ importos
+ webnotes.conn.sql("update `tab"+dt+"` set module=%s where name=%s",(to,dn))
+
+ ifexport:
+ export_doc(dt,dn)
+
+ # copy code files
+ ifdtin('DocType','Page','Search Criteria'):
+ from_path=os.path.join(get_module_path(frm),scrub(dt),scrub(dn),scrub(dn))
+ to_path=os.path.join(get_module_path(to),scrub(dt),scrub(dn),scrub(dn))
+
+ # make dire if exists
+ os.system('mkdir -p %s'%os.path.join(get_module_path(to),scrub(dt),scrub(dn)))
+
+ forextin('py','js','html','css'):
+ os.system('cp %s%s')
+
[docs]defget_page_js(page,module=None):
+ """
+ Returns the js code of a page. Will replace $import (page) or $import(module.page)
+ with the code from the file
+ """
+ importwebnotes,os
+ fromwebnotes.modulesimportscrub,get_module_path
+
+ iftype(page)==str:
+ page_name=page
+ else:
+ page_name,module=page.name,page.module
+
+ code=get_js_code(os.path.join(get_module_path(module),'page',scrub(page_name),scrub(page_name)))
+
+ ifnotcodeandtype(page)!=str:
+ code=page.script
+
+ # compile for import
+ ifcodeandcode.strip():
+ importre
+ p=re.compile('\$import\( (?P<name> [^)]*) \)',re.VERBOSE)
+
+ code=p.sub(sub_get_page_js,code)
+
+ returncode
+
+
+# compress
+#==============================================================================
+
+
[docs]defcompress(src,comp):
+ out=open(comp,'w')
+
+ jsm=JavascriptMinify()
+ jsm.minify(open(src,'r'),out)
+
+ out.close()
+
+
+
+
+#==============================================================================
+#==============================================================================
+#
+# This code is original from jsmin by Douglas Crockford, it was translated to
+# Python by Baruch Even. The original code had the following copyright and
+# license.
+#
+# /* jsmin.c
+# 2007-05-22
+#
+# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# The Software shall be used for Good, not Evil.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# */
+
[docs]defisAlphanum(c):
+ """return true if the character is a letter, digit, underscore,
+ dollar sign, or non-ASCII character.
+ """
+ return((c>='a'andc<='z')or(c>='0'andc<='9')or
+ (c>='A'andc<='Z')orc=='_'orc=='$'orc=='\\'or(cisnotNoneandord(c)>126));
+
[docs]classJavascriptMinify(object):
+
+ def_outA(self):
+ self.outstream.write(self.theA)
+ def_outB(self):
+ self.outstream.write(self.theB)
+
+ def_get(self):
+ """return the next character from stdin. Watch out for lookahead. If
+ the character is a control character, translate it to a space or
+ linefeed.
+ """
+ c=self.theLookahead
+ self.theLookahead=None
+ ifc==None:
+ c=self.instream.read(1)
+ ifc>=' 'orc=='\n':
+ returnc
+ ifc=='':# EOF
+ return'\000'
+ ifc=='\r':
+ return'\n'
+ return' '
+
+ def_peek(self):
+ self.theLookahead=self._get()
+ returnself.theLookahead
+
+ def_next(self):
+ """get the next character, excluding comments. peek() is used to see
+ if an unescaped '/' is followed by a '/' or '*'.
+ """
+ c=self._get()
+ ifc=='/'andself.theA!='\\':
+ p=self._peek()
+ ifp=='/':
+ c=self._get()
+ whilec>'\n':
+ c=self._get()
+ returnc
+ ifp=='*':
+ c=self._get()
+ while1:
+ c=self._get()
+ ifc=='*':
+ ifself._peek()=='/':
+ self._get()
+ return' '
+ ifc=='\000':
+ raiseUnterminatedComment()
+
+ returnc
+
+ def_action(self,action):
+ """do something! What you do is determined by the argument:
+ 1 Output A. Copy B to A. Get the next B.
+ 2 Copy B to A. Get the next B. (Delete A).
+ 3 Get the next B. (Delete B).
+ action treats a string as a single character. Wow!
+ action recognizes a regular expression if it is preceded by ( or , or =.
+ """
+ ifaction<=1:
+ self._outA()
+
+ ifaction<=2:
+ self.theA=self.theB
+ ifself.theA=="'"orself.theA=='"':
+ while1:
+ self._outA()
+ self.theA=self._get()
+ ifself.theA==self.theB:
+ break
+ ifself.theA<='\n':
+ raiseUnterminatedStringLiteral()
+ ifself.theA=='\\':
+ self._outA()
+ self.theA=self._get()
+
+
+ ifaction<=3:
+ self.theB=self._next()
+ ifself.theB=='/'and(self.theA=='('orself.theA==','or
+ self.theA=='='orself.theA==':'or
+ self.theA=='['orself.theA=='?'or
+ self.theA=='!'orself.theA=='&'or
+ self.theA=='|'orself.theA==';'or
+ self.theA=='{'orself.theA=='}'or
+ self.theA=='\n'):
+ self._outA()
+ self._outB()
+ while1:
+ self.theA=self._get()
+ ifself.theA=='/':
+ break
+ elifself.theA=='\\':
+ self._outA()
+ self.theA=self._get()
+ elifself.theA<='\n':
+ raiseUnterminatedRegularExpression()
+ self._outA()
+ self.theB=self._next()
+
+
+ def_jsmin(self):
+ """Copy the input to the output, deleting the characters which are
+ insignificant to JavaScript. Comments will be removed. Tabs will be
+ replaced with spaces. Carriage returns will be replaced with linefeeds.
+ Most spaces and linefeeds will be removed.
+ """
+ self.theA='\n'
+ self._action(3)
+
+ whileself.theA!='\000':
+ ifself.theA==' ':
+ ifisAlphanum(self.theB):
+ self._action(1)
+ else:
+ self._action(2)
+ elifself.theA=='\n':
+ ifself.theBin['{','[','(','+','-']:
+ self._action(1)
+ elifself.theB==' ':
+ self._action(3)
+ else:
+ ifisAlphanum(self.theB):
+ self._action(1)
+ else:
+ self._action(2)
+ else:
+ ifself.theB==' ':
+ ifisAlphanum(self.theA):
+ self._action(1)
+ else:
+ self._action(3)
+ elifself.theB=='\n':
+ ifself.theAin['}',']',')','+','-','"','\'']:
+ self._action(1)
+ else:
+ ifisAlphanum(self.theA):
+ self._action(1)
+ else:
+ self._action(3)
+ else:
+ self._action(1)
+
+
+"""
+Imports Documents from modules (.txt) files in the filesystem
+"""
+
+importwebnotes
+
+#
+# imports / updates all files in a module into the database
+#
+
[docs]defimport_module(module,verbose=0):
+ "imports the all the records and files from the given module"
+ fromwebnotes.modulesimportget_module_path
+ importos
+
+ not_module=('startup','event_handlers','files','patches')
+ ifmoduleinnot_module:
+ ifverbose:webnotes.msgprint('%s is not a module'%module)
+ return
+
+ path=get_module_path(module)
+
+ doctypes=listfolders(path,1)
+ if'doctype'indoctypes:
+ doctypes.remove('doctype')
+ doctypes=['doctype']+doctypes
+
+ fordoctypeindoctypes:
+ fordocnameinlistfolders(os.path.join(path,doctype),1):
+ import_file(module,doctype,docname,path)
+ ifverbose:webnotes.msgprint('Imported %s/%s/%s'%(module,doctype,docname))
+
+ import_attachments(module)
+
+#
+# get doclist from file
+#
+
[docs]defget_doclist(path,doctype,docname):
+ "returns a doclist (list of dictionaries) of multiple records for the given parameters"
+ importos
+ do_not_import=('control_panel')
+
+ fname=os.path.join(path,doctype,docname,docname+'.txt')
+ ifos.path.exists(fname)and(doctypenotindo_not_import):
+ f=open(fname,'r')
+ dl=eval(f.read())
+ f.close()
+ returndl
+ else:
+ returnNone
+
+#
+# import a file into the database
+#
+
[docs]defimport_file(module,doctype,docname,path=None):
+ "imports a given file into the database"
+
+ ifnotpath:
+ fromwebnotes.modulesimportget_module_path
+ path=get_module_path(module)
+
+ doclist=get_doclist(path,doctype,docname)
+
+ ifdoclist:
+ fromwebnotes.utils.transferimportset_doc
+ set_doc(doclist,1,1,1)
+
+#
+# list folders in a dir
+#
+
[docs]deflistfolders(path,only_name=0):
+ """returns the list of folders (with paths) in the given path,
+ if only_name is set, it returns only the folder names"""
+
+ importos
+ out=[]
+ foreachinos.listdir(path):
+ dirname=each.split(os.path.sep)[-1]
+ fullpath=os.path.join(path,dirname)
+
+ ifos.path.isdir(fullpath)andnotdirname.startswith('.'):
+ out.append(only_nameanddirnameorfullname)
+ returnout
+
+
+
+
+
+
+
+# ==============================================================================
+# Import from files
+# =============================================================================
+
[docs]defimport_from_files(modules=[],record_list=[],sync_cp=0,target_db=None,target_ac=None):
+
+ iftarget_dbortarget_ac:
+ init_db_login(target_ac,target_db)
+
+ fromwebnotes.utilsimporttransfer
+ # Get paths of folder which will be imported
+ folder_list=get_folder_paths(modules,record_list)
+ ret=[]
+
+ iffolder_list:
+ # get all doclist
+ all_doclist=get_all_doclist(folder_list)
+
+ # import doclist
+ ret+=accept_module(all_doclist)
+
+ # import attachments
+ forminmodules:
+ import_attachments(m)
+
+ # sync control panel
+ ifsync_cp:
+ ret.append(sync_control_panel())
+ else:
+ ret.append("Module/Record not found")
+
+ returnret
+
+
+# ==============================================================================
+# Get list of folder path
+# =============================================================================
+# record_list in format [[module,dt,dn], ..]
+
[docs]defget_folder_paths(modules,record_list):
+ importos
+ importwebnotes
+ importfnmatch
+ importwebnotes.defs
+ fromwebnotes.modulesimporttransfer_types,get_module_path,scrub
+
+ folder_list=[]
+
+ # get the folder list
+ ifrecord_list:
+ forrecordinrecord_list:
+ ifscrub(record[1])in('doctype','page','search_criteria'):
+ record[1],record[2]=scrub(record[1]),scrub(record[2])
+
+ folder_list.append(os.path.join(get_module_path(scrub(record[0])), \
+ record[1],record[2].replace('/','_')))
+
+ ifmodules:
+ # system modules will be transferred in a predefined order and before all other modules
+ sys_mod_ordered_list=['roles','core','application_internal','mapper','settings']
+ all_mod_ordered_list=[tfortinsys_mod_ordered_listiftinmodules]+list(set(modules).difference(sys_mod_ordered_list))
+
+ formoduleinall_mod_ordered_list:
+ mod_path=get_module_path(module)
+ types_list=listfolders(mod_path,1)
+
+ # list of types
+ types_list=list(set(types_list).difference(['control_panel']))
+ all_transfer_types=[tfortintransfer_typesiftintypes_list]+list(set(types_list).difference(transfer_types))
+
+ # build the folders
+ fordinall_transfer_types:
+ ifdnotin('files','startup','patches'):
+ # get all folders inside type
+ folder_list+=listfolders(os.path.join(mod_path,d))
+
+ returnfolder_list
+
+
+# ==============================================================================
+# Get doclist for all folder
+# =============================================================================
+
+
+
[docs]defget_all_doclist(folder_list):
+ importfnmatch
+ importos
+
+ doclist=[]
+ all_doclist=[]
+
+ # build into doclist
+ forfolderinfolder_list:
+ # get the doclist
+ file_list=os.listdir(folder)
+ foreachinfile_list:
+
+ iffnmatch.fnmatch(each,'*.txt'):
+ doclist=eval(open(os.path.join(folder,each),'r').read())
+ # add code
+ all_doclist.append(doclist)
+
+ returnall_doclist
+
+
+# ==============================================================================
+# accept a module coming from a remote server
+# ==============================================================================
+#==============================================================================
+# SYNC
+#==============================================================================
+defreload_doc(module,dt,dn):
+ "alias for webnotes.modules.import_module.import_file"
+
[docs]fromwebnotes.modules.import_moduleimportimport_file
+
+ import_file(module,dt,dn)
+
+#
+# get list of doctypes and their last update times
+#
+defget_doc_list(dt):
+ """
+
[docs] returns the list of records and their last update times from the table
+ if the column _last_update does not exist, it will add it to the table
+ """
+
+ importwebnotes
+ module=dt=='Module Def'and'name'or'module'
+ q="select %s, name, _last_update from `tab%s`"%(module,dt)
+ try:
+ returnwebnotes.conn.sql(q)
+ exceptException,e:
+ ife.args[0]==1054:
+ webnotes.conn.commit()
+ webnotes.conn.sql("alter table `tab%s` add column _last_update varchar(32)"%dt)
+ webnotes.conn.begin()
+ returnwebnotes.conn.sql(q)
+ elife.args[0]==1146:
+ return[]
+ else:
+ raisee
+
+#
+# sync dt
+#
+defsync_one_doc(d,dt,ts):
+ importwebnotes
+
[docs]fromwebnotes.model.db_schemaimportupdatedb
+ reload_doc(d[0],dt,d[1])
+
+ # update schema(s)
+ ifdt=='DocType':
+ updatedb(d[1])
+ webnotes.conn.sql("update `tab%s` set _last_update=%s where name=%s"%(dt,'%s','%s'),(ts,d[1]))
+
+#
+# sync doctypes, mappers and
+#
+defsync_meta():
+ importwebnotes,os
[docs]classProfile:
+ """
+ A profile object is created at the beginning of every request with details of the use.
+ The global profile object is `webnotes.user`
+ """
+ def__init__(self,name=''):
+ self.name=nameorwebnotes.session.get('user')
+ self.roles=[]
+
+ self.can_create=[]
+ self.can_read=[]
+ self.can_write=[]
+ self.can_get_report=[]
+
+ def_load_roles(self):
+ res=webnotes.conn.sql('select role from tabUserRole where parent = "%s"'%self.name)
+ self.roles=[]
+ fortinres:
+ ift[0]:self.roles.append(t[0])
+ ifwebnotes.session.get('user')=='Guest':
+ self.roles.append('Guest')
+ else:
+ self.roles.append('All')
+
+ returnself.roles
+
+
[docs]defget_roles(self):
+ """
+ get list of roles
+ """
+ ifself.roles:
+ returnself.roles
+
+ returnself._load_roles()
+
+
[docs]defget_allow_list(self,key):
+ """
+ Internal - get list of DocType where `key` is allowed. Key is either 'read', 'write' or 'create'
+ """
+ conn=webnotes.conn
+ roles=self.get_roles()
+ return[r[0]forrinconn.sql('SELECT DISTINCT t1.parent FROM `tabDocPerm` t1, tabDocType t2 WHERE t1.`%s`=1 AND t1.parent not like "old_parent:%%" AND t1.parent = t2.name AND IFNULL(t2.istable,0) = 0 AND t1.role in ("%s") order by t1.parent'%(key,'", "'.join(roles)))]
+
+
[docs]defget_create_list(self):
+ """
+ Get list of DocTypes the user can create. Will filter DocTypes tagged with 'not_in_create' and table
+ """
+ cl=self.get_allow_list('create')
+ conn=webnotes.conn
+ no_create_list=[r[0]forrinconn.sql('select name from tabDocType where ifnull(in_create,0)=1 or ifnull(istable,0)=1 or ifnull(issingle,0)=1')]
+ self.can_create=filter(lambdax:xnotinno_create_list,cl)
+ returnself.can_create
+
+
[docs]defget_read_list(self):
+ """
+ Get list of DocTypes the user can read
+ """
+ self.can_read=list(set(self.get_allow_list('read')+self.get_allow_list('write')))
+ returnself.can_read
+
+
[docs]defget_report_list(self):
+
+ conn=webnotes.conn
+
+ # get all tables list
+ res=conn.sql('SELECT parent, options from tabDocField where fieldtype="Table"')
+ table_types,all_tabletypes={},[]
+
+ # make a dictionary fo all table types
+ fortinres:
+ all_tabletypes.append(t[1])
+ ifnottable_types.has_key(t[0]):
+ table_types[t[0]]=[]
+ table_types[t[0]].append(t[1])
+
+ no_search_list=[r[0]forrinconn.sql('SELECT name FROM tabDocType WHERE read_only = 1 ORDER BY name')]
+ # make the lists
+ forfinself.can_read:
+ tl=table_types.get(f,None)
+ iftl:
+ fortintl:
+ iftand(nottinself.can_get_report)and(nottinno_search_list):
+ self.can_get_report.append(t)
+
+ iffand(notfinself.can_get_report)and(notfinno_search_list):
+ self.can_get_report.append(f)
+
+ returnself.can_get_report
+
+
[docs]defget_write_list(self):
+ """
+ Get list of DocTypes the user can write
+ """
+ self.can_write=self.get_allow_list('write')
+ returnself.can_write
+
+
[docs]defget_home_page(self):
+ """
+ Get the name of the user's home page from the `Control Panel`
+ """
+ try:
+ hpl=webnotes.conn.sql("select role, home_page from `tabDefault Home Page` where parent='Control Panel' order by idx asc")
+ forhinhpl:
+ ifh[0]inself.get_roles():
+ returnh[1]
+ except:
+ pass
+ returnwebnotes.conn.get_value('Control Panel',None,'home_page')
+
+
[docs]defget_defaults(self):
+ """
+ Get the user's default values based on user and role profile
+ """
+ roles=self.get_roles()+[self.name]
+ res=webnotes.conn.sql('select defkey, defvalue from `tabDefaultValue` where parent in ("%s")'%'", "'.join(roles))
+
+ self.defaults={'owner':[self.name,]}
+
+ forrecinres:
+ ifnotself.defaults.has_key(rec[0]):
+ self.defaults[rec[0]]=[]
+ self.defaults[rec[0]].append(rec[1]or'')
+
+ returnself.defaults
+
+
[docs]defget_hide_tips(self):
+ try:
+ returnwebnotes.conn.sql("select hide_tips from tabProfile where name=%s",self.name)[0][0]or0
+ except:
+ return0
+
[docs]defupdate_recent(self,dt,dn):
+ """
+ Update the user's `Recent` list with the given `dt` and `dn`
+ """
+ conn=webnotes.conn
+
+ # get list of child tables, so we know what not to add in the recent list
+ child_tables=[t[0]fortinconn.sql('select name from tabDocType where istable = 1')]
+ ifnot(dtin['Print Format','Start Page','Event','ToDo Item','Search Criteria'])andnotwebnotes.is_testingandnot(dtinchild_tables):
+ r=webnotes.conn.sql("select recent_documents from tabProfile where name=%s",self.name)[0][0]or''
+ new_str=dt+'~~~'+dn+'\n'
+ ifnew_strinr:
+ r=r.replace(new_str,'')
+
+ self.recent=new_str+r
+
+ iflen(self.recent.split('\n'))>50:
+ self.recent='\n'.join(self.recent.split('\n')[:50])
+
+ webnotes.conn.sql("update tabProfile set recent_documents=%s where name=%s",(self.recent,self.name))
+
+
[docs]defload_profile(self):
+ """
+ Return a dictionary of user properites to be stored in the session
+ """
+ t=webnotes.conn.sql('select email, first_name, last_name, recent_documents from tabProfile where name = %s',self.name)[0]
+
+ d={}
+ d['name']=self.name
+ d['email']=t[0]or''
+ d['first_name']=t[1]or''
+ d['last_name']=t[2]or''
+ d['recent']=t[3]or''
+
+ d['hide_tips']=self.get_hide_tips()
+
+ d['roles']=self.get_roles()
+ d['defaults']=self.get_defaults()
+
+ d['can_create']=self.get_create_list()
+ d['can_read']=self.get_read_list()
+ d['can_write']=self.get_write_list()
+ d['can_get_report']=self.get_report_list()
+
+ returnd
+
+
[docs]defload_from_session(self,d):
+ """
+ Setup the user profile from the dictionary saved in the session (generated by `load_profile`)
+ """
+ self.can_create=d['can_create']
+ self.can_read=d['can_read']
+ self.can_write=d['can_write']
+ self.can_get_report=d['can_get_report']
+
+ self.roles=d['roles']
+ self.defaults=d['defaults']
+
+"""
+Run tests from modules. Sets up database connection, modules path and session before running test
+
+Usage: from shell, run
+
+python tests.py [test modules]
+
+Options:
+ test modules: list of modules separated by space
+
+if no modules are specified, it will run all "tests.py" files from all modules
+"""
+
+importsys,os
+importunittest
+
+# webnotes path
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),'..')))
+
+# modules path
+importwebnotes
+importwebnotes.defs
+
+ifwebnotes.defs.__dict__.get('modules_path'):
+ sys.path.append(webnotes.defs.modules_path)
+
+
[docs]defget_tests():
+ """
+ Returns list of test modules identified by "tests.py"
+ """
+ ret=[]
+ forwalk_tupleinos.walk(webnotes.defs.modules_path):
+ if'tests.py'inwalk_tuple[2]:
+ dir_path=os.path.relpath(walk_tuple[0],webnotes.defs.modules_path)
+ ifdir_path=='.':
+ ret.append('tests')
+ else:
+ ret.append(dir_path.replace('/','.')+'.tests')
+
+ returnret
+
+
[docs]defsetup():
+ """
+ Sets up connection and session
+ """
+ fromwebnotes.dbimportDatabase
+ webnotes.conn=Database()
+ webnotes.session={'user':'Administrator'}
+
[docs]defsendmail(recipients,sender='',msg='',subject='[No Subject]',parts=[],cc=[],attach=[]):
+ """
+ Send an email. For more details see :func:`email_lib.sendmail`
+ """
+ importwebnotes.utils.email_lib
+ returnemail_lib.sendmail(recipients,sender,msg,subject,parts,cc,attach)
+
+
[docs]defgenerate_hash():
+ """
+ Generates reandom hash for session id
+ """
+ importsha,time
+ returnsha.new(str(time.time())).hexdigest()
+
+
[docs]defdb_exists(dt,dn):
+ returnwebnotes.conn.sql('select name from `tab%s` where name="%s"'%(dt,dn))
+
[docs]deflog(event,details):
+ webnotes.logger.info(details)
+
+# Date and Time
+# ==============================================================================
+
+
[docs]defget_first_day(dt,d_years=0,d_months=0):
+ """
+ Returns the first day of the month for the date specified by date object
+ Also adds `d_years` and `d_months` if specified
+ """
+ importdatetime
+ # d_years, d_months are "deltas" to apply to dt
+ y,m=dt.year+d_years,dt.month+d_months
+ a,m=divmod(m-1,12)
+ returndatetime.date(y+a,m+1,1)
+
+
[docs]defget_last_day(dt):
+ """
+ Returns last day of the month using:
+ `get_first_day(dt, 0, 1) + datetime.timedelta(-1)`
+ """
+ importdatetime
+ returnget_first_day(dt,0,1)+datetime.timedelta(-1)
+
+user_format=None
+"""
+ User format specified in :term:`Control Panel`
+
+ Examples:
+
+ * dd-mm-yyyy
+ * mm-dd-yyyy
+ * dd/mm/yyyy
+"""
+
+
[docs]defformatdate(string_date):
+ """
+ Convers the given string date to :data:`user_format`
+ """
+ globaluser_format
+ ifnotuser_format:
+ user_format=webnotes.conn.get_value('Control Panel',None,'date_format')
+ d=string_date.split('-');
+ out=user_format
+ returnout.replace('dd',('%.2i'%cint(d[2]))).replace('mm',('%.2i'%cint(d[1]))).replace('yyyy',d[0])
+
[docs]defget_defaults(key=None):
+ """
+ Get dictionary of default values from the :term:`Control Panel`, or a value if key is passed
+ """
+ ifkey:
+ res=webnotes.conn.sql('select defvalue from `tabDefaultValue` where parent = "Control Panel" where defkey=%s',key)
+ returnresandres[0][0]orNone
+ else:
+ res=webnotes.conn.sql('select defkey, defvalue from `tabDefaultValue` where parent = "Control Panel"')
+ d={}
+ forrecinres:
+ d[rec[0]]=rec[1]or''
+ returnd
+
+
[docs]defset_default(key,val):
+ """
+ Set / add a default value to :term:`Control Panel`
+ """
+ res=webnotes.conn.sql('select defkey from `tabDefaultValue` where defkey="%s" and parent = "Control Panel"'%key)
+ ifres:
+ webnotes.conn.sql('update `tabDefaultValue` set defvalue="%s" where parent = "Control Panel" and defkey="%s"'%(val,key))
+ else:
+ fromwebnotes.model.docimportDocument
+ d=Document('DefaultValue')
+ d.parent='Control Panel'
+ d.parenttype='Control Panel'
+ d.parentfield='system_defaults'
+ d.defkey=key
+ d.defvalue=val
+ d.save(1)
+
+#
+# Clear recycle bin
+#
+
[docs]defclear_recycle_bin():
+ sql=webnotes.conn.sql
+
+ tl=sql('show tables')
+ total_deleted=0
+ fortintl:
+ fl=[i[0]foriinsql('desc `%s`'%t[0])]
+
+ if'name'infl:
+ total_deleted+=sql("select count(*) from `%s` where name like '__overwritten:%%'"%t[0])[0][0]
+ sql("delete from `%s` where name like '__overwritten:%%'"%t[0])
+
+ if'parent'infl:
+ total_deleted+=sql("select count(*) from `%s` where parent like '__oldparent:%%'"%t[0])[0][0]
+ sql("delete from `%s` where parent like '__oldparent:%%'"%t[0])
+
+ total_deleted+=sql("select count(*) from `%s` where parent like 'oldparent:%%'"%t[0])[0][0]
+ sql("delete from `%s` where parent like 'oldparent:%%'"%t[0])
+
+ total_deleted+=sql("select count(*) from `%s` where parent like 'old_parent:%%'"%t[0])[0][0]
+ sql("delete from `%s` where parent like 'old_parent:%%'"%t[0])
+
+ return"%s records deleted"%str(int(total_deleted))
+
+
+# Send Error Report
+# ==============================================================================
+
+importwebnotes
+
+sql=webnotes.conn.sql
+
+# main function
+# -------------------------
+
+
[docs]defarchive_doc(doctype,name,restore=0):
+ archive_record(doctype,name,restore)
+
+ tables=sql("select options from tabDocField where parent=%s and fieldtype='Table'",doctype)
+ fortintables:
+ try:
+ rec_list=sql("select name from `%s%s` where parent=%s"%((restoreand'arc'or'tab'),t[0],'%s'),name)
+ exceptException,e:
+ ife.args[0]==1146:# no child table
+ rec_list=[]
+ else:
+ raisee
+
+ forrinrec_list:
+ archive_record(t[0],r[0],restore)
+
+# archive individual record
+# -------------------------
+
+
[docs]defarchive_record(doctype,name,restore=0):
+ src_tab=(restoreand'arc'or'tab')+doctype
+ tar_tab=(restoreand'tab'or'arc')+doctype
+
+ # get the record
+ try:
+ rec=sql("select * from `%s` where name=%s for update"%(src_tab,'%s'),name,as_dict=1)[0]
+ exceptException,e:
+ ife.args[0]==1146:
+ return# source table does not exist
+ else:
+ raisee
+
+ # insert the record
+ insert_record(doctype,tar_tab,name)
+
+ # put it field by field (ignore missing columns)
+ forfieldinrec.keys():
+ ifrec.get(field):
+ update_value(src_tab,tar_tab,name,rec,field)
+
+ # delete from main
+ try:
+ sql("delete from `%s` where name=%s limit 1"%(src_tab,'%s'),name)
+ exceptException,e:
+ ife.args[0]==1451:
+ webnotes.msgprint("Cannot archive %s '%s' as it is referenced in another record. You must delete the referred record first"%(doctype,name))
+
+ # delete from target, as it will create a double copy!
+ sql("delete from `%s` where name=%s limit 1"%(tar_tab,'%s'),name)
+
+# insert the record
+# -------------------------
+
+
[docs]definsert_record_name(tab,name):
+ sql("insert ignore into `%s` (name) values (%s)"%(tab,'%s'),name)
+
+# insert record
+# -------------------------
+
+
[docs]definsert_record(doctype,tar_tab,name):
+ try:
+ insert_record_name(tar_tab,name)
+ exceptException,e:
+ ife.args[0]==1146:
+ # missing table - create it
+ fromwebnotes.model.db_schemaimportupdatedb
+ updatedb(doctype,1)
+
+ # again
+ insert_record_name(tar_tab,name)
+ else:
+ raisee
+
+# update single value
+# -------------------------
+
+
[docs]defupdate_single_value(tab,field,value,name):
+ sql("update `%s` set `%s`=%s where name=%s"%(tab,field,'%s','%s'),(value,name))
+
+
+# update value
+# -------------------------
+
+"""
+Simple Caching:
+
+Stores key-value pairs in database and enables simple caching
+
+get_item(key).get() returns the cached value if not expired (else returns null)
+get_item(key).set(interval = 60000) sets a value to cache, expiring after x seconds
+get_item(key).clear() clears an old value
+setup() sets up cache
+"""
+
+importwebnotes
+
+
[docs]classCacheItem:
+ def__init__(self,key):
+ """create a new cache"""
+ self.key=key
+
+
[docs]defget(self):
+ """get value"""
+ try:
+ returnwebnotes.conn.sql("select `value` from __CacheItem where `key`=%s and expires_on > NOW()",self.key)[0][0]
+ exceptException:
+ returnNone
+
+
[docs]defset(self,value,interval=6000):
+ """set a new value, with interval"""
+ try:
+ self.clear()
+ webnotes.conn.sql("""INSERT INTO
+ __CacheItem (`key`, `value`, expires_on)
+ VALUES
+ (%s, %s, addtime(now(), sec_to_time(%s)))
+ """,(self.key,str(value),interval))
+ exceptException,e:
+ ife.args[0]==1146:
+ setup()
+ self.set(value,interval)
+ else:raisee
+
+
[docs]defclear(self):
+ """clear the item"""
+ webnotes.conn.sql("delete from __CacheItem where `key`=%s",self.key)
+
[docs]defsendmail_html(sender,recipients,subject,html,text=None,template=None,send_now=1,reply_to=None):
+ """
+ Send an html mail with alternative text and using Page Templates
+ """
+ sendmail(recipients,sender,html,subject,send_now=send_now,reply_to=reply_to,template=template)
+
+
[docs]defmake_html_body(content,template=None):
+ """
+ Generate html content from a Page Template object
+ """
+ template_html='%(content)s'
+
+ iftemplate:
+ fromwebnotes.model.codeimportget_code
+ template_html=get_code(webnotes.conn.get_value('Page Template',template,'module'),'Page Template',template,'html',fieldname='template')
+
+ returntemplate_html%{'content':content}
+
+
+
[docs]defsendmail(recipients,sender='',msg='',subject='[No Subject]',parts=[],cc=[],attach=[],send_now=1,reply_to=None,template=None):
+ """
+ send an html email as multipart with attachments and all
+ """
+
+ fromwebnotes.utils.email_lib.html2textimporthtml2text
+ fromwebnotes.utils.email_lib.sendimportEMail
+
+ email=EMail(sender,recipients,subject,reply_to=reply_to)
+ email.cc=cc
+
+ ifmsg:
+ iftemplate:
+ msg=make_html_body(msg,template)
+ else:
+ # if not html, then lets put some whitespace
+ if(not'<br>'inmsg)or(not'<p>'inmsg):
+ msg=msg.replace('\n','<br>')
+
+ footer=get_footer()
+ msg=msg+(footeror'')
+ email.set_text(html2text(msg))
+ email.set_html(msg)
+ forpinparts:
+ email.set_message(p[1])
+ forainattach:
+ email.attach(a)
+
+ email.send(send_now)
+
+
+
[docs]defget_footer():
+ """
+ Returns combination of footer from globals and Control Panel
+ """
+
+ footer=webnotes.conn.get_value('Control Panel',None,'mail_footer')or''
+ footer+=(webnotes.conn.get_global('global_mail_footer')or'')
+ returnfooter
+
+
+
[docs]defsend_form():
+ """
+ Emails a print format (form)
+ Called from form UI
+ """
+
+ fromwebnotes.utils.email_lib.form_emailimportFormEmail
+ FormEmail().send()
+
+
+
[docs]defget_contact_list():
+ """
+ Returns contacts (from autosuggest)
+ """
+ importwebnotes
+
+ cond=['`%s` like "%s%%"'%(f,webnotes.form.getvalue('txt'))forfinwebnotes.form.getvalue('where').split(',')]
+ cl=webnotes.conn.sql("select `%s` from `tab%s` where %s"%(
+ webnotes.form.getvalue('select')
+ ,webnotes.form.getvalue('from')
+ ,' OR '.join(cond)
+ )
+ )
+ webnotes.response['cl']=filter(None,[c[0]forcincl])
+
+
[docs]classFormEmail:
+ """
+ Represents an email sent from a Form
+ """
+ def__init__(self):
+ """
+ Get paramteres from the cgi form object
+ """
+ self.__dict__.update(webnotes.form_dict)
+
+ self.recipients=None
+ ifself.sendto:
+ self.recipients=self.sendto.replace(';',',')
+ self.recipients=self.recipients.split(',')
+
+
[docs]defupdate_contacts(self):
+ """
+ Add new email contact to database
+ """
+ importwebnotes
+ fromwebnotes.model.docimportDocument
+
+ forrinself.recipients:
+ r=r.strip()
+ try:
+ ifnotwebnotes.conn.sql("select email_id from tabContact where email_id=%s",r):
+ d=Document('Contact')
+ d.email_id=r
+ d.save(1)
+ exceptException,e:
+ ife.args[0]==1146:pass# no table
+ else:raisee
+
+
[docs]defmake_full_links(self):
+ """
+ Adds server name the relative links, so that images etc can be seen correctly
+ """
+ # only domain
+ ifnotself.__dict__.get('full_domain'):
+ return
+
+ defmake_full_link(match):
+ importos
+ link=match.group('name')
+ ifnotlink.startswith('http'):
+ link=os.path.join(self.full_domain,link)
+ return'src="%s"'%link
+
+ importre
+ p=re.compile('src[ ]*=[ ]*" (?P<name> [^"]*) "',re.VERBOSE)
+ self.body=p.sub(make_full_link,self.body)
+
+ p=re.compile("src[ ]*=[ ]*' (?P<name> [^']*) '",re.VERBOSE)
+ self.body=p.sub(make_full_link,self.body)
+
+
[docs]defget_form_link(self):
+ """
+ Returns publicly accessible form link
+ """
+ public_domain=webnotes.conn.get_value('Control Panel',None,'public_domain')
+ fromwebnotes.utils.encryptimportencrypt
+
+ ifnotpublic_domain:
+ return''
+
+ args={
+ 'dt':self.dt,
+ 'dn':self.dn,
+ 'acx':webnotes.conn.get_value('Control Panel',None,'account_id'),
+ 'server':public_domain,
+ 'akey':encrypt(self.dn)
+ }
+ return'<div>If you are unable to view the form below <a href="http://%(server)s/index.cgi?page=Form/%(dt)s/%(dn)s&acx=%(acx)s&akey=%(akey)s">click here to see it in your browser</div>'%args
+
+
[docs]defset_attachments(self):
+ """
+ Set attachments to the email from the form
+ """
+ al=[]
+ try:
+ al=webnotes.conn.sql('select file_list from `tab%s` where name="%s"'%(form.getvalue('dt'),form.getvalue('dn')))
+ ifal:
+ al=(al[0][0]or'').split('\n')
+ exceptException,e:
+ ife.args[0]==1146:
+ pass# no attachments in single types!
+ else:
+ raiseException,e
+ returnal
+
+
[docs]defbuild_message(self):
+ """
+ Builds the message object
+ """
+
+ self.email=EMail(self.sendfrom,self.recipients,self.subject,alternative=1)
+
+ fromwebnotes.utils.email_lib.html2textimporthtml2text
+
+ self.make_full_links()
+
+ # message
+ ifnotself.__dict__.get('message'):
+ self.message='Please find attached %s: %s\n'%(self.dt,self.dn)
+
+ html_message=text_message=self.message.replace('\n','<br>')
+
+ # separator
+ html_message+='<div style="margin:17px 0px; border-bottom:1px solid #AAA"></div>'
+
+ # form itself (only in the html message)
+ html_message+=self.body
+
+ # form link
+ html_message+=self.get_form_link()
+ text_message+=self.get_form_link()
+
+ # footer
+ footer=get_footer()
+ iffooter:
+ html_message+=footer
+ text_message+=footer
+
+ # message as text
+ self.email.set_text(html2text(text_message))
+ self.email.set_html(html_message)
+
+
[docs]defsend(self):
+ """
+ Send the form with html attachment
+ """
+
+ ifnotself.recipients:
+ webnotes.msgprint('No one to send to!')
+ return
+
+ self.build_message()
+
+ # print format (as attachment also - for text-only clients)
+ self.email.add_attachment(self.dn.replace(' ','').replace('/','-')+'.html',self.body)
+
+ # attachments
+ # self.with_attachments comes from http form variables
+ # i.e. with_attachments=1
+ ifcint(self.with_attachments):
+ forainself.set_attachments():
+ aandself.email.attach_file(a.split(',')[0])
+
+ # cc
+ ifself.cc:
+ self.email.cc=[self.cc]
+
+ self.email.send(send_now=1)
+ webnotes.msgprint('Sent')
+try:
+ importhtmlentitydefs
+ importurlparse
+ importHTMLParser
+exceptImportError:#Python3
+ importhtml.entitiesashtmlentitydefs
+ importurllib.parseasurlparse
+ importhtml.parserasHTMLParser
+try:#Python3
+ importurllib.requestasurllib
+except:
+ importurllib
+importoptparse,re,sys,codecs,types
+
+try:fromtextwrapimportwrap
+except:pass
+
+# Use Unicode characters instead of their ascii psuedo-replacements
+UNICODE_SNOB=0
+
+# Put the links after each paragraph instead of at the end.
+LINKS_EACH_PARAGRAPH=0
+
+# Wrap long lines at position. 0 for no wrapping. (Requires Python 2.3.)
+BODY_WIDTH=78
+
+# Don't show internal links (href="#local-anchor") -- corresponding link targets
+# won't be visible in the plain text file anyway.
+SKIP_INTERNAL_LINKS=False
+
+### Entity Nonsense ###
+
+
[docs]defunescape(s):
+ returnr_unescape.sub(replaceEntities,s)
+
+### End Entity Nonsense ###
+
+
[docs]defonlywhite(line):
+ """Return true if the line does only consist of whitespace characters."""
+ forcinline:
+ ifcisnot' 'andcisnot' ':
+ returncis' '
+ returnline
+
+"""
+ This module contains classes for managing incoming emails
+"""
+
+
[docs]classIncomingMail:
+ """
+ Single incoming email object. Extracts, text / html and attachments from the email
+ """
+ def__init__(self,content):
+ """
+ Parse the incoming mail content
+ """
+ importemail
+
+ self.mail=email.message_from_string(content)
+ self.text_content=''
+ self.html_content=''
+ self.attachments=[]
+ self.parse()
+
+
[docs]defget_text_content(self):
+ """
+ Returns the text parts of the email. If None, then HTML parts
+ """
+ returnself.text_contentorself.html_content
+
[docs]defparse(self):
+ """
+ Extracts text, html and attachments from the mail
+ """
+ forpartinself.mail.walk():
+ self.process_part(part)
+
+
[docs]defget_thread_id(self):
+ """
+ Extracts thread id of the message between first []
+ from the subject
+ """
+ subject=self.mail.get('Subject','')
+ if'['insubjectand']'insubject:
+ returnsubject.split('[')[1].split(']')[0]
+
+
[docs]defprocess_part(self,part):
+ """
+ Process a single part of an email
+ """
+ charset=self.get_charset(part)
+ content_type=part.get_content_type()
+
+ ifcontent_type=='text/plain':
+ self.text_content+=self.get_payload(part,charset)
+
+ ifcontent_type=='text/html':
+ self.html_content+=self.get_payload(part,charset)
+
+ ifpart.get_filename():
+ self.get_attachment(part,charset)
+
+
[docs]classPOP3Mailbox:
+ """
+ A simple pop3 mailbox, abstracts connection and mail extraction
+ To use, subclass it and override method process_message(from, subject, text, thread_id)
+ """
+
+ def__init__(self,settings_doc):
+ """
+ settings_doc must contain
+ is_ssl, host, username, password
+ """
+ fromwebnotes.model.docimportDocument
+ self.settings=Document(settings_doc,settings_doc)
+
+
+"""
+Sends email via outgoing server specified in "Control Panel"
+Allows easy adding of Attachments of "File" objects
+"""
+
+importwebnotes
+importwebnotes.defs
+fromwebnotesimportmsgprint
+importemail
+
+
[docs]classEMail:
+ """
+ Wrapper on the email module. Email object represents emails to be sent to the client.
+ Also provides a clean way to add binary `FileData` attachments
+ Also sets all messages as multipart/alternative for cleaner reading in text-only clients
+ """
+ def__init__(self,sender='',recipients=[],subject='',from_defs=0,alternative=0,reply_to=None):
+ fromemail.mime.multipartimportMIMEMultipart
+ iftype(recipients)==str:
+ recipients=recipients.replace(';',',')
+ recipients=recipients.split(',')
+
+ self.from_defs=from_defs
+ self.sender=sender
+ self.reply_to=reply_toorsender
+ self.recipients=recipients
+ self.subject=subject
+
+ self.msg_root=MIMEMultipart('mixed')
+ self.msg_multipart=MIMEMultipart('alternative')
+ self.msg_root.attach(self.msg_multipart)
+ self.cc=[]
+
+
[docs]defset_text(self,message):
+ """
+ Attach message in the text portion of multipart/alternative
+ """
+ fromemail.mime.textimportMIMEText
+ part=MIMEText(message,'plain')
+ self.msg_multipart.attach(part)
+
+
[docs]defset_html(self,message):
+ """
+ Attach message in the html portion of multipart/alternative
+ """
+ fromemail.mime.textimportMIMEText
+ part=MIMEText(message,'html')
+ self.msg_multipart.attach(part)
+
+
[docs]defset_message(self,message,mime_type='text/html',as_attachment=0,filename='attachment.html'):
+ """
+ Append the message with MIME content to the root node (as attachment)
+ """
+ fromemail.mime.textimportMIMEText
+
+ maintype,subtype=mime_type.split('/')
+ part=MIMEText(message,_subtype=subtype)
+
+ ifas_attachment:
+ part.add_header('Content-Disposition','attachment',filename=filename)
+
+ self.msg_root.attach(part)
+
+
[docs]defattach_file(self,n):
+ """
+ attach a file from the `FileData` table
+ """
+ fromwebnotes.utils.file_managerimportget_file
+ res=get_file(n)
+ ifnotres:
+ return
+
+ self.add_attachment(res[0],res[1])
+
+
[docs]defadd_attachment(self,fname,fcontent,content_type=None):
+
+ fromemail.mime.audioimportMIMEAudio
+ fromemail.mime.baseimportMIMEBase
+ fromemail.mime.imageimportMIMEImage
+ fromemail.mime.textimportMIMEText
+
+ importmimetypes
+
+ ifnotcontent_type:
+ content_type,encoding=mimetypes.guess_type(fname)
+
+ ifcontent_typeisNone:
+ # No guess could be made, or the file is encoded (compressed), so
+ # use a generic bag-of-bits type.
+ content_type='application/octet-stream'
+
+ maintype,subtype=content_type.split('/',1)
+ ifmaintype=='text':
+ # Note: we should handle calculating the charset
+ part=MIMEText(fcontent,_subtype=subtype)
+ elifmaintype=='image':
+ part=MIMEImage(fcontent,_subtype=subtype)
+ elifmaintype=='audio':
+ part=MIMEAudio(fcontent,_subtype=subtype)
+ else:
+ part=MIMEBase(maintype,subtype)
+ part.set_payload(fcontent)
+ # Encode the payload using Base64
+ fromemailimportencoders
+ encoders.encode_base64(part)
+
+ # Set the filename parameter
+ iffname:
+ part.add_header('Content-Disposition','attachment',filename=fname)
+
+ self.msg_root.attach(part)
+
+
[docs]defvalidate(self):
+ """
+ validate the email ids
+ """
+ ifnotself.sender:
+ self.sender=webnotes.conn.get_value('Control Panel',None,'auto_email_id')
+
+ fromwebnotes.utilsimportvalidate_email_add
+ # validate ids
+ ifself.senderand(notvalidate_email_add(self.sender)):
+ webnotes.msgprint("%s is not a valid email id"%self.sender,raise_exception=1)
+
+ ifself.reply_toand(notvalidate_email_add(self.reply_to)):
+ webnotes.msgprint("%s is not a valid email id"%self.reply_to,raise_exception=1)
+
+ foreinself.recipients:
+ ifnotvalidate_email_add(e):
+ webnotes.msgprint("%s is not a valid email id"%e,raise_exception=1)
+
+
[docs]defsetup(self):
+ """
+ setup the SMTP (outgoing) server from `Control Panel` or defs.py
+ """
+ ifself.from_defs:
+ self.server=getattr(webnotes.defs,'mail_server','')
+ self.login=getattr(webnotes.defs,'mail_login','')
+ self.port=getattr(webnotes.defs,'mail_port',None)
+ self.password=getattr(webnotes.defs,'mail_password','')
+ self.use_ssl=getattr(webnotes.defs,'use_ssl',0)
+
+ else:
+ importwebnotes.model.doc
+ fromwebnotes.utilsimportcint
+
+ # get defaults from control panel
+ cp=webnotes.model.doc.Document('Control Panel','Control Panel')
+ self.server=cp.outgoing_mail_serverorgetattr(webnotes.defs,'mail_server','')
+ self.login=cp.mail_loginorgetattr(webnotes.defs,'mail_login','')
+ self.port=cp.mail_portorgetattr(webnotes.defs,'mail_port',None)
+ self.password=cp.mail_passwordorgetattr(webnotes.defs,'mail_password','')
+ self.use_ssl=cint(cp.use_ssl)
+
+"""
+XTEA Block Encryption Algorithm
+Author: Paul Chakravarti (paul_dot_chakravarti_at_gmail_dot_com)
+License: Public Domain
+"""
+
+
[docs]defget_key():
+ # Encryption key is datetime of creation of DocType, DocType"
+ importwebnotes
+ returnwebnotes.conn.sql("select creation from tabDocType where name='DocType'")[0][0].strftime('%Y%m%d%H%M%s')[:16]
+
[docs]defsave_file(fname,content,module=None):
+ importwebnotes
+ fromwebnotes.model.docimportDocument
+
+ # some browsers return the full path
+ if'\\'infname:
+ fname=fname.split('\\')[-1]
+ if'/'infname:
+ fname=fname.split('/')[-1]
+
+ # generate the ID (?)
+ f=Document('File Data')
+ f.file_name=fname
+ ifmodule:
+ f.module=module
+ f.save(1)
+
+ write_file(f.name,content)
+
+ returnf.name
+
+# -------------------------------------------------------
+
+
[docs]defwrite_file(fid,content):
+ importwebnotes,os,webnotes.defs
+
+ # test size
+ max_file_size=1000000
+ ifhasattr(webnotes.defs,'max_file_size'):
+ max_file_size=webnotes.defs.max_file_size
+
+ iflen(content)>max_file_size:
+ raiseException,'Maximum File Limit (%s MB) Crossed'%(int(max_file_size/1000000))
+
+ # no slashes
+ fid=fid.replace('/','-')
+
+ # save to a folder (not accessible to public)
+ folder=webnotes.get_files_path()
+
+ # create account folder (if not exists)
+ webnotes.create_folder(folder)
+
+ # write the file
+ file=open(os.path.join(folder,fid),'w+')
+ file.write(content)
+ file.close()
+
+
+# -------------------------------------------------------
+
[docs]defget_file_system_name(fname):
+ # get system name from File Data table
+ importwebnotes
+ returnwebnotes.conn.sql("select name, file_name from `tabFile Data` where name=%s or file_name=%s",(fname,fname))
+
+# -------------------------------------------------------
+importos,os.path,shutil
+
+# This code is original from jsmin by Douglas Crockford, it was translated to
+# Python by Baruch Even. The original code had the following copyright and
+# license.
+#
+# /* jsmin.c
+# 2007-05-22
+#
+# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# The Software shall be used for Good, not Evil.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# */
+
+fromStringIOimportStringIO
+
+
[docs]defisAlphanum(c):
+ """return true if the character is a letter, digit, underscore,
+ dollar sign, or non-ASCII character.
+ """
+ return((c>='a'andc<='z')or(c>='0'andc<='9')or
+ (c>='A'andc<='Z')orc=='_'orc=='$'orc=='\\'or(cisnotNoneandord(c)>126));
+
[docs]classJavascriptMinify(object):
+
+ def_outA(self):
+ self.outstream.write(self.theA)
+ def_outB(self):
+ self.outstream.write(self.theB)
+
+ def_get(self):
+ """return the next character from stdin. Watch out for lookahead. If
+ the character is a control character, translate it to a space or
+ linefeed.
+ """
+ c=self.theLookahead
+ self.theLookahead=None
+ ifc==None:
+ c=self.instream.read(1)
+ ifc>=' 'orc=='\n':
+ returnc
+ ifc=='':# EOF
+ return'\000'
+ ifc=='\r':
+ return'\n'
+ return' '
+
+ def_peek(self):
+ self.theLookahead=self._get()
+ returnself.theLookahead
+
+ def_next(self):
+ """get the next character, excluding comments. peek() is used to see
+ if an unescaped '/' is followed by a '/' or '*'.
+ """
+ c=self._get()
+ ifc=='/'andself.theA!='\\':
+ p=self._peek()
+ ifp=='/':
+ c=self._get()
+ whilec>'\n':
+ c=self._get()
+ returnc
+ ifp=='*':
+ c=self._get()
+ while1:
+ c=self._get()
+ ifc=='*':
+ ifself._peek()=='/':
+ self._get()
+ return' '
+ ifc=='\000':
+ raiseUnterminatedComment()
+
+ returnc
+
+ def_action(self,action):
+ """do something! What you do is determined by the argument:
+ 1 Output A. Copy B to A. Get the next B.
+ 2 Copy B to A. Get the next B. (Delete A).
+ 3 Get the next B. (Delete B).
+ action treats a string as a single character. Wow!
+ action recognizes a regular expression if it is preceded by ( or , or =.
+ """
+ ifaction<=1:
+ self._outA()
+
+ ifaction<=2:
+ self.theA=self.theB
+ ifself.theA=="'"orself.theA=='"':
+ while1:
+ self._outA()
+ self.theA=self._get()
+ ifself.theA==self.theB:
+ break
+ ifself.theA<='\n':
+ raiseUnterminatedStringLiteral()
+ ifself.theA=='\\':
+ self._outA()
+ self.theA=self._get()
+
+
+ ifaction<=3:
+ self.theB=self._next()
+ ifself.theB=='/'and(self.theA=='('orself.theA==','or
+ self.theA=='='orself.theA==':'or
+ self.theA=='['orself.theA=='?'or
+ self.theA=='!'orself.theA=='&'or
+ self.theA=='|'orself.theA==';'or
+ self.theA=='{'orself.theA=='}'or
+ self.theA=='\n'):
+ self._outA()
+ self._outB()
+ while1:
+ self.theA=self._get()
+ ifself.theA=='/':
+ break
+ elifself.theA=='\\':
+ self._outA()
+ self.theA=self._get()
+ elifself.theA<='\n':
+ raiseUnterminatedRegularExpression()
+ self._outA()
+ self.theB=self._next()
+
+
+ def_jsmin(self):
+ """Copy the input to the output, deleting the characters which are
+ insignificant to JavaScript. Comments will be removed. Tabs will be
+ replaced with spaces. Carriage returns will be replaced with linefeeds.
+ Most spaces and linefeeds will be removed.
+ """
+ self.theA='\n'
+ self._action(3)
+
+ whileself.theA!='\000':
+ ifself.theA==' ':
+ ifisAlphanum(self.theB):
+ self._action(1)
+ else:
+ self._action(2)
+ elifself.theA=='\n':
+ ifself.theBin['{','[','(','+','-']:
+ self._action(1)
+ elifself.theB==' ':
+ self._action(3)
+ else:
+ ifisAlphanum(self.theB):
+ self._action(1)
+ else:
+ self._action(2)
+ else:
+ ifself.theB==' ':
+ ifisAlphanum(self.theA):
+ self._action(1)
+ else:
+ self._action(3)
+ elifself.theB=='\n':
+ ifself.theAin['}',']',')','+','-','"','\'']:
+ self._action(1)
+ else:
+ ifisAlphanum(self.theA):
+ self._action(1)
+ else:
+ self._action(3)
+ else:
+ self._action(1)
+
+
+# Tree (Hierarchical) Nested Set Model (nsm)
+#
+# To use the nested set model,
+# use the following pattern
+# 1. name your parent field as "parent_node" if not have a property nsm_parent_field as your field name in the document class
+# 2. have a field called "old_parent" in your fields list - this identifies whether the parent has been changed
+# 3. call update_nsm(doc_obj) in the on_upate method
+
+# ------------------------------------------
+
+importwebnotes
+
+# called in the on_update method
+
[docs]defupdate_nsm(doc_obj):
+ # get fields, data from the DocType
+ d=doc_obj.doc
+ pf,opf='parent_node','old_parent'
+ ifhasattr(doc_obj,'nsm_parent_field'):
+ pf=doc_obj.nsm_parent_field
+ ifhasattr(doc_obj,'nsm_oldparent_field'):
+ opf=doc_obj.nsm_oldparent_field
+ p,op=d.fields[pf],d.fields.get(opf,'')
+
+ # has parent changed (?) or parent is None (root)
+ ifnotdoc_obj.doc.lftandnotdoc_obj.doc.rgt:
+ update_add_node(doc_obj.doc.doctype,doc_obj.doc.name,por'',pf)
+ elifop!=p:
+ update_remove_node(doc_obj.doc.doctype,doc_obj.doc.name)
+ update_add_node(doc_obj.doc.doctype,doc_obj.doc.name,por'',pf)
+ # set old parent
+ webnotes.conn.set(d,opf,por'')
+
+
[docs]defrebuild_tree(doctype,parent_field):
+ # get all roots
+ right=1
+ result=webnotes.conn.sql("SELECT name FROM `tab%s` WHERE `%s`='' or `%s` IS NULL"%(doctype,parent_field,parent_field))
+ forrinresult:
+ right=rebuild_node(doctype,r[0],right,parent_field)
+
+
[docs]defrebuild_node(doctype,parent,left,parent_field):
+ # the right value of this node is the left value + 1
+ right=left+1
+
+ # get all children of this node
+ result=webnotes.conn.sql("SELECT name FROM `tab%s` WHERE `%s`='%s'"%(doctype,parent_field,parent))
+ forrinresult:
+ right=rebuild_node(doctype,r[0],right,parent_field)
+
+ # we've got the left value, and now that we've processed
+ # the children of this node we also know the right value
+ webnotes.conn.sql('UPDATE `tab%s` SET lft=%s, rgt=%s WHERE name="%s"'%(doctype,left,right,parent))
+
+ #return the right value of this node + 1
+ returnright+1
+
+
[docs]defupdate_add_node(doctype,name,parent,parent_field):
+ # get the last sibling of the parent
+ ifparent:
+ right=webnotes.conn.sql("select rgt from `tab%s` where name='%s'"%(doctype,parent))[0][0]
+ else:# root
+ right=webnotes.conn.sql("select ifnull(max(rgt),0)+1 from `tab%s` where ifnull(`%s`,'') =''"%(doctype,parent_field))[0][0]
+ right=rightor1
+
+ # update all on the right
+ webnotes.conn.sql("update `tab%s` set rgt = rgt+2 where rgt >= %s"%(doctype,right))
+ webnotes.conn.sql("update `tab%s` set lft = lft+2 where lft >= %s"%(doctype,right))
+
+ #$ update index of new node
+ webnotes.conn.sql("update `tab%s` set lft=%s, rgt=%s where name='%s'"%(doctype,right,right+1,name))
+ returnright
+
+
[docs]defupdate_remove_node(doctype,name):
+ left=webnotes.conn.sql("select lft from `tab%s` where name='%s'"%(doctype,name))
+ ifleft[0][0]:
+ # reset this node
+ webnotes.conn.sql("update `tab%s` set lft=0, rgt=0 where name='%s'"%(doctype,name))
+
+ # update all on the right
+ webnotes.conn.sql("update `tab%s` set rgt = rgt-2 where rgt > %s"%(doctype,left[0][0]))
+ webnotes.conn.sql("update `tab%s` set lft = lft-2 where lft > %s"%(doctype,left[0][0]))
+"""
+Simple Scheduler
+
+This scheduler is used to fire events across multiple databases. A database
+master_scheduler is maintained with one event and one log table
+
+Events are added by different databases in the master scheduler using the
+`set_event` method and they are executed by the cron.
+
+__main__ will call run
+
+To install:
+-----------
+
+python install_lib.py [root] [password] master_scheduler
+
+In cron:
+--------
+
+python [path]webnotes/utils/scheduler.py
+
+"""
+
+
+
[docs]defset(self,event,interval,recurring,db_name=None):
+ """
+ Add an event to the Event table in the master scheduler
+ """
+ self.connect()
+
+ ifnotdb_name:
+ importwebnotes
+ db_name=webnotes.conn.cur_db_name
+
+ self.clear(db_name,event)
+ self.conn.sql("""insert into
+ Event (`db_name`, `event`, `interval`, next_execution, recurring)
+ values (%s, %s, %s, ADDTIME(NOW(), SEC_TO_TIME(%s)), %s)
+ """,(webnotes.conn.cur_db_name,event,interval,interval,recurring))
+
+
[docs]defget_events(self,db_name=None):
+ """
+ Returns list of upcoming events
+ """
+ self.connect()
+ ifnotdb_name:
+ importwebnotes
+ db_name=webnotes.conn.cur_db_name
+
+ returnself.conn.sql("select * from Event where db_name=%s",db_name,as_dict=1)
+
+
+
[docs]defget_log(self,db_name=None):
+ """
+ Returns log of events
+ """
+ self.connect()
+ ifnotdb_name:
+ importwebnotes
+ db_name=webnotes.conn.cur_db_name
+
+ returnself.conn.sql("select * from EventLog where db_name=%s limit 50",db_name,as_dict=1)
+
+
[docs]defclear(self,db_name,event):
+ """
+ Clears the event
+ """
+ self.connect()
+ self.conn.sql("delete from Event where `event`=%s and db_name=%s",(event,db_name))
+
[docs]deflog(self,db_name,event,traceback):
+ """
+ Log an event error
+ """
+ self.conn.sql("insert into `EventLog`(db_name, event, log, executed_on) values (%s, %s, %s, now())", \
+ (db_name,event,traceback))
+
+ # delete old logs
+ self.conn.sql("delete from EventLog where executed_on < subdate(curdate(), interval 30 day)")
+
+
[docs]defrun(self):
+ """
+ Run scheduled (due) events and reset time for recurring events
+ """
+ el=self.conn.sql("""select `db_name`, `event`, `recurring`, `interval`
+ from `Event`
+ where next_execution < NOW()""",as_dict=1)
+
+ foreinel:
+ # execute the event
+ self.execute(e['db_name'],e['event'])
+
+ # if recurring, update next_execution
+ ife['recurring']:
+ self.conn.sql("update Event set next_execution = addtime(now(), sec_to_time(%s))",e['interval'])
+
+ # else clear
+ else:
+ self.clear(e['db_name'],e['event'])
+
+
[docs]defset_event(event,interval=60*60*24,recurring=1):
+ """
+ Adds an event to the master scheduler
+ """
+ returnScheduler().set(event,interval,recurring)
+
+
+
[docs]defcancel_event(event):
+ """
+ Cancels an event
+ """
+ importwebnotes
+ returnScheduler().clear(webnotes.conn.cur_db_name,event)
+
+# to be called from cron
[docs]defget_orignal_values(self,d):
+ ifd.doctype=='DocField':
+ t=self.get_id(d)[0]
+ return{'name':t[0],'options':t[1],'reqd':t[3],'print_hide':t[4],'hidden':t[5]}
+
+ ifd.doctype=='DocType':
+ returnwebnotes.conn.sql("select server_code, client_script from `tabDocType` where name=%s",d.name,as_dict=1)[0]
+
+ # renumber the indexes
+
[docs]defrenum(self):
+ extra=self.get_extra_fields()
+ self.clear_section_breaks()
+ self.add_section_breaks_and_renum()
+ self.fix_extra_fields(extra)
+
+ # get fields not in the incoming list (to preserve order)
+
[docs]defget_extra_fields(self):
+ prev_field,prev_field_key,extra='','',[]
+
+ # get new fields and labels
+ fieldnames=[d.get('fieldname')fordinself.in_doclist]
+ labels=[d.get('label')fordinself.in_doclist]
+
+ # check if all existing are present
+ forfinwebnotes.conn.sql("select fieldname, label, idx from tabDocField where parent=%s and fieldtype not in ('Section Break', 'Column Break', 'HTML') order by idx asc",self.doc.name):
+ iff[0]andnotf[0]infieldnames:
+ extra.append([f[0],f[1],prev_field,prev_field_key])
+ eliff[1]andnotf[1]inlabels:
+ extra.append([f[0],f[1],prev_field,prev_field_key])
+
+ prev_field,prev_field_key=f[0]orf[1],f[0]and'fieldname'or'label'
+
+ returnextra
+
+ # clear section breaks
+
[docs]defclear_section_breaks(self):
+ webnotes.conn.sql("delete from tabDocField where fieldtype in ('Section Break', 'Column Break', 'HTML') and parent=%s and ifnull(options,'')!='Custom'",self.doc.name)
+
+ # add section breaks
+
[docs]defadd_section_breaks_and_renum(self):
+ fordinself.in_doclist:
+ ifd.get('parentfield')=='fields':
+ ifd.get('fieldtype')in('Section Break','Column Break','HTML'):
+ tmp=Document(fielddata=d)
+ tmp.fieldname=''
+ tmp.name=None
+ tmp.save(1,ignore_fields=1,check_links=0)
+ else:
+ webnotes.conn.sql("update tabDocField set idx=%s where %s=%s and parent=%s"% \
+ ('%s',d.get('fieldname')and'fieldname'or'label','%s','%s'),(d.get('idx'),d.get('fieldname')ord.get('label'),self.doc.name))
+
+
+ # adjust the extra fields
+
[docs]deffix_extra_fields(self,extra):
+ # push fields down at new idx
+ foreinextra:
+ # get idx of the prev to extra field
+ idx=0
+ ife[2]:
+ idx=webnotes.conn.sql("select idx from tabDocField where %s=%s and parent=%s"%(e[3],'%s','%s'),(e[2],self.doc.name))
+ idx=idxandidx[0][0]or0
+
+ ifidx:
+ webnotes.conn.sql("update tabDocField set idx=idx+1 where idx>%s and parent=%s",(idx,self.doc.name))
+ webnotes.conn.sql("update tabDocField set idx=%s where %s=%s and parent=%s"% \
+ ('%s',e[0]and'fieldname'or'label','%s','%s'),(idx+1,e[0]ore[1],self.doc.name))
+
+
+
+
+#
+# update module def
+#
[docs]defget_id(self,d):
+ returnwebnotes.conn.sql("select name from `tabModule Def Item` where doc_type=%s and doc_name=%s and display_name=%s and parent=%s",(d.doc_type,d.doc_name,d.display_name,d.parent))
+
[docs]defget_id(self,d):
+ ifd.doctype=='Field Mapper Detail':
+ returnwebnotes.conn.sql("select name from `tabField Mapper Detail` where from_field=%s and to_field=%s and match_id=%s and parent=%s",(d.from_field,d.to_field,d.match_id,d.parent))
+ elifd.doctype=='Table Mapper Detail':
+ returnwebnotes.conn.sql("select name from `tabTable Mapper Detail` where from_table=%s and to_table = %s and match_id=%s and parent=%s",(d.from_table,d.to_table,d.match_id,d.parent))
+
[docs]classFrameworkServer:
+ """
+ Connect to a remote server via HTTP (webservice).
+
+ * `remote_host` is the the address of the remote server
+ * `path` is the path of the Framework (excluding index.cgi)
+ """
+ def__init__(self,remote_host,path,user='',password='',account='',cookies=None,opts=None,https=0):
+ # validate
+ ifnot(remote_hostandpath):
+ raiseException,"Server address and path necessary"
+
+ ifnot((userandpassword)or(cookies)):
+ raiseException,"Either cookies or user/password necessary"
+
+ self.remote_host=remote_host
+ self.path=path
+ self.cookies=cookiesor{}
+ self.webservice_method='POST'
+ self.account=account
+ self.account_id=None
+ self.https=https
+ self.conn=None
+
+ # login
+ ifnotcookies:
+ args={'usr':user,'pwd':password,'acx':account}
+
+ ifopts:
+ args.update(opts)
+
+ res=self.http_get_response('login',args)
+
+ ret=res.read()
+ try:
+ ret=eval(ret)
+ exceptException,e:
+ webnotes.msgprint(ret)
+ raiseException,e
+
+ ifret.get('message')andret.get('message')!='Logged In':
+ raiseException,ret.get('message')
+
+ ifret.get('exc'):
+ raiseException,ret.get('exc')
+
+ self._extract_cookies(res)
+
+ self.account_id=self.cookies.get('account_id')
+ self.sid=self.cookies.get('sid')
+
+ self.login_response=res
+ self.login_return=ret
+
+ # -----------------------------------------------------------------------------------------
+
+
[docs]defhttp_get_response(self,method,args):
+ """
+ Run a method on the remote server, with the given arguments
+ """
+ # get response from remote server
+
+ importurllib,urllib2,os
+
+ args['cmd']=method
+ ifself.path.startswith('/'):self.path=self.path[1:]
+
+ protocol=self.httpsand'https://'or'http://'
+ req=urllib2.Request(protocol+os.path.join(self.remote_host,self.path,'index.cgi'),urllib.urlencode(args))
+ forkeyinself.cookies:
+ req.add_header('cookie','; '.join(['%s=%s'%(key,self.cookies[key])forkeyinself.cookies]))
+ returnurllib2.urlopen(req)
+
+ # -----------------------------------------------------------------------------------------
+
[docs]defget_cal_events(m_st,m_end):
+ importwebnotes
+ importwebnotes.model.doc
+
+ sql=webnotes.conn.sql
+
+ # load owned events
+ res1=sql("select name from `tabEvent` WHERE ifnull(event_date,'2000-01-01') between '%s' and '%s' and owner = '%s' and event_type != 'Public' and event_type != 'Cancel'"%(m_st,m_end,webnotes.user.name))
+
+ # load individual events
+ res2=sql("select t1.name from `tabEvent` t1, `tabEvent User` t2 where ifnull(t1.event_date,'2000-01-01') between '%s' and '%s' and t2.person = '%s' and t1.name = t2.parent and t1.event_type != 'Cancel'"%(m_st,m_end,webnotes.user.name))
+
+ # load role events
+ roles=webnotes.user.get_roles()
+ myroles=['t2.role = "%s"'%rforrinroles]
+ myroles='('+(' OR '.join(myroles))+')'
+ res3=sql("select t1.name from `tabEvent` t1, `tabEvent Role` t2 where ifnull(t1.event_date,'2000-01-01') between '%s' and '%s' and t1.name = t2.parent and t1.event_type != 'Cancel' and %s"%(m_st,m_end,myroles))
+
+ # load public events
+ res4=sql("select name from `tabEvent` where ifnull(event_date,'2000-01-01') between '%s' and '%s' and event_type='Public'"%(m_st,m_end))
+
+ doclist,rl=[],[]
+ forrinres1+res2+res3+res4:
+ ifnotrinrl:
+ doclist+=webnotes.model.doc.get('Event',r[0])
+ rl.append(r)
+
+ returndoclist
+
+
+# Load Month Events
+# -----------------
+
+"""
+Server side methods for the follower pattern (Follow button used in forms)
+"""
+
+importwebnotes
+form=webnotes.form_dict
+
+#
+# Follow
+#
+
[docs]deffollow(dt=None,dn=None,user=None,verbose=0):
+ "Add as follower to a particular record. If no parameteres, then take from the http request (form)"
+
+ ifnotdt:
+ dt,dn,user=form.get('dt'),form.get('dn'),form.get('user')
+ verbose=1
+
+ ifnotuser:return
+
+ ifnotis_follower(dt,dn,user):
+ make_follower(dt,dn,user,verbose)
+ else:
+ ifverbose:webnotes.msgprint("%s is already a follower!"%user)
+
+ returnload_followers(dt,dn)
+
+
[docs]defmake_follower(dt,dn,user,verbose):
+ "Add the user as a follower"
+ ifhas_permission(dt,user):
+ fromwebnotes.model.docimportDocument
+ d=Document('Follower')
+ d.doc_type=dt
+ d.doc_name=dn
+ d.owner=user
+ d.save(1)
+ else:
+ ifverbose:webnotes.msgprint('%s does not have sufficient permission to follow'%user)
+
+
[docs]defhas_permission(dt,user):
+ "Check to see if the user has permission to follow"
+
+ returnwebnotes.conn.sql("select name from tabDocPerm where parent=%s and ifnull(`read`,0)=1 and role in ('%s') limit 1" \
+ %('%s',("', '".join(webnotes.user.get_roles()))),dt)
+
+
[docs]defis_follower(dt,dn,user):
+ "returns true if given user is a follower"
+
+ returnwebnotes.conn.sql("""
+ select name from tabFollower
+ where ifnull(doc_type,'')=%s
+ and ifnull(doc_name,'')=%s
+ and owner=%s""",(dt,dn,user))
+#
+# Unfollow
+#
+
[docs]defunfollow(dt=None,dn=None,user=None):
+ "Unfollow a particular record. If no parameteres, then take from the http request (form)"
+
+ ifnotdt:
+ dt,dn,user=form.get('dt'),form.get('dn'),form.get('user')
+
+ webnotes.conn.sql("delete from tabFollower where doc_name=%s and doc_type=%s and owner=%s",(dn,dt,user))
+
+ returnload_followers(dt,dn)
+
+#
+# Load followers
+#
+
[docs]defload_followers(dt=None,dn=None):
+ "returns list of followers (Full Names) for a particular object"
+
+ ifnotdt:dt,dn=form.get('dt'),form.get('dn')
+
+ try:
+ return[t[0]fortinwebnotes.conn.sql("""
+ SELECT IFNULL(CONCAT(t1.first_name, if(t1.first_name IS NULL, '', ' '), t1.last_name), t1.name)
+ FROM tabProfile t1, tabFollower t2 WHERE t2.doc_type=%s AND t2.doc_name=%s
+ AND t1.name = t2.owner""",(dt,dn))]
+
+ exceptException,e:
+ ife.args[0]in(1146,1054):
+ setup()
+ return[]
+ else:
+ raisee
+
+#
+# Email followers
+#
+
[docs]defemail_followers(dt,dn,msg_html=None,msg_text=None):
+ "Send an email to all followers of this object"
+ pass
+
+#
+# Update feed
+#
+
[docs]defon_docsave(doc):
+ "Add the owner and all linked Profiles as followers"
+ follow(doc.doctype,doc.name,doc.owner)
+ forpinget_profile_fields(doc.doctype):
+ follow(doc.doctype,doc.name,doc.fields.get(p))
+
+ update_followers(doc=doc)
+
+#
+# update the follower record timestamp and subject
+#
+
[docs]defupdate_followers(dt=None,dn=None,subject=None,update_by=None,doc=None):
+ "Updates the timestamp and subject in follower table (for feed generation)"
+ fromwebnotes.utilsimportnow
+ webnotes.conn.sql("update tabFollower set modified=%s, subject=%s, modified_by=%s where doc_type=%s and doc_name=%s", \
+ (now(),
+ subjectordoc.fields.get('subject'), \
+ update_byorwebnotes.session['user'],\
+ dtordoc.doctype,
+ dnordoc.name))
+
+#
+# get type of "Profile" fields
+#
+
[docs]defget_profile_fields(dt):
+ "returns a list of all profile link fields from the doctype"
+ return[f[0]forfin \
+ webnotes.conn.sql("select fieldname from tabDocField where parent=%s and fieldtype='Link' and options='Profile'",dt)]
+
+#
+# setup - make followers table
+#
+
[docs]defsetup():
+ "Make table for followers - if missing"
+ webnotes.conn.commit()
+ fromwebnotes.modules.module_managerimportreload_doc
+ reload_doc('core','doctype','follower')
+ webnotes.conn.begin()
+"""
+Server side handler for "Form" events
+"""
+
+importwebnotes
+importwebnotes.model.doc
+importwebnotes.model.meta
+fromwebnotes.model.triggersimportfire_event
+
+
[docs]defgetdoc():
+ """
+ Loads a doclist for a given document. This method is called directly from the client.
+ Requries "doctype", "docname" as form variables. If "from_archive" is set, it will get from archive.
+ Will also call the "onload" method on the document.
+ """
+
+ importwebnotes
+ fromwebnotes.utilsimportcint
+
+ form=webnotes.form_dict
+ doctype,docname=form.get('doctype'),form.get('name')
+ prefix=cint(form.get('from_archive'))and'arc'or'tab'
+
+ ifnot(doctypeanddocname):
+ raiseException,'doctype and name required!'
+
+ doclist=[]
+ # single
+ doclist=load_single_doc(doctype,docname,(form.get('user')orwebnotes.session['user']),prefix)
+
+ # load doctype along with the doc
+ ifform.get('getdoctype'):
+ importwebnotes.model.doctype
+ doclist+=webnotes.model.doctype.get(doctype)
+
+ # tag as archived
+ ifprefix=='arc':
+ doclist[0].__archived=1
+
+ webnotes.response['docs']=doclist
+
+#===========================================================================================
+
+
[docs]defget_comments(doctype=None,docname=None,limit=5):
+ nc,cl=0,[]
+
+ ifnotdoctype:
+ doctype,docname,limit=webnotes.form_dict.get('dt'),webnotes.form_dict.get('dn'),webnotes.form_dict.get('limit')
+
+ try:
+ nc=int(webnotes.conn.sql("select count(*) from `tabComment Widget Record` where comment_doctype=%s and comment_docname=%s",(doctype,docname))[0][0])
+ ifnc:
+ cl=webnotes.conn.sql("select comment, ifnull(comment_by_fullname, comment_by) AS 'comment_by_fullname', creation from `tabComment Widget Record` where comment_doctype=%s and comment_docname=%s order by creation desc limit %s"%('%s','%s',limit),(doctype,docname),as_dict=1)
+
+ exceptException,e:
+ ife.args[0]==1146:
+ # no table
+ make_comment_table()
+ else:
+ raisee
+
+ webnotes.response['n_comments'],webnotes.response['comment_list']=nc,cl
+
+#
+# make comment table
+#
+
[docs]defmake_comment_table():
+ "Make table for comments - if missing via import module"
+ webnotes.conn.commit()
+ fromwebnotes.modulesimportreload_doc
+ reload_doc('core','doctype','comment_widget_record')
+ webnotes.conn.begin()
+
+#===========================================================================================
+
[docs]defcheck_guest_access(doc):
+ ifwebnotes.session['user']=='Guest'andnotwebnotes.conn.sql("select name from tabDocPerm where role='Guest' and parent=%s and ifnull(`read`,0)=1",doc.doctype):
+ webnotes.msgprint("Guest not allowed to call this object")
+ raiseException
+
+# Runserverobj - run server calls from form
+#===========================================================================================
+
[docs]defcheck_integrity(doc):
+ importwebnotes
+
+ if(notwebnotes.model.meta.is_single(doc.doctype))and(notdoc.fields.get('__islocal')):
+ tmp=webnotes.conn.sql('SELECT modified FROM `tab%s` WHERE name="%s" for update'%(doc.doctype,doc.name))
+ iftmpandstr(tmp[0][0])!=str(doc.modified):
+ webnotes.msgprint('Document has been modified after you have opened it. To maintain the integrity of the data, you will not be able to save your changes. Please refresh this document. [%s/%s]'%(tmp[0][0],doc.modified))
+ return0
+
+ return1
+
+#===========================================================================================
+
+"""
+Server side methods called from DocBrowser
+"""
+
+importwebnotes
+fromwebnotes.utilsimportcint,cstr
+
+sql=webnotes.conn.sql
+
+
[docs]defget_menu_items():
+ """
+ Returns a list of items to show in `Options` of the Web Notes Toolbar
+ List contains Pages and Single DocTypes
+ """
+ importwebnotes.utils
+
+ rl=webnotes.user.get_roles()+[webnotes.session['user']]
+ role_options=["role = '"+r+"'"forrinrl]
+
+ sql=webnotes.conn.sql
+ menuitems=[]
+
+ # pages
+ pages=sql("select distinct parent from `tabPage Role` where docstatus!=2 and (%s)"%(' OR '.join(role_options)))
+
+ forpinpages:
+ tmp=sql("select icon, parent_node, menu_index, show_in_menu from tabPage where name = '%s'"%p[0])
+ iftmpandtmp[0][3]:
+ menuitems.append(['Page',p[0]or'',tmp[0][1]or'',tmp[0][0]or'',webnotes.utils.cint(tmp[0][2])])
+
+ # singles
+ tmp=sql("select smallicon, parent_node, menu_index, name from tabDocType where (show_in_menu = 1 and show_in_menu is not null)")
+ singles={}
+ fortintmp:singles[t[3]]=t
+
+ forpinwebnotes.user.can_read:
+ tmp=singles.get(p,None)
+ iftmp:menuitems.append([p,p,tmp[1]or'',tmp[0]or'',int(tmp[2]or0)])
+
+ returnmenuitems
+
+# --------------------------------------------------------------
+
[docs]defhas_result():
+ returnsql("select name from `tab%s` limit 1"%webnotes.form_dict.get('dt'))and'Yes'or'No'
+
+# --------------------------------------------------------------
+
+
[docs]defis_submittable(dt):
+ returnsql("select name from tabDocPerm where parent=%s and ifnull(submit,0)=1 and docstatus<1 limit 1",dt)
+
+# --------------------------------------------------------------
+
+
[docs]defcan_cancel(dt):
+ returnsql('select name from tabDocPerm where parent="%s" and ifnull(cancel,0)=1 and docstatus<1 and role in ("%s") limit 1'%(dt,'", "'.join(webnotes.user.get_roles())))
+
+# --------------------------------------------------------------
+
[docs]defget_dt_trend(dt):
+ ret={}
+ forrinsql("select datediff(now(),modified), count(*) from `tab%s` where datediff(now(),modified) between 0 and 30 group by date(modified)"%dt):
+ ret[cint(r[0])]=cint(r[1])
+ returnret
+
+# --------------------------------------------------------------
+
+
[docs]defget_columns(out,sf,fl,dt,tag_fields):
+ ifnotfl:
+ fl=sf
+
+ # subject
+ subject=webnotes.conn.get_value('DocType',dt,'subject')
+ ifsubject:
+ out['subject']=subject
+
+ # get fields from subject
+ importre
+ fl=re.findall('\%\( (?P<name> [^)]*) \)s',subject,re.VERBOSE)
+
+ iftag_fields:
+ fl+=[t.strip()fortintag_fields.split(',')]
+
+ res=[]
+ forfintuple(set(fl)):
+ iff:
+ res+=[[cor''forcinr]forrinsql("select fieldname, label, fieldtype, options from tabDocField where parent='%s' and fieldname='%s'"%(dt,f))]
+
+
+ returnres
+
+
+# --------------------------------------------------------------
+# NOTE: THIS SHOULD BE CACHED IN DOCTYPE CACHE
+# --------------------------------------------------------------
+
+
[docs]defget_dt_details():
+ """
+ Returns details called by DocBrowser this includes:
+ the filters, columns, subject and tag_fields
+ also if the doctype is of type "submittable"
+ """
+ fl=eval(webnotes.form_dict.get('fl'))
+ dt=webnotes.form_dict.get('dt')
+ tag_fields,description=webnotes.conn.get_value('DocType',dt,['tag_fields','description'])
+
+ submittable=is_submittable(dt)and1or0
+
+ out={
+ 'submittable':(is_submittable(dt)and1or0),
+ 'can_cancel':(can_cancel(dt)and1or0)
+ }
+
+ # filters
+ # -------
+
+ sf=sql("select search_fields from tabDocType where name=%s",dt)[0][0]or''
+
+ # get fields from in_filter (if not in search_fields)
+ ifnotsf.strip():
+ res=sql("select fieldname, label, fieldtype, options from tabDocField where parent=%s and `in_filter` = 1 and ifnull(fieldname,'') != ''",dt)
+ sf=[s[0]forsinres]
+ else:
+ sf=[s.strip()forsinsf.split(',')]
+ res=sql("select fieldname, label, fieldtype, options from tabDocField where parent='%s' and fieldname in (%s)"%(dt,'"'+'","'.join(sf)+'"'))
+
+ # select "link" options
+ res=[[cor''forcinr]forrinres]
+ forrinres:
+ ifr[2]=='Select'andr[3]andr[3].startswith('link:'):
+ tdt=r[3][5:]
+ ol=sql("select name from `tab%s` where docstatus!=2 order by name asc"%tdt)
+ r[3]="\n".join(['']+[o[0]foroinol])
+
+ ifnotres:
+ out['filters']=[['name','ID','Data','']]
+ else:
+ out['filters']=[['name','ID','Data','']]+res
+
+ # columns
+ # -------
+ res=get_columns(out,sf,fl,dt,tag_fields)
+
+ fromwebnotes.widgets.tagsimportcheck_user_tags
+ check_user_tags(dt)
+
+ out['columns']=[['name','ID','Link',dt],['modified','Modified','Data',''],['_user_tags','Tags','Data','']]+res
+ out['tag_fields']=tag_fields
+ out['description']=description
+
+ returnout
+
+
+# --------------------------------------------------------------
+
+
[docs]defget_trend():
+ return{'trend':get_dt_trend(webnotes.form_dict.get('dt'))}
+
+
+
+
+
+#
+# delete and archive in docbrowser
+#
[docs]classPage:
+ """
+ A page class helps in loading a Page in the system. On loading
+
+ * Page will import Client Script from other pages where specified by `$import(page_name)`
+ * Execute dynamic HTML if the `content` starts with `#python`
+ """
+ def__init__(self,name):
+ self.name=name
+
+
[docs]defget(name):
+ """
+ Return the :term:`doclist` of the `Page` specified by `name`
+ """
+ returnPage(name).load()
+
+
[docs]defgetpage():
+ """
+ Load the page from `webnotes.form` and send it via `webnotes.response`
+ """
+ doclist=get(webnotes.form.getvalue('name'))
+
+ # send
+ webnotes.response['docs']=doclist
+
[docs]defget_doc_content(dt,dn):
+ """
+ Gets the HTML content of a document record by using the overridden or standard :method: `doclist.to_html`
+ """
+ importwebnotes.model.code
+
+ ifdtinwebnotes.user.get_read_list():
+ # generate HTML
+ do=webnotes.model.code.get_obj(dt,dn,with_children=1)
+ ifhasattr(do,'to_html'):
+ returndn,do.to_html()
+ else:
+ importwebnotes.model.doclist
+ returndn,webnotes.model.doclist.to_html(do.doclist)
+ else:
+ return'Forbidden - 404','<h1>Forbidden - 404</h1>'
+
+# find the page to load as static
+# -------------------------------
+
[docs]defget_parent_dt(dt):
+ pdt=''
+ ifsql('select name from `tabDocType` where istable=1 and name="%s"'%dt):
+ res=sql('select parent from `tabDocField` where fieldtype="Table" and options="%s"'%dt)
+ ifres:pdt=res[0][0]
+ returnpdt
+
+
[docs]defget_sql_meta(tl):
+ std_columns={
+ 'owner':('Owner','','','100'),
+ 'creation':('Created on','Date','','100'),
+ 'modified':('Last modified on','Date','','100'),
+ 'modified_by':('Modified By','','','100')
+ }
+
+ meta={}
+
+ fordtintl:
+ meta[dt]=std_columns.copy()
+
+ # for table doctype, the ID is the parent id
+ pdt=get_parent_dt(dt)
+ ifpdt:
+ meta[dt]['parent']=('ID','Link',pdt,'200')
+
+ # get the field properties from DocField
+ res=sql("select fieldname, label, fieldtype, options, width from tabDocField where parent='%s'"%dt)
+ forrinres:
+ ifr[0]:
+ meta[dt][r[0]]=(r[1],r[2],r[3],r[4]);
+
+ # name
+ meta[dt]['name']=('ID','Link',dt,'200')
+
+ returnmeta
+
+# Additional conditions to fulfill match permission rules
+# ====================================================================
+
+
[docs]defgetmatchcondition(dt,ud,ur):
+ res=sql("SELECT `role`, `match` FROM tabDocPerm WHERE parent = '%s' AND (`read`=1) AND permlevel = 0"%dt)
+ cond=[]
+ forrinres:
+ ifr[0]inur:# role applicable to user
+ ifr[1]:
+ defvalues=ud.get(r[1],['_NA'])
+ fordindefvalues:
+ cond.append('`tab%s`.`%s`="%s"'%(dt,r[1],d))
+ else:# nomatch i.e. full read rights
+ return''
+
+ return' OR '.join(cond)
+
[docs]defbuild_description_standard(meta,tl):
+
+ desc=webnotes.conn.get_description()
+
+ colnames,coltypes,coloptions,colwidths=[],[],[],[]
+
+ # merged metadata - used if we are unable to
+ # get both the table name and field name from
+ # the description - in case of joins
+ merged_meta={}
+ fordinmeta:
+ merged_meta.update(meta[d])
+
+ forfindesc:
+ fn,dt=f[0],''
+ if'.'infn:
+ dt,fn=fn.split('.')
+
+ if(notdt)andmerged_meta.get(fn):
+ # no "AS" given, find type from merged description
+
+ desc=merged_meta[fn]
+ colnames.append(desc[0]orfn)
+ coltypes.append(desc[1]or'')
+ coloptions.append(desc[2]or'')
+ colwidths.append(desc[3]or'100')
+
+ elifmeta.get(dt,{}).has_key(fn):
+ # type specified for a multi-table join
+ # usually from Report Builder
+
+ desc=meta[dt][fn]
+ colnames.append(desc[0]orfn)
+ coltypes.append(desc[1]or'')
+ coloptions.append(desc[2]or'')
+ colwidths.append(desc[3]or'100')
+
+ else:
+ # nothing found
+ # guess
+
+ colnames.append(fn)
+ coltypes.append(guess_type(f[1]))
+ coloptions.append('')
+ colwidths.append('100')
+
+ returncolnames,coltypes,coloptions,colwidths
+
+# Entry Point - Run the query
+# ====================================================================
+
+
[docs]defrunquery(q='',ret=0,from_export=0):
+ importwebnotes.utils
+
+ formatted=cint(form.getvalue('formatted'))
+
+ # CASE A: Simple Query
+ # --------------------
+ ifform.getvalue('simple_query')orform.getvalue('is_simple'):
+ q=form.getvalue('simple_query')orform.getvalue('query')
+ ifq.split()[0].lower()!='select':
+ raiseException,'Query must be a SELECT'
+
+ as_dict=cint(form.getvalue('as_dict'))
+ res=sql(q,as_dict=as_dict,as_list=notas_dict,formatted=formatted)
+
+ # build colnames etc from metadata
+ colnames,coltypes,coloptions,colwidths=[],[],[],[]
+
+ # CASE B: Standard Query
+ # -----------------------
+ else:
+ ifnotq:q=form.getvalue('query')
+
+ tl=get_sql_tables(q)
+ meta=get_sql_meta(tl)
+
+ q=add_match_conditions(q,tl,webnotes.user.roles,webnotes.user.get_defaults())
+
+ # replace special variables
+ q=q.replace('__user',session['user'])
+ q=q.replace('__today',webnotes.utils.nowdate())
+
+ res=sql(q,as_list=1,formatted=formatted)
+
+ colnames,coltypes,coloptions,colwidths=build_description_standard(meta,tl)
+
+ # run server script
+ # -----------------
+ style,header_html,footer_html,page_template='','','',''
+ ifform.has_key('sc_id')andform.getvalue('sc_id'):
+ sc_id=form.getvalue('sc_id')
+ fromwebnotes.model.codeimportget_code
+ sc_details=webnotes.conn.sql("select module, standard, server_script from `tabSearch Criteria` where name=%s",sc_id)[0]
+ ifsc_details[1]!='No':
+ code=get_code(sc_details[0],'Search Criteria',sc_id,'py')
+ else:
+ code=sc_details[2]
+
+ ifcode:
+ filter_values=form.has_key('filter_values')andeval(form.getvalue('filter_values',''))or{}
+ res,style,header_html,footer_html,page_template=exec_report(code,res,colnames,colwidths,coltypes,coloptions,filter_values,q,from_export)
+
+ out['colnames']=colnames
+ out['coltypes']=coltypes
+ out['coloptions']=coloptions
+ out['colwidths']=colwidths
+ out['header_html']=header_html
+ out['footer_html']=footer_html
+ out['page_template']=page_template
+
+ ifstyle:
+ out['style']=style
+
+ # just the data - return
+ ifret==1:
+ returnres
+
+ out['values']=res
+
+ # return num of entries
+ qm=form.has_key('query_max')andform.getvalue('query_max')or''
+ ifqmandqm.strip():
+ ifqm.split()[0].lower()!='select':
+ raiseException,'Query (Max) must be a SELECT'
+ ifnotform.has_key('simple_query'):
+ qm=add_match_conditions(qm,tl,webnotes.user.roles,webnotes.user.defaults)
+
+ out['n_values']=webnotes.utils.cint(sql(qm)[0][0])
+
+# Export to CSV
+# ====================================================================
+
+# Search
+importwebnotes
+
+# this is called when a new doctype is setup for search - to set the filters
+
[docs]defgetsearchfields():
+
+ sf=webnotes.conn.sql("select search_fields from tabDocType where name=%s",webnotes.form.getvalue("doctype"))
+ sf=sfandsf[0][0]or''
+ sf=[s.strip()forsinsf.split(',')]
+ ifsfandsf[0]:
+ res=webnotes.conn.sql("select fieldname, label, fieldtype, options from tabDocField where parent='%s' and fieldname in (%s)"%(webnotes.form.getvalue("doctype","_NA"),'"'+'","'.join(sf)+'"'))
+ else:
+ res=[]
+
+ res=[[cor''forcinr]forrinres]
+ forrinres:
+ ifr[2]=='Select'andr[3]andr[3].startswith('link:'):
+ dt=r[3][5:]
+ ol=webnotes.conn.sql("select name from `tab%s` where docstatus!=2 order by name asc"%dt)
+ r[3]='\n'.join(['']+[o[0]foroinol])
+
+ webnotes.response['searchfields']=[['name','ID','Data','']]+res
+
+
[docs]defmake_query(fields,dt,key,txt,start,length):
+ return"""SELECT %(fields)s
+ FROM `tab%(dt)s`
+ WHERE `tab%(dt)s`.`%(key)s` LIKE '%(txt)s' AND `tab%(dt)s`.docstatus != 2
+ ORDER BY `tab%(dt)s`.`%(key)s`
+ DESC LIMIT %(start)s, %(len)s """%{
+ 'fields':fields,
+ 'dt':dt,
+ 'key':key,
+ 'txt':txt+'%',
+ 'start':start,
+ 'len':length
+ }
+
+
[docs]defget_std_fields_list(dt,key):
+ # get additional search fields
+ sflist=webnotes.conn.sql("select search_fields from tabDocType where name = '%s'"%dt)
+ sflist=sflistandsflist[0][0]andsflist[0][0].split(',')or[]
+
+ sflist=['name']+sflist
+ ifnotkeyinsflist:
+ sflist=sflist+[key]
+
+ return['`tab%s`.`%s`'%(dt,f.strip())forfinsflist]
+
[docs]defscrub_custom_query(query,key,txt):
+ if'%(key)s'inquery:
+ query=query.replace('%(key)s',key)
+ if'%s'inquery:
+ query=query.replace('%s',((txtor'')+'%'))
+ returnquery
+
+# this is called by the Link Field
+
[docs]defsearch_link():
+ importwebnotes.widgets.query_builder
+
+ txt=webnotes.form.getvalue('txt')
+ dt=webnotes.form.getvalue('dt')
+ query=webnotes.form.getvalue('query')
+
+ ifquery:
+ res=webnotes.conn.sql(scrub_custom_query(query,'name',txt))
+ else:
+ q=make_query(', '.join(get_std_fields_list(dt,'name')),dt,'name',txt,'0','10')
+ res=webnotes.widgets.query_builder.runquery(q,ret=1)
+
+ # make output
+ webnotes.response['results']=build_for_autosuggest(res)
+
+# this is called by the search box
+"""
+Server side functions for tagging.
+
+- Tags can be added to any record (doctype, name) in the system.
+- Items are filtered by tags
+- Top tags are shown in the sidebar (?)
+- Tags are also identified by the tag_fields property of the DocType
+
+Discussion:
+
+Tags are shown in the docbrowser and ideally where-ever items are searched.
+There should also be statistics available for tags (like top tags etc)
+
+
+Design:
+
+- free tags (user_tags) are stored in __user_tags
+- doctype tags are set in tag_fields property of the doctype
+- top tags merges the tags from both the lists (only refreshes once an hour (max))
+
+"""
+
+
+
[docs]defcheck_user_tags(dt):
+ "if the user does not have a tags column, then it creates one"
+ try:
+ webnotes.conn.sql("select `_user_tags` from `tab%s` limit 1"%dt)
+ exceptException,e:
+ ife.args[0]==1054:
+ DocTags(dt).setup()
+
+
+#
+# Add a new tag
+#
+
[docs]defadd_tag():
+ "adds a new tag to a record, and creates the Tag master"
+
+ f=webnotes.form_dict
+ tag,color=f.get('tag'),f.get('color')
+ dt,dn=f.get('dt'),f.get('dn')
+
+ DocTags(dt).add(dn,tag)
+
+ returntag
+
+#
+# remove tag
+#
+
[docs]defremove_tag():
+ "removes tag from the record"
+ f=webnotes.form_dict
+ tag,dt,dn=f.get('tag'),f.get('dt'),f.get('dn')
+
+ DocTags(dt).remove(dn,tag)
+
+
+
[docs]defadd(self,dn,tag):
+ """add a new user tag"""
+ self.create(tag)
+ tl=self.get_tags(dn).split(',')
+ ifnottagintl:
+ tl.append(tag)
+ self.update(dn,tl)
+ TagCounter(self.dt).update(tag,1)
+
+
[docs]defremove(self,dn,tag):
+ """remove a user tag"""
+ tl=self.get_tags(dn).split(',')
+ self.update(dn,filter(lambdax:x!=tag,tl))
+ TagCounter(self.dt).update(tag,-1)
+
+
[docs]defupdate(self,dn,tl):
+ """updates the _user_tag column in the table"""
+
+ tl=list(set(filter(lambdax:x,tl)))
+
+ try:
+ webnotes.conn.sql("update `tab%s` set _user_tags=%s where name=%s"% \
+ (self.dt,'%s','%s'),(','+','.join(tl),dn))
+ exceptException,e:
+ ife.args[0]==1054:
+ self.setup()
+ self.update(dn,tl)
+ else:raisee
+
+
[docs]defsetup_tags(self):
+ """creates the tabTag table if not exists"""
+ webnotes.conn.commit()
+ fromwebnotes.modules.module_managerimportreload_doc
+ reload_doc('core','doctype','tag')
+ webnotes.conn.begin()
+
+
[docs]defsetup(self):
+ """adds the _user_tags column if not exists"""
+ webnotes.conn.commit()
+ webnotes.conn.sql("alter table `tab%s` add column `_user_tags` varchar(180)"%self.dt)
+ webnotes.conn.begin()
+
+
+
+
+
+
+
+
+
[docs]classTagCounter:
+ """
+ Tag Counter stores tag count per doctype in table _tag_cnt
+ """
+ def__init__(self,doctype):
+ self.doctype=doctype
+
+ # setup / update tag cnt
+ # keeps tags in _tag_cnt (doctype, tag, cnt)
+ # if doctype cnt does not exist
+ # creates it for the first time
+
[docs]defupdate(self,tag,diff):
+ "updates tag cnt for a doctype and tag"
+ cnt=webnotes.conn.sql("select cnt from `_tag_cnt` where doctype=%s and tag=%s",(self.doctype,tag))
+
+ ifnotcnt:
+ # first time? build a cnt and add
+ self.new_tag(tag,1)
+ else:
+ webnotes.conn.sql("update `_tag_cnt` set cnt = ifnull(cnt,0) + (%s) where doctype=%s and tag=%s",\
+ (diff,self.doctype,tag))
+
+
+
[docs]defnew_tag(self,tag,cnt=0,dt=None):
+ "Creates a new row for the tag and doctype"
+ webnotes.conn.sql("insert into `_tag_cnt`(doctype, tag, cnt) values (%s, %s, %s)", \
+ (dtorself.doctype,tag,cnt))
+
[docs]defload_top(self):
+ try:
+ returnwebnotes.conn.sql("select tag, cnt from `_tag_cnt` where doctype=%s and cnt>0 order by cnt desc limit 10",self.doctype,as_list=1)
+ exceptException,e:
+ ife.args[0]==1146:
+ self.setup()
+ returnself.load_top()
+ else:raisee
+
+
[docs]defsetup(self):
+ "creates the tag cnt table from the DocType"
+ webnotes.conn.commit()
+ webnotes.conn.sql("""
+ create table `_tag_cnt` (
+ doctype varchar(180), tag varchar(22), cnt int(10),
+ primary key (doctype, tag), index cnt(cnt)) ENGINE=InnoDB
+ """)
+ webnotes.conn.begin()
+
+ # build all
+ fordtinwebnotes.conn.sql("select name from tabDocType where ifnull(issingle,0)=0 and docstatus<2"):
+ try:
+ self.build(dt[0])
+ exceptException,e:
+ ife.args[0]==1054:pass
+ else:raisee
+
+
+
+
+
[docs]defget_top_field_tags(dt):
+ tf=webnotes.conn.get_value('DocType',dt,'tag_fields')
+ ifnottf:return[]
+
+ # restrict to only 2 fields
+ tf=tuple(set(tf.split(',')))[:2]
+ tl=[]
+
+ fortintf:
+ t=t.strip()
+ # disastrous query but lets try it!
+ tl+=webnotes.conn.sql("""select `%s`, count(*), '%s' from `tab%s`
+ where docstatus!=2
+ and ifnull(`%s`, '')!=''
+ group by `%s`
+ order by count(*) desc
+ limit 10"""%(t,t,dt,t,t),as_list=1)
+
+ iftl:
+ tl.sort(lambdax,y:y[1]-x[1])
+
+ returntl[:10]
+
+# returns the top ranked 10 tags for the
+# doctype.
+# merges the top tags from fields and user tags
+
[docs]defget_top_tags(args=''):
+ "returns the top 10 tags for the doctype from fields (7) and users (3)"
+ tl=None
+ dt=webnotes.form_dict['doctype']
+
+ fromwebnotes.utils.cacheimportget_item
+
+ # if not reload, try and load from cache
+ ifnotcint(webnotes.form_dict.get('refresh')):
+ tl=get_item('tags-'+dt).get()
+
+ iftl:
+ returneval(tl)
+ else:
+ tl=TagCounter(dt).load_top()+get_top_field_tags(dt)
+ iftl:
+ tl.sort(lambdax,y:y[1]-x[1])
+ tl=tl[:20]
+
+ # set in cache and don't reload for an hour
+ get_item('tags-'+dt).set(tl,60*60)
+
+ returntl
+