diff --git a/conf/apache.conf b/conf/apache.conf index be284cd55e..decfa74f17 100644 --- a/conf/apache.conf +++ b/conf/apache.conf @@ -20,9 +20,9 @@ Listen 8080 # rewrite rule RewriteEngine on - # don't filter static files + # disallow static files RewriteRule ^(.*)/(lib/|app/|js/|css/|files/|backup/)(.*)$ $1/$2$3 [L] - RewriteRule ^(.*)/(app.html|unsupported.html|rss.xml|sitemap.xml|web.py|server.py)(.*)$ $1/$2$3 [L] + RewriteRule ^(.*)/(app.html|unsupported.html|blank.html|rss.xml|sitemap.xml|web.py|server.py)(.*)$ $1/$2$3 [L] # everything else is a web page RewriteRule ^(.*)/([^/]*)$ $1/web.py?page=$2 [L] diff --git a/core/page/data_import_tool/data_import_tool.py b/core/page/data_import_tool/data_import_tool.py index af33961bb5..18cede9c4a 100644 --- a/core/page/data_import_tool/data_import_tool.py +++ b/core/page/data_import_tool/data_import_tool.py @@ -117,18 +117,16 @@ def getdocfield(fieldname): @webnotes.whitelist(allow_roles=['System Manager', 'Administrator']) def upload(): """upload data""" - import csv global doctype_dl - from webnotes.utils.file_manager import get_uploaded_content import webnotes.model.doctype from webnotes.model.doc import Document + from webnotes.utils.datautils import read_csv_content_from_uploaded_file - fname, fcontent = get_uploaded_content() overwrite = webnotes.form_dict.get('overwrite') ret = [] - rows = read_csv_content(fcontent) + rows = read_csv_content_from_uploaded_file() # doctype doctype = rows[0][0].split(':')[1].strip() @@ -184,41 +182,11 @@ def upload(): webnotes.errprint(webnotes.getTraceback()) return ret -def read_csv_content(fcontent): - import csv - import webnotes - from webnotes.utils import cstr - - rows = [] - - try: - reader = csv.reader(fcontent.splitlines()) - # decode everything - csvrows = [[val for val in row] for row in reader] - - for row in csvrows: - newrow = [] - for val in row: - if webnotes.form_dict.get('ignore_encoding_errors'): - newrow.append(cstr(val.strip())) - else: - try: - newrow.append(unicode(val.strip(), 'utf-8')) - except UnicodeDecodeError, e: - raise Exception, """Some character(s) in row #%s, column #%s are - not readable by utf-8. Ignoring them. If you are importing a non - english language, please make sure your file is saved in the 'utf-8' - encoding.""" % (csvrows.index(row)+1, row.index(val)+1) - - rows.append(newrow) - - return rows - except Exception, e: - webnotes.msgprint("Not a valid Comma Separated Value (CSV File)") - raise e - def check_record(d, parenttype): """check for mandatory, select options, dates. these should ideally be in doclist""" + + from webnotes.utils.dateutils import user_to_str + if parenttype and not d.get('parent'): raise Exception, "parent is required." @@ -240,14 +208,7 @@ def check_record(d, parenttype): raise Exception, "%s must be one of:" % key if docfield.fieldtype=='Date' and val: - import datetime - dateformats = { - 'yyyy-mm-dd':'%Y-%m-%d', - 'dd/mm/yyyy':'%d/%m/%Y', - 'mm/dd/yyyy':'%m/%d/%Y' - } - d[key] = datetime.datetime.strptime(val, - dateformats[webnotes.form_dict['date_format']]).strftime('%Y-%m-%d') + d[key] = user_to_str(val, webnotes.form_dict['date_format']) def getlink(doctype, name): return '%(name)s' % locals() diff --git a/public/build.json b/public/build.json index b62270bad8..0c0a272c2e 100644 --- a/public/build.json +++ b/public/build.json @@ -11,6 +11,7 @@ "lib/public/js/wn/misc/user.js", "lib/public/js/lib/public/json2.js", "lib/public/js/wn/router.js", + "lib/public/js/wn/ui/messages.js", "lib/public/js/wn/ui/listing.js", "lib/public/js/wn/ui/filters.js", "lib/public/js/wn/views/container.js", diff --git a/public/html/blank.html b/public/html/blank.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/js/legacy/utils/datatype.js b/public/js/legacy/utils/datatype.js index c4c93b48bb..895d638009 100644 --- a/public/js/legacy/utils/datatype.js +++ b/public/js/legacy/utils/datatype.js @@ -110,7 +110,7 @@ function $s(ele, v, ftype, fopt) { ele.style.textAlign = 'right'; ele.innerHTML = v; } else if(ftype == 'Check') { - if(v) ele.innerHTML = ''; + if(v) ele.innerHTML = ''; else ele.innerHTML = ''; } else { ele.innerHTML = v; diff --git a/public/js/legacy/widgets/form/fields.js b/public/js/legacy/widgets/form/fields.js index 947c942b65..717f39a565 100644 --- a/public/js/legacy/widgets/form/fields.js +++ b/public/js/legacy/widgets/form/fields.js @@ -874,7 +874,7 @@ CheckField.prototype.validate = function(v) { CheckField.prototype.onmake = function() { this.checkimg = $a(this.disp_area, 'div'); var img = $a(this.checkimg, 'img'); - img.src = 'images/lib/ui/tick.gif'; + img.src = 'lib/images/ui/tick.gif'; $dh(this.checkimg); } diff --git a/public/js/legacy/widgets/listing.js b/public/js/legacy/widgets/listing.js index 1f6bccd3ab..9c1ce5b02b 100644 --- a/public/js/legacy/widgets/listing.js +++ b/public/js/legacy/widgets/listing.js @@ -99,7 +99,7 @@ Listing.prototype.make = function(parent) { // results this.results = $a($a(this.body_area, 'div','srs_results_area'),'div'); - this.fetching_area = $a(this.body_area, 'div','',{height:'120px', background:'url("images/lib/ui/square_loading.gif") center no-repeat', display:'none'}); + this.fetching_area = $a(this.body_area, 'div','',{height:'120px', background:'url("lib/images/ui/square_loading.gif") center no-repeat', display:'none'}); this.show_no_records = $a(this.body_area,'div','',{margin:'200px 0px', textAlign:'center', fontSize:'14px', color:'#888', display:'none'}); this.show_no_records.innerHTML = 'No Result'; @@ -155,7 +155,7 @@ Listing.prototype.make_toolbar = function() { } this.loading_img = $a(this.btn_area,'img','',{display:'none',marginBottom:'-2px'}); - this.loading_img.src = 'images/lib/ui/button-load.gif'; + this.loading_img.src = 'lib/images/ui/button-load.gif'; if(!keys(this.buttons).length) $dh(this.btn_area); @@ -202,7 +202,7 @@ Listing.prototype.add_filter = function(label, ftype, options, tname, fname, con // filter label var d1= $a(c,'div','',{fontSize:'11px', marginBottom:'2px'}); d1.innerHTML = label; - if(ftype=='Link') d1.innerHTML += ' '; + if(ftype=='Link') d1.innerHTML += ' '; var d2= $a(c,'div'); diff --git a/public/js/legacy/widgets/report_builder/datatable.js b/public/js/legacy/widgets/report_builder/datatable.js index d35b06c1a5..56d03dd2b6 100644 --- a/public/js/legacy/widgets/report_builder/datatable.js +++ b/public/js/legacy/widgets/report_builder/datatable.js @@ -89,14 +89,14 @@ _r.DataTable = function(html_fieldname, dt, repname, hide_toolbar) { this.no_data_tag = $a(this.wrapper, 'div', 'report_no_data'); this.no_data_tag.innerHTML = 'No Records Found'; - this.fetching_tag = $a(this.wrapper, 'div', '', {height:'100%', background:'url("images/lib/ui/square_loading.gif") center no-repeat', display:'none'}); + this.fetching_tag = $a(this.wrapper, 'div', '', {height:'100%', background:'url("lib/images/ui/square_loading.gif") center no-repeat', display:'none'}); } _r.DataTable.prototype.add_icon = function(parent, imgsrc) { var i = $a(parent, 'img'); i.style.padding = '2px'; i.style.cursor = 'pointer'; - i.setAttribute('src', 'images/lib/icons/'+imgsrc+'.gif'); + i.setAttribute('src', 'lib/images/icons/'+imgsrc+'.gif'); return i; } @@ -184,10 +184,10 @@ _r.DataTable.prototype.make_toolbar = function(parent) { } _r.DataTable.prototype.set_desc = function() { - this.sort_icon.src = 'images/lib/icons/arrow_down.gif'; this.sort_order='DESC'; + this.sort_icon.src = 'lib/images/icons/arrow_down.gif'; this.sort_order='DESC'; } _r.DataTable.prototype.set_asc = function(icon) { - this.sort_icon.src = 'images/lib/icons/arrow_up.gif'; this.sort_order='ASC'; + this.sort_icon.src = 'lib/images/icons/arrow_up.gif'; this.sort_order='ASC'; } //// diff --git a/public/js/legacy/widgets/tree.js b/public/js/legacy/widgets/tree.js index 1173f3dfab..acd3122d3c 100644 --- a/public/js/legacy/widgets/tree.js +++ b/public/js/legacy/widgets/tree.js @@ -31,8 +31,8 @@ function Tree(parent, width, do_animate) { this.is_root = 1; this.do_animate = do_animate; var me = this; - this.exp_img = 'images/lib/icons/plus.gif'; - this.col_img = 'images/lib/icons/minus.gif'; + this.exp_img = 'lib/images/icons/plus.gif'; + this.col_img = 'lib/images/icons/minus.gif'; this.body = $a(parent, 'div'); if(width)$w(this.body, width); @@ -124,7 +124,7 @@ function TreeNode(tree, parent, id, imagesrc, onclick, onexpand, opts, label) { $y(t2,{borderCollapse:'collapse'}); this.img_cell = $td(t2, 0, 0); $y(this.img_cell, {cursor:'pointer',verticalAlign:'middle',width:'20px'}); - if(!imagesrc) imagesrc = "images/lib/icons/folder.gif"; + if(!imagesrc) imagesrc = "lib/images/icons/folder.gif"; this.usrimg = $a(this.img_cell, 'img'); this.usrimg.src = imagesrc; diff --git a/public/js/lib/slickgrid/slick-default-theme.css b/public/js/lib/slickgrid/slick-default-theme.css index 8327da1e94..c7389ba3dd 100644 --- a/public/js/lib/slickgrid/slick-default-theme.css +++ b/public/js/lib/slickgrid/slick-default-theme.css @@ -6,17 +6,17 @@ classes should alter those! */ .slick-header-columns { - background: url('js/lib/slickgrid/images/header-columns-bg.gif') repeat-x center bottom; + background: url('lib/js/lib/slickgrid/images/header-columns-bg.gif') repeat-x center bottom; border-bottom: 1px solid silver; } .slick-header-column { - background: url('js/lib/slickgrid/images/header-columns-bg.gif') repeat-x center bottom; + background: url('lib/js/lib/slickgrid/images/header-columns-bg.gif') repeat-x center bottom; border-right: 1px solid silver; } .slick-header-column:hover, .slick-header-column-active { - background: white url('js/lib/slickgrid/images/header-columns-over-bg.gif') repeat-x center bottom; + background: white url('lib/js/lib/slickgrid/images/header-columns-over-bg.gif') repeat-x center bottom; } .slick-headerrow { @@ -61,11 +61,11 @@ classes should alter those! } .slick-group-toggle.expanded { - background: url(js/lib/slickgrid/images/collapse.gif) no-repeat center center; + background: url(lib/js/lib/slickgrid/images/collapse.gif) no-repeat center center; } .slick-group-toggle.collapsed { - background: url(js/lib/slickgrid/images/expand.gif) no-repeat center center; + background: url(lib/js/lib/slickgrid/images/expand.gif) no-repeat center center; } .slick-group-totals { @@ -107,7 +107,7 @@ classes should alter those! border: 1px solid gray; border-bottom: 0; border-top: 0; - background: url('js/lib/slickgrid/images/header-bg.gif') repeat-x center top; + background: url('lib/js/lib/slickgrid/images/header-bg.gif') repeat-x center top; color: black; height: 24px; line-height: 24px; @@ -229,7 +229,7 @@ input.editor-percentcomplete { display: inline-block; width: 16px; height: 100%; - background: url("js/lib/slickgrid/images/pencil.gif") no-repeat center center; + background: url("lib/js/lib/slickgrid/images/pencil.gif") no-repeat center center; overflow: visible; z-index: 1000; float: right; @@ -240,7 +240,7 @@ input.editor-percentcomplete { position: absolute; top: -2px; left: -9px; - background: url("js/lib/slickgrid/images/editor-helper-bg.gif") no-repeat top left; + background: url("lib/js/lib/slickgrid/images/editor-helper-bg.gif") no-repeat top left; padding-left: 9px; width: 120px; diff --git a/public/js/wn/ui/messages.js b/public/js/wn/ui/messages.js new file mode 100644 index 0000000000..d0ed216bbd --- /dev/null +++ b/public/js/wn/ui/messages.js @@ -0,0 +1,13 @@ +wn.provide("wn.messages") + +wn.messages.waiting = function(parent, msg, bar_percent) { + if(!bar_percent) bar_percent = '100'; + return $(repl('
\ +

%(msg)s

\ +
\ +
', { + bar_percent: bar_percent, + msg: msg + })) + .appendTo(parent); +}, \ No newline at end of file diff --git a/public/js/wn/ui/overlay.js b/public/js/wn/ui/overlay.js deleted file mode 100644 index 6ff6fad87f..0000000000 --- a/public/js/wn/ui/overlay.js +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com) -// -// MIT License (MIT) -// -// 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 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. -// - -// overlay an element -// http://blog.learnboost.com/blog/a-css3-overlay-system/ - -wn.ui.Overlay = function(ele) { - wn.require('lib/css/ui/overlay.css'); - - var me = this; - $.extend(this, { - render: function() { - me.wrap = wn.dom.add( - wn.dom.add( - wn.dom.add($('body').get(0), 'div', 'overlay') - , 'div', 'wrap-outer') - , 'div', 'wrap'); - me.wrap.appendChild(ele); - $('body').addClass('overlaid'); - }, - hide: function() { - wn.dom.hide(me.wrap); - $('body').removeClass('overlaid'); - } - }); - me.render(); -} \ No newline at end of file diff --git a/public/js/wn/views/grid_report.js b/public/js/wn/views/grid_report.js index 676aaa4a70..0e64f8d589 100644 --- a/public/js/wn/views/grid_report.js +++ b/public/js/wn/views/grid_report.js @@ -200,12 +200,7 @@ wn.views.GridReport = Class.extend({ }); }, make_waiting: function() { - this.waiting = $('
\ -

Loading Report...

\ -
\ -
') - .appendTo(this.wrapper); - + this.waiting = wn.messages.waiting(this.wrapper, "Loading Report...", '10'); }, load_filter_values: function() { var me = this; diff --git a/webnotes/install_lib/setup_public_folder.py b/webnotes/install_lib/setup_public_folder.py index 65a3d80e8d..b89f72a4c4 100644 --- a/webnotes/install_lib/setup_public_folder.py +++ b/webnotes/install_lib/setup_public_folder.py @@ -19,6 +19,7 @@ def make(): ["web.py", "../lib/public/html/web.py"], ["server.py", "../lib/public/html/server.py"], ["app.html", "../lib/public/html/app.html"], + ["blank.html", "../lib/public/html/blank.html"], ["unsupported.html", "../lib/public/html/unsupported.html"], ["sitemap.xml", "../lib/public/html/sitemap.xml"], ["rss.xml", "../lib/public/html/rss.xml"], diff --git a/webnotes/model/controller.py b/webnotes/model/controller.py new file mode 100644 index 0000000000..51794bfcc4 --- /dev/null +++ b/webnotes/model/controller.py @@ -0,0 +1,396 @@ +# Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com) +# +# MIT License (MIT) +# +# 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 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. +# + +""" +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 json, os +import webnotes.model +import webnotes.model.doc +import webnotes.model.doclist +import webnotes.utils.cache + +from webnotes import _ +from webnotes.utils import cint, cstr, now_datetime, get_datetime, get_datetime_str, comma_and, cast + +controllers = webnotes.DictObj() + +def get(doctype, name=None, module=None): + """return controller object""" + global controllers + + doclist = doctype + if isinstance(doctype, list): + doctype = doclist[0]["doctype"] + + # return if already loaded + if doctype not in controllers: + + from webnotes.modules import get_doc_path, scrub + + module_name = module or webnotes.conn.get_value("DocType", doctype, "module") or "Core" + doc_path = get_doc_path(module_name, 'doctype', doctype) + module_path = os.path.join(doc_path, scrub(doctype)+'.py') + + # load translations + if webnotes.can_translate(): + from webnotes.utils.translate import get_lang_data + webnotes._messages.update(get_lang_data(doc_path, None, 'py')) + + # vanilla controller + controllers[doctype] = get_controller_class(module_name, doctype, module_path) + + return controllers[doctype](doclist, name) + + +def get_controller_class(module_name, doctype, module_path): + if os.path.exists(module_path): + from webnotes.modules import get_doc_path, scrub + module = __import__(scrub(module_name) + '.doctype.' + scrub(doctype) + '.' \ + + scrub(doctype), fromlist = [scrub(doctype)]) + + # find controller in module + import inspect + for attr in dir(module): + attrobj = getattr(module, attr) + if inspect.isclass(attrobj) and attr.startswith(doctype.replace(' ', '').replace('-', '')) \ + and issubclass(attrobj, DocListController): + return attrobj + + return DocListController + +class DocListController(object): + """ + Collection of Documents with one parent and multiple children + """ + def __init__(self, doctype=None, name=None): + if doctype: + self.load(doctype, name) + if hasattr(self, "setup"): + self.setup() + + def load(self, doctype, name=None): + if isinstance(doctype, list): + self.set_doclist(doctype) + return + + if not name: name = doctype + + self.set_doclist(self.load_doclist(doctype, name)) + + def load_doclist(self, doctype, name): + return webnotes.model.doclist.load(doctype, name) + + def set_doclist(self, doclist): + if not isinstance(doclist, webnotes.model.doclist.DocList): + self.doclist = webnotes.model.doclist.objectify(doclist) + else: + self.doclist = doclist + self.doc = self.doclist[0] + + def save(self): + """Save the doclist""" + # if docstatus is null, set it as 0 + self.doc.docstatus = self.doc.docstatus or 0 + + self.prepare_for_save() + self.run('validate') + self.doctype_validate() + + from webnotes.model.doctype import get_property + if get_property(self.doc.doctype, "document_type") in ["Master", "Transaction"]\ + and not self.doc.get("__islocal"): + from webnotes.model.doclist import load + # get the old doclist + try: + oldlist = load(self.doc.doctype, self.doc.name) + except NameError, e: + oldlist = None + else: + oldlist = None + + self.save_main() + self.save_children() + self.run('on_update') + + # version is saved after save, because we need names + if oldlist: + self.save_version(oldlist) + + def prepare_for_save(self): + """Set owner, modified etc before saving""" + self.check_if_latest() + self.check_permission() + self.check_links() + self.check_mandatory() + self.update_timestamps() + + def save_main(self): + """Save the main doc""" + self.doc.save(cint(self.doc.get('__islocal'))) + + def save_children(self): + """Save Children, with the new parent name""" + child_map = {} + + for d in self.doclist[1:]: + if d.has_key('parentfield'): + d.parent = self.doc.name # rename if reqd + d.parenttype = self.doc.doctype + + # set docstatus of children as that of parent + d.docstatus = self.doc.docstatus + d.modified = self.doc.modified + d.modified_by = self.doc.modified_by + + d.save(new = cint(d.get('__islocal'))) + + child_map.setdefault(d.doctype, []).append(d.name) + + # delete all children in database that are not in the child_map + self.remove_children(child_map) + + def save_version(self, oldlist): + """create a new version of given difflist""" + from webnotes.model.versions import save_version + save_version(oldlist, self.doclist) + + def remove_children(self, child_map): + """delete children from database if they do not exist in the doclist""" + # get all children types + tablefields = webnotes.conn.get_table_fields(self.doc.doctype) + + for dt in tablefields: + cnames = child_map.get(dt['options']) or [] + if cnames: + webnotes.conn.sql("""delete from `tab%s` where parent=%s + and parenttype=%s and name not in (%s)""" % \ + (dt['options'], '%s', '%s', ','.join(['%s'] * len(cnames))), + tuple([self.doc.name, self.doc.doctype] + cnames)) + else: + webnotes.conn.sql("""delete from `tab%s` where parent=%s + and parenttype=%s""" % (dt['options'], '%s', '%s'), + (self.doc.name, self.doc.doctype)) + + def check_if_latest(self): + """Raises exception if the modified time is not the same as in the database""" + if not (webnotes.conn.is_single(self.doc.doctype) or \ + cint(self.doc.get('__islocal'))): + modified = webnotes.conn.sql("""select modified from `tab%s` + where name=%s for update""" % (self.doc.doctype, "%s"), + self.doc.name or "") + + if modified and get_datetime_str(modified[0].modified) != \ + get_datetime_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.)"""), + raise_exception=webnotes.IntegrityError) + + def check_permission(self): + """Raises exception if permission is not valid""" + # hail the administrator - nothing can stop you! + if webnotes.session.user == "Administrator": + return + + doctypelist = webnotes.model.get_doctype("DocType", self.doc.doctype) + if not hasattr(self, "user_roles"): + self.user_roles = webnotes.user and webnotes.user.get_roles() or ["Guest"] + if not hasattr(self, "user_defaults"): + self.user_defaults = webnotes.user and webnotes.user.get_defaults() or {} + + has_perm = False + match = [] + + # check if permission exists and if there is any match condition + for perm in doctypelist.get({"doctype": "DocPerm"}): + if cint(perm.permlevel) == 0 and cint(perm.read) == 1 and perm.role in self.user_roles: + has_perm = True + if perm.match and match != -1: + match.append(perm.match) + else: + # this indicates that there exists atleast one permission + # where match is not specified + match = -1 + + # check match conditions + if has_perm and match and match != -1: + for match_field in match: + if self.doc.get(match_field, "no_value") in self.user_defaults.get(match_field, []): + # field value matches with user's credentials + has_perm = True + break + else: + # oops, illegal value + has_perm = False + webnotes.msgprint(_("""Value: "%s" is not allowed for field "%s" """) % \ + (self.doc.get(match_field, "no_value"), + doctypelist.get_field(match_field).label)) + + if not has_perm: + webnotes.msgprint(_("""Not enough permissions to save %s: "%s" """) % \ + (self.doc.doctype, self.doc.name), raise_exception=webnotes.PermissionError) + + def check_links(self): + """Checks integrity of links (throws exception if links are invalid)""" + from webnotes.model.doctype import get_link_fields + link_fields = {} + error_list = [] + for doc in self.doclist: + for lf in link_fields.setdefault(doc.doctype, get_link_fields(doc.doctype)): + options = (lf.options or "").split("\n")[0].strip() + options = options.startswith("link:") and options[5:] or options + if doc.get(lf.fieldname) and options and \ + not webnotes.conn.exists(options, doc[lf.fieldname]): + error_list.append((options, doc[lf.fieldname], lf.label)) + + if error_list: + webnotes.msgprint(_("""The following values do not exist in the database: %s. + Please correct these values and try to save again.""") % \ + comma_and(["%s: \"%s\" (specified in field: %s)" % err for err in error_list]), + raise_exception=webnotes.InvalidLinkError) + + def check_mandatory(self): + """check if all required fields have value""" + reqd = [] + for doc in self.doclist: + for df in webnotes.model.get_doctype(doc.doctype).get({ + "parent": doc.doctype, "doctype": "DocField", "reqd": 1}): + if doc.get(df.fieldname) is None: + reqd.append("""\"%s\" is a Mandatory field [in %s%s]""" % \ + (df.label, df.parent, doc.idx and " - %d" % doc.idx or "")) + if reqd: + webnotes.msgprint(_("In") + " %s - %s\n" % (self.doc.doctype, self.doc.name or "") + + "\n".join(reqd), + raise_exception=webnotes.MandatoryError) + + def update_timestamps(self): + """Update owner, creation, modified_by, modified, docstatus""" + ts = get_datetime(now_datetime()) + + for d in self.doclist: + if self.doc.get('__islocal'): + d.owner = webnotes.session.user + d.creation = ts + + d.modified_by = webnotes.session.user + d.modified = ts + + def doctype_validate(self): + """run DocType Validator""" + from core.doctype.doctype_validator.doctype_validator import validate + validate(self) + + def run(self, method, args=None): + if hasattr(self, method): + if args: + getattr(self, method)(args) + else: + getattr(self, method)() + + # if possible, deprecate + trigger(method, self.doclist[0]) + + def clear_table(self, table_field): + self.doclist = filter(lambda d: d.parentfield != table_field, self.doclist) + + def add_child(self, doc): + """add a child doc to doclist""" + # make child + if not isinstance(doc, webnotes.model.doc.Document): + doc = webnotes.model.doc.Document(fielddata = doc) + doc.__islocal = 1 + doc.parent = self.doc.name + doc.parenttype = self.doc.doctype + # parentfield is to be supplied in the doc + + # add to doclist + self.doclist.append(doc) + + def export(self): + """export current doc to file system""" + import conf + if (getattr(conf,'developer_mode', 0) and not getattr(webnotes, 'syncing', False) + and not getattr(webnotes, "testing", False)): + from webnotes.modules.export import export_to_files + export_to_files(record_list=self.doclist) + + def set_as_default(self, filters=None): + """sets is_default to 0 in rest of the related documents""" + if self.doc.is_default: + conditions, filters = webnotes.conn.build_conditions(filters) + filters.update({"name": self.doc.name}) + webnotes.conn.sql("""update `tab%s` set `is_default`=0 + where %s and name!=%s""" % (self.doc.doctype, conditions, "%(name)s"), + filters) + + def set_default_values(self): + """set's default values in doclist""" + import webnotes.model.utils + for doc in self.doclist: + for df in webnotes.model.get_doctype(doc.doctype).get({ + "parent": doc.doctype, "doctype": "DocField"}): + if doc.get(df.fieldname) in [None, ""] and df.default: + doc[df.fieldname] = cast(df, df.default) + + # TODO: should this method be here? + def get_csv_from_attachment(self): + """get csv from attachment""" + if not self.doc.file_list: + msgprint("File not attached!") + raise Exception + + # get file_id + fid = self.doc.file_list.split(',')[1] + + # get file from file_manager + try: + from webnotes.utils import file_manager + fn, content = file_manager.get_file(fid) + except Exception, e: + webnotes.msgprint(_("Unable to open attached file. Please try again.")) + raise e + + # convert char to string (?) + if not isinstance(content, basestring) and hasattr(content, 'tostring'): + content = content.tostring() + + import csv + return csv.reader(content.splitlines()) + +def trigger(method, doc): + """trigger doctype events""" + try: + import startup.event_handlers + except ImportError: + return + + if hasattr(startup.event_handlers, method): + getattr(startup.event_handlers, method)(doc) + + if hasattr(startup.event_handlers, 'doclist_all'): + startup.event_handlers.doclist_all(doc, method) diff --git a/webnotes/model/doc.py b/webnotes/model/doc.py index 22c21fb888..c7f8b8b0dd 100755 --- a/webnotes/model/doc.py +++ b/webnotes/model/doc.py @@ -73,9 +73,9 @@ class Document: self._prefix = prefix if fielddata: - self.fields = fielddata + self.fields = webnotes.DictObj(fielddata) else: - self.fields = {} + self.fields = webnotes.DictObj() if not self.fields.has_key('name'): self.fields['name']='' # required on save @@ -95,6 +95,9 @@ class Document: else: if not fielddata: self.fields['__islocal'] = 1 + + if not self.fields.docstatus: + self.fields.docstatus = 0 def __nonzero__(self): return True @@ -541,7 +544,7 @@ class Document: d.owner = webnotes.session['user'] if local: - d.fields['__islocal'] = '1' # for Client to identify unsaved doc + d.fields['__islocal'] = 1 # for Client to identify unsaved doc else: d.save(new=1) diff --git a/webnotes/model/doclist.py b/webnotes/model/doclist.py index c57c647a6e..01351755d6 100644 --- a/webnotes/model/doclist.py +++ b/webnotes/model/doclist.py @@ -219,7 +219,7 @@ class DocList: for d in self.children: if d.fields.has_key('parent'): - if d.parent and (not d.parent.startswith('old_parent:')): + if d.parent: d.parent = self.doc.name # rename if reqd d.parenttype = self.doc.doctype @@ -257,7 +257,7 @@ class DocList: Save & Submit - set docstatus = 1, run "on_submit" """ if self.doc.docstatus != 0: - msgprint("Only draft can be submitted", raise_exception=1) + webnotes.msgprint("Only draft can be submitted", raise_exception=1) self.to_docstatus = 1 self.save() self.run_method('on_submit') diff --git a/webnotes/model/sync.py b/webnotes/model/sync.py index 0c7285b67c..1671d3941c 100644 --- a/webnotes/model/sync.py +++ b/webnotes/model/sync.py @@ -4,6 +4,8 @@ from __future__ import unicode_literals perms will get synced only if none exist """ import webnotes +import os +import conf def sync_all(force=0): modules = [] @@ -18,41 +20,38 @@ def sync_all(force=0): return modules def sync_core_doctypes(force=0): - import os - import core # doctypes - return walk_and_sync(os.path.abspath(os.path.dirname(core.__file__)), force) + return walk_and_sync(os.path.join(os.path.dirname(conf.__file__), 'lib'), force) def sync_modules(force=0): - import conf, os return walk_and_sync(os.path.join(os.path.dirname(conf.__file__), 'app'), force) def walk_and_sync(start_path, force=0): """walk and sync all doctypes and pages""" - import os from webnotes.modules import reload_doc modules = [] for path, folders, files in os.walk(start_path): - for f in files: - if f.endswith(".txt"): - # great grand-parent folder is module_name - module_name = path.split(os.sep)[-3] - if not module_name in modules: - modules.append(module_name) + if os.path.basename(os.path.dirname(path)) in ('doctype', 'page'): + for f in files: + if f.endswith(".txt"): + # great grand-parent folder is module_name + module_name = path.split(os.sep)[-3] + if not module_name in modules: + modules.append(module_name) - # grand parent folder is doctype - doctype = path.split(os.sep)[-2] + # grand parent folder is doctype + doctype = path.split(os.sep)[-2] - # parent folder is the name - name = path.split(os.sep)[-1] + # parent folder is the name + name = path.split(os.sep)[-1] - if doctype == 'doctype': - sync(module_name, name, force) - elif doctype in ['page']:#, 'search_criteria', 'Print Format', 'DocType Mapper']: - if reload_doc(module_name, doctype, name): - print module_name + ' | ' + doctype + ' | ' + name + if doctype == 'doctype': + sync(module_name, name, force) + elif doctype in ['page']:#, 'search_criteria', 'Print Format', 'DocType Mapper']: + if reload_doc(module_name, doctype, name): + print module_name + ' | ' + doctype + ' | ' + name return modules @@ -87,7 +86,6 @@ def sync(module_name, docname, force=0): def get_file_path(module_name, docname): if not (module_name and docname): raise Exception('No Module Name or DocName specified') - import os module = __import__(module_name) module_init_path = os.path.abspath(module.__file__) module_path = os.sep.join(module_init_path.split(os.sep)[:-1]) @@ -139,7 +137,6 @@ def sync_install(force=1): load_install_docs(modules) def load_install_docs(modules): - import os if isinstance(modules, basestring): modules = [modules] for module_name in modules: diff --git a/webnotes/model/utils.py b/webnotes/model/utils.py index e702cbc461..5623ce2524 100644 --- a/webnotes/model/utils.py +++ b/webnotes/model/utils.py @@ -100,7 +100,7 @@ def getlist(doclist, field): from webnotes.utils import cint l = [] for d in doclist: - if d.parent and (not d.parent.lower().startswith('old_parent:')) and d.parentfield == field: + if d.parentfield == field: l.append(d) l.sort(lambda a, b: cint(a.idx) - cint(b.idx)) diff --git a/webnotes/password_reset.txt b/webnotes/password_reset.txt deleted file mode 100644 index 331b42b859..0000000000 --- a/webnotes/password_reset.txt +++ /dev/null @@ -1,9 +0,0 @@ -## Password Reset - -Dear %(user)s, - -Your new password is: %(password)s - -Please log in using this new password, and don't forget to change it. - -Thanks! \ No newline at end of file diff --git a/webnotes/utils/__init__.py b/webnotes/utils/__init__.py index 696f8cdd93..aecadbb181 100644 --- a/webnotes/utils/__init__.py +++ b/webnotes/utils/__init__.py @@ -259,13 +259,18 @@ user_format = None * dd/mm/yyyy """ -def formatdate(string_date): +def formatdate(string_date=None): """ Convers the given string date to :data:`user_format` """ global user_format + if not user_format: user_format = webnotes.conn.get_value('Control Panel', None, 'date_format') + + if not string_date: + string_date = nowdate() + if not isinstance(string_date, basestring): string_date = str(string_date) d = string_date.split('-'); diff --git a/webnotes/utils/datautils.py b/webnotes/utils/datautils.py new file mode 100644 index 0000000000..d371a4187c --- /dev/null +++ b/webnotes/utils/datautils.py @@ -0,0 +1,57 @@ +# Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com) +# +# MIT License (MIT) +# +# 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 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. +# + +def read_csv_content_from_uploaded_file(): + import csv + import webnotes + from webnotes.utils import cstr + from webnotes.utils.file_manager import get_uploaded_content + + fname, fcontent = get_uploaded_content() + + rows = [] + + try: + reader = csv.reader(fcontent.splitlines()) + # decode everything + csvrows = [[val for val in row] for row in reader] + + for row in csvrows: + newrow = [] + for val in row: + if webnotes.form_dict.get('ignore_encoding_errors'): + newrow.append(cstr(val.strip())) + else: + try: + newrow.append(unicode(val.strip(), 'utf-8')) + except UnicodeDecodeError, e: + raise Exception, """Some character(s) in row #%s, column #%s are + not readable by utf-8. Ignoring them. If you are importing a non + english language, please make sure your file is saved in the 'utf-8' + encoding.""" % (csvrows.index(row)+1, row.index(val)+1) + + rows.append(newrow) + + return rows + except Exception, e: + webnotes.msgprint("Not a valid Comma Separated Value (CSV File)") + raise e diff --git a/webnotes/utils/dateutils.py b/webnotes/utils/dateutils.py new file mode 100644 index 0000000000..182b0b4e65 --- /dev/null +++ b/webnotes/utils/dateutils.py @@ -0,0 +1,48 @@ +# Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com) +# +# MIT License (MIT) +# +# 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 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. +# + +import webnotes + +user_date_format = None + +def user_to_str(date, date_format = None): + if not date: return date + + import datetime + + if not date_format: + if not user_date_format: + global user_date_format + user_date_format = webnotes.conn.get_value("Control Panel", None, "date_format") + + date_format = user_date_format + + dateformats = { + 'yyyy-mm-dd':'%Y-%m-%d', + 'dd/mm/yyyy':'%d/%m/%Y', + 'mm/dd/yyyy':'%m/%d/%Y', + 'dd-mm-yyyy':'%d-%m-%Y' + } + return datetime.datetime.strptime(date, + dateformats[date_format]).strftime('%Y-%m-%d') + + \ No newline at end of file diff --git a/webnotes/utils/webservice.py b/webnotes/utils/webservice.py deleted file mode 100644 index 6d21fad8f7..0000000000 --- a/webnotes/utils/webservice.py +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com) -# -# MIT License (MIT) -# -# 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 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. -# - -from __future__ import unicode_literals -import webnotes -import webnotes.utils - -class FrameworkServer: - """ - 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 - if not (remote_host and path): - raise Exception, "Server address and path necessary" - - if not ((user and password) or (cookies)): - raise Exception, "Either cookies or user/password necessary" - - self.remote_host = remote_host - self.path = path - self.cookies = cookies or {} - self.webservice_method='POST' - self.account = account - self.account_id = None - self.https = https - self.conn = None - - # login - if not cookies: - args = { 'usr': user, 'pwd': password, 'ac_name': account } - - if opts: - args.update(opts) - - res = self.http_get_response('login', args) - - ret = res.read() - try: - ret = eval(ret) - except Exception, e: - webnotes.msgprint(ret) - raise Exception, e - - 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') - - 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 - - # ----------------------------------------------------------------------------------------- - - def http_get_response(self, method, args): - """ - Run a method on the remote server, with the given arguments - """ - # get response from remote server - - import urllib, urllib2, os - - args['cmd'] = method - if self.path.startswith('/'): self.path = self.path[1:] - - protocol = self.https and 'https://' or 'http://' - req = urllib2.Request(protocol + os.path.join(self.remote_host, self.path, 'index.cgi'), \ - urllib.urlencode(args)) - for key in self.cookies: - req.add_header('cookie', '; '.join(['%s=%s' % (key, self.cookies[key]) \ - for key in self.cookies])) - - return urllib2.urlopen(req) - - # ----------------------------------------------------------------------------------------- - - def _extract_cookies(self, res): - import Cookie - cookies = Cookie.SimpleCookie() - cookies.load(res.headers.get('set-cookie')) - for c in cookies.values(): - self.cookies[c.key] = c.value.rstrip(',') - - # ----------------------------------------------------------------------------------------- - - - def runserverobj(self, doctype, docname, method, arg=''): - """ - 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 = json.loads(res.read()) - if ret.get('exc'): - raise Exception, ret.get('exc') - return ret - - # ----------------------------------------------------------------------------------------- - - def run_method(self, method, args={}): - """ - 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