From 683e4dc9d3ca900ba26f4160e3199084ec64102b Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 12 Feb 2013 19:20:39 +0530 Subject: [PATCH 1/3] upgraded test_runner, now can run multiple tests --- conf/Framework.sql | 2 +- core/doctype/letter_head/letter_head.py | 2 - core/doctype/letter_head/test_letter_head.py | 1 + core/doctype/profile/profile.py | 14 +- core/doctype/profile/test_profile.py | 12 ++ .../workflow_state/test_workflow_state.py | 1 + core/doctype/workflow_state/workflow_state.py | 2 - webnotes/__init__.py | 35 ++--- webnotes/db.py | 31 ++++- webnotes/model/code.py | 7 +- webnotes/model/meta.py | 15 +- webnotes/model/wrapper.py | 5 + webnotes/test_runner.py | 129 +++++++++++++----- webnotes/utils/email_lib/bulk.py | 23 ++-- 14 files changed, 173 insertions(+), 106 deletions(-) create mode 100644 core/doctype/letter_head/test_letter_head.py create mode 100644 core/doctype/profile/test_profile.py create mode 100644 core/doctype/workflow_state/test_workflow_state.py diff --git a/conf/Framework.sql b/conf/Framework.sql index 35a8aabc01..5920a8cacf 100644 --- a/conf/Framework.sql +++ b/conf/Framework.sql @@ -160,7 +160,7 @@ DROP TABLE IF EXISTS `tabSeries`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tabSeries` ( - `name` varchar(40) DEFAULT NULL, + `name` varchar(100) DEFAULT NULL, `current` int(10) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/core/doctype/letter_head/letter_head.py b/core/doctype/letter_head/letter_head.py index 61ba745fba..d0d0471f27 100755 --- a/core/doctype/letter_head/letter_head.py +++ b/core/doctype/letter_head/letter_head.py @@ -25,8 +25,6 @@ import webnotes sql = webnotes.conn.sql -test_records = [] - class DocType: def __init__(self, doc, doclist=[]): self.doc = doc diff --git a/core/doctype/letter_head/test_letter_head.py b/core/doctype/letter_head/test_letter_head.py new file mode 100644 index 0000000000..3afb472bae --- /dev/null +++ b/core/doctype/letter_head/test_letter_head.py @@ -0,0 +1 @@ +test_records = [] \ No newline at end of file diff --git a/core/doctype/profile/profile.py b/core/doctype/profile/profile.py index e1a6788fe4..f4f02acd60 100644 --- a/core/doctype/profile/profile.py +++ b/core/doctype/profile/profile.py @@ -277,16 +277,4 @@ def get_perm_info(arg=None): def get_defaults(arg=None): return webnotes.conn.sql("""select defkey, defvalue from tabDefaultValue where parent=%s and parenttype = 'Profile'""", webnotes.form_dict['profile']) - -test_records = [[{ - "doctype":"Profile", - "email": "test@erpnext.com", - "first_name": "_Test", - "new_password": "testpassword" -}], -[{ - "doctype":"Profile", - "email": "test1@erpnext.com", - "first_name": "_Test1", - "new_password": "testpassword" -}]] \ No newline at end of file + \ No newline at end of file diff --git a/core/doctype/profile/test_profile.py b/core/doctype/profile/test_profile.py new file mode 100644 index 0000000000..a041818853 --- /dev/null +++ b/core/doctype/profile/test_profile.py @@ -0,0 +1,12 @@ +test_records = [[{ + "doctype":"Profile", + "email": "test@erpnext.com", + "first_name": "_Test", + "new_password": "testpassword" +}], +[{ + "doctype":"Profile", + "email": "test1@erpnext.com", + "first_name": "_Test1", + "new_password": "testpassword" +}]] \ No newline at end of file diff --git a/core/doctype/workflow_state/test_workflow_state.py b/core/doctype/workflow_state/test_workflow_state.py new file mode 100644 index 0000000000..3afb472bae --- /dev/null +++ b/core/doctype/workflow_state/test_workflow_state.py @@ -0,0 +1 @@ +test_records = [] \ No newline at end of file diff --git a/core/doctype/workflow_state/workflow_state.py b/core/doctype/workflow_state/workflow_state.py index 63a1d72dae..e669908514 100644 --- a/core/doctype/workflow_state/workflow_state.py +++ b/core/doctype/workflow_state/workflow_state.py @@ -22,8 +22,6 @@ from __future__ import unicode_literals import webnotes -test_records = [] - class DocType: def __init__(self, d, dl): self.doc, self.doclist = d, dl \ No newline at end of file diff --git a/webnotes/__init__.py b/webnotes/__init__.py index c5f25e282a..7223584a7d 100644 --- a/webnotes/__init__.py +++ b/webnotes/__init__.py @@ -72,7 +72,8 @@ debug_log = [] message_log = [] mute_emails = False test_objects = {} - +request_method = None +print_messages = False user_lang = False lang = 'en' @@ -114,20 +115,20 @@ def getTraceback(): return utils.getTraceback() def errprint(msg): - """ - Append to the :data:`debug log` - """ + if not request_method: + print repr(msg) + from utils import cstr debug_log.append(cstr(msg or '')) def msgprint(msg, small=0, raise_exception=0, as_table=False): - """ - Append to the :data:`message_log` - """ from utils import cstr if as_table and type(msg) in (list, tuple): msg = '' + ''.join([''+''.join(['' % c for c in r])+'' for r in msg]) + '
%s
' + if print_messages: + print "Message: " + repr(msg) + message_log.append((small and '__small:' or '')+cstr(msg or '')) if raise_exception: import inspect @@ -137,11 +138,7 @@ def msgprint(msg, small=0, raise_exception=0, as_table=False): raise ValidationError, msg def create_folder(path): - """ - Wrapper function for os.makedirs (does not throw exception if directory exists) - """ import os - try: os.makedirs(path) except OSError, e: @@ -149,11 +146,7 @@ def create_folder(path): raise e def create_symlink(source_path, link_path): - """ - Wrapper function for os.symlink (does not throw exception if directory exists) - """ import os - try: os.symlink(source_path, link_path) except OSError, e: @@ -161,11 +154,7 @@ def create_symlink(source_path, link_path): raise e def remove_file(path): - """ - Wrapper function for os.remove (does not throw exception if file/symlink does not exists) - """ import os - try: os.remove(path) except OSError, e: @@ -173,9 +162,6 @@ def remove_file(path): raise e def connect(db_name=None, password=None): - """ - Connect to this db (or db), if called from command prompt - """ import webnotes.db global conn conn = webnotes.db.Database(user=db_name, password=password) @@ -185,7 +171,7 @@ def connect(db_name=None, password=None): import webnotes.profile global user - user = webnotes.profile.Profile('Administrator') + user = webnotes.profile.Profile('Administrator') def get_env_vars(env_var): import os @@ -391,4 +377,5 @@ def get_application_home_page(user='Guest'): if hpl: return hpl[0][0] else: - return conn.get_value('Control Panel',None,'home_page') or 'Login Page' \ No newline at end of file + from startup import application_home_page + return application_home_page diff --git a/webnotes/db.py b/webnotes/db.py index 9c3ca5b1f6..b87e235e84 100644 --- a/webnotes/db.py +++ b/webnotes/db.py @@ -97,7 +97,11 @@ class Database: if values!=(): if isinstance(values, dict): values = dict(values) - if debug: webnotes.errprint(query % values) + if debug: + try: + webnotes.errprint(query % values) + except TypeError: + webnotes.errprint([query, values]) self._cursor.execute(query, values) else: @@ -126,11 +130,15 @@ class Database: else: return self._cursor.fetchall() - def sql_list(self, query, values=()): - return [r[0] for r in self.sql(query, values)] + def sql_list(self, query, values=(), debug=False): + return [r[0] for r in self.sql(query, values, debug=debug)] + + def sql_ddl(self, query, values=()): + self.commit() + self.sql(query) def check_transaction_status(self, query): - if self.in_transaction and query and query.strip().split()[0].lower() in ['start', 'alter', 'drop', 'create']: + if self.in_transaction and query and query.strip().split()[0].lower() in ['start', 'alter', 'drop', 'create', "begin"]: raise Exception, 'This statement can cause implicit commit' if query and query.strip().lower()=='start transaction': @@ -240,15 +248,23 @@ class Database: return " and ".join(conditions), filters + def get(self, doctype, filters=None, as_dict=False): + return self.get_value(doctype, filters, "*", as_dict=as_dict) + def get_value(self, doctype, filters=None, fieldname="name", ignore=None, as_dict=False): """Get a single / multiple value from a record. For Single DocType, let filters be = None""" + + if fieldname!="*" and isinstance(fieldname, basestring): + fieldname = "`" + fieldname + "`" + if filters is not None and (filters!=doctype or filters=='DocType'): - fl = isinstance(fieldname, basestring) and fieldname or "`, `".join(fieldname) + fl = isinstance(fieldname, basestring) and fieldname or \ + ("`" + "`, `".join(fieldname) + "`") conditions, filters = self.build_conditions(filters) try: - r = self.sql("select `%s` from `tab%s` where %s" % (fl, doctype, + r = self.sql("select %s from `tab%s` where %s" % (fl, doctype, conditions), filters, as_dict) except Exception, e: if e.args[0]==1054 and ignore: @@ -360,7 +376,8 @@ class Database: self.sql("start transaction") def commit(self): - self.sql("commit") + if self.in_transaction: + self.sql("commit") def rollback(self): self.sql("ROLLBACK") diff --git a/webnotes/model/code.py b/webnotes/model/code.py index ab528da9c2..ba083779b6 100644 --- a/webnotes/model/code.py +++ b/webnotes/model/code.py @@ -134,11 +134,16 @@ def get_doctype_class(doctype, module): return DocType +def get_module_name(doctype, module, prefix): + from webnotes.modules import scrub + _doctype, _module = scrub(doctype), scrub(module) + return '%s.doctype.%s.%s%s' % (_module, _doctype, prefix, _doctype) + def load_doctype_module(doctype, module, prefix=""): from webnotes.modules import scrub _doctype, _module = scrub(doctype), scrub(module) try: - module = __import__('%s.doctype.%s.%s%s' % (_module, _doctype, prefix, _doctype), fromlist=['']) + module = __import__(get_module_name(doctype, module, prefix), fromlist=['']) return module except ImportError, e: return None diff --git a/webnotes/model/meta.py b/webnotes/model/meta.py index 05c41a6143..064eb15d22 100644 --- a/webnotes/model/meta.py +++ b/webnotes/model/meta.py @@ -25,8 +25,6 @@ from __future__ import unicode_literals import webnotes -#================================================================================= - def get_dt_values(doctype, fields, as_dict = 0): return webnotes.conn.sql('SELECT %s FROM tabDocType WHERE name="%s"' % (fields, doctype), as_dict = as_dict) @@ -39,21 +37,15 @@ def is_single(doctype): except IndexError, e: raise Exception, 'Cannot determine whether %s is single' % doctype -#================================================================================= - def get_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) return parent_dt and parent_dt[0][0] or '' -#================================================================================= - def set_fieldname(field_id, fieldname): webnotes.conn.set_value('DocField', field_id, 'fieldname', fieldname) -#================================================================================= - def get_link_fields(doctype): """ Returns list of link fields for a doctype in tuple (fieldname, options, label) @@ -71,8 +63,6 @@ def get_link_fields(doctype): ) ] -#================================================================================= - def get_table_fields(doctype): child_tables = [[d[0], d[1]] for d in webnotes.conn.sql("select options, fieldname from tabDocField \ where parent='%s' and fieldtype='Table'" % doctype, as_list=1)] @@ -81,3 +71,8 @@ def get_table_fields(doctype): where dt='%s' and fieldtype='Table'" % doctype, as_list=1)] return child_tables + custom_child_tables + +def has_field(doctype, fieldname): + doclist = webnotes.model.doctype.get(doctype) + return doclist.get({"parent":doctype, "doctype":"DocField", "fieldname":fieldname}) + \ No newline at end of file diff --git a/webnotes/model/wrapper.py b/webnotes/model/wrapper.py index b479f2402c..a704e71a0c 100644 --- a/webnotes/model/wrapper.py +++ b/webnotes/model/wrapper.py @@ -106,6 +106,7 @@ class ModelWrapper: from webnotes.model.code import get_obj self.obj = get_obj(doc=self.doc, doclist=self.doclist) + self.controller = self.obj return self.obj def to_dict(self): @@ -195,6 +196,10 @@ class ModelWrapper: self.set_doclist(self.obj.doclist) + def get_method(self, method): + self.make_obj() + return getattr(self.obj, method, None) + def save_main(self): try: self.doc.save(cint(self.doc.fields.get('__islocal'))) diff --git a/webnotes/test_runner.py b/webnotes/test_runner.py index 575475b311..7032c76eed 100644 --- a/webnotes/test_runner.py +++ b/webnotes/test_runner.py @@ -5,65 +5,78 @@ if __name__=="__main__": sys.path.extend([".", "app", "lib"]) import webnotes -from webnotes.model.meta import get_link_fields -from webnotes.model.code import load_doctype_module +import unittest + +from webnotes.model.meta import get_link_fields, has_field +from webnotes.model.code import load_doctype_module, get_module_name + def make_test_records(doctype, verbose=0): webnotes.mute_emails = True if not webnotes.conn: webnotes.connect() - - # also include doctype itself - options_list = list(set([options for fieldname, options, label - in get_link_fields(doctype)] + [doctype])) - - for options in options_list: + + for options in get_dependencies(doctype): + if options.startswith("link:"): options = options[5:] if options == "[Select]": continue - + if options not in webnotes.test_objects: webnotes.test_objects[options] = [] make_test_records(options, verbose) - - load_module_and_make_records(options, verbose) - -def load_module_and_make_records(options, verbose=0): - module = webnotes.conn.get_value("DocType", options, "module") + make_test_records_for_doctype(options, verbose) - # get methods for either [doctype].py or test_[doctype].py - doctype_module = load_doctype_module(options, module) - test_module = load_doctype_module(options, module, "test_") +def get_modules(doctype): + module = webnotes.conn.get_value("DocType", doctype, "module") + test_module = load_doctype_module(doctype, module, "test_") + + return module, test_module + +def get_dependencies(doctype): + module, test_module = get_modules(doctype) + + options_list = list(set([options for fieldname, options, label + in get_link_fields(doctype)] + [doctype])) + + if hasattr(test_module, "test_dependencies"): + options_list += test_module.test_dependencies + + if hasattr(test_module, "test_ignore"): + for doctype_name in test_module.test_ignore: + if doctype_name in options_list: + options_list.remove(doctype_name) + + return options_list + +def make_test_records_for_doctype(doctype, verbose=0): + module, test_module = get_modules(doctype) if hasattr(test_module, "make_test_records"): - webnotes.test_objects[options] += test_module.make_test_records(verbose) - - elif hasattr(doctype_module, "make_test_records"): - webnotes.test_objects[options] += doctype_module.make_test_records(verbose) + webnotes.test_objects[doctype] += test_module.make_test_records(verbose) elif hasattr(test_module, "test_records"): - webnotes.test_objects[options] += make_test_objects(test_module) - - elif hasattr(doctype_module, "test_records"): - webnotes.test_objects[options] += make_test_objects(doctype_module) + webnotes.test_objects[doctype] += make_test_objects(doctype, test_module.test_records) elif verbose: - print_mandatory_fields(options) + print_mandatory_fields(doctype) -def make_test_objects(obj): - if isinstance(obj, list): - test_records = obj - else: - # obj is a module object - test_records = obj.test_records - + +def make_test_objects(doctype, test_records): records = [] + for doclist in test_records: + if not "doctype" in doclist[0]: + doclist[0]["doctype"] = doctype d = webnotes.model_wrapper((webnotes.doclist(doclist)).copy()) if webnotes.test_objects.get(d.doc.doctype): # do not create test records, if already exists return [] + if has_field(d.doc.doctype, "naming_series"): + if not d.doc.naming_series: + d.doc.naming_series = "_T-" + d.doc.doctype + "-" + d.insert() records.append(d.doc.name) return records @@ -78,6 +91,52 @@ def print_mandatory_fields(doctype): print d.parent + ":" + d.fieldname + " | " + d.fieldtype + " | " + (d.options or "") print -if __name__=="__main__": - make_test_records(sys.argv[1], verbose=1) +def run_unittest(doctype): + module = webnotes.conn.get_value("DocType", doctype, "module") + test_module = get_module_name(doctype, module, "test_") + make_test_records(args.doctype[0], verbose=True) + + try: + exec ('from %s import *' % test_module) in globals() + del sys.argv[1:] + unittest.main() + + except ImportError, e: + print "No test module." + +def run_all_tests(verbose): + import os, imp + from webnotes.modules.utils import peval_doclist + + for path, folders, files in os.walk("."): + for filename in files: + if filename.startswith("test_") and filename.endswith(".py"): + test_suite = unittest.TestSuite() + if os.path.basename(os.path.dirname(path))=="doctype": + txt_file = os.path.join(path, filename[5:].replace(".py", ".txt")) + with open(txt_file, 'r') as f: + doctype_doclist = peval_doclist(f.read()) + doctype = doctype_doclist[0]["name"] + make_test_records(doctype, verbose) + + module = imp.load_source('test', os.path.join(path, filename)) + test_suite.addTest(unittest.TestLoader().loadTestsFromModule(module)) + unittest.TextTestRunner(verbosity=2).run(test_suite) + +if __name__=="__main__": + import argparse + + parser = argparse.ArgumentParser(description='Run tests.') + parser.add_argument('-d', '--doctype', nargs=1, metavar = "DOCTYPE", + help="test for doctype") + parser.add_argument('-v', '--verbose', default=False, action="store_true") + + args = parser.parse_args() + webnotes.print_messages = args.verbose + + if args.doctype: + run_unittest(args.doctype[0]) + else: + run_all_tests(args.verbose) + diff --git a/webnotes/utils/email_lib/bulk.py b/webnotes/utils/email_lib/bulk.py index 0f58f7ddaa..a9db2c4410 100644 --- a/webnotes/utils/email_lib/bulk.py +++ b/webnotes/utils/email_lib/bulk.py @@ -30,13 +30,10 @@ def send(recipients=None, sender=None, doctype='Profile', email_field='email', subject='[No Subject]', message='[No Content]'): """send bulk mail if not unsubscribed and within conf.bulk_mail_limit""" import webnotes - - if webnotes.mute_emails: - return - + def is_unsubscribed(rdata): if not rdata: return 1 - return rdata[0]['unsubscribed'] + return rdata.unsubscribed def check_bulk_limit(new_mails): import conf, startup @@ -53,20 +50,22 @@ def send(recipients=None, sender=None, doctype='Profile', email_field='email', webnotes.msgprint("""Monthly Bulk Mail Limit (%s) Crossed""" % monthly_bulk_mail_limit, raise_exception=BulkLimitCrossedError) - def add_unsubscribe_link(email): + def update_message(doc): from webnotes.utils import get_request_site_address import urllib - return message + """
Unsubscribe from this list.
""" % (get_request_site_address(), urllib.urlencode({ "cmd": "webnotes.utils.email_lib.bulk.unsubscribe", - "email": email, + "email": doc.get(email_field), "type": doctype, "email_field": email_field })) - + + return updated + if not recipients: recipients = [] if not sender or sender == "Administrator": sender = webnotes.conn.get_value('Email Settings', None, 'auto_mail_id') @@ -85,9 +84,11 @@ def send(recipients=None, sender=None, doctype='Profile', email_field='email', rdata = webnotes.conn.sql("""select * from `tab%s` where %s=%s""" % (doctype, email_field, '%s'), r, as_dict=1) - if not is_unsubscribed(rdata): + doc = rdata and rdata[0] or {} + + if not is_unsubscribed(doc): # add to queue - add(r, sender, subject, add_unsubscribe_link(r), text_content) + add(r, sender, subject, update_message(doc), text_content) def add(email, sender, subject, message, text_content = None): """add to bulk mail queue""" From ebdfb6f2d44daad552ef1371980b08f42a13d809 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 13 Feb 2013 09:42:30 +0530 Subject: [PATCH 2/3] fixed communication subject --- public/js/wn/views/communication.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/public/js/wn/views/communication.js b/public/js/wn/views/communication.js index e11dc9fd3c..0227747a09 100644 --- a/public/js/wn/views/communication.js +++ b/public/js/wn/views/communication.js @@ -55,9 +55,14 @@ wn.views.CommunicationList = Class.extend({ }, add_reply: function() { + var subject = this.doc.subject; + if(!subject && this.list.length) { + // get subject from previous message + subject = this.list[0].subject; + } new wn.views.CommunicationComposer({ doc: this.doc, - subject: this.doc.subject, + subject: subject, recipients: this.recipients }) }, From 39adba6d8ea399bc2cbf31663eb439931b1b3f25 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 13 Feb 2013 09:44:27 +0530 Subject: [PATCH 3/3] fixed communication subject --- public/js/wn/views/communication.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/js/wn/views/communication.js b/public/js/wn/views/communication.js index 0227747a09..3484fcdaba 100644 --- a/public/js/wn/views/communication.js +++ b/public/js/wn/views/communication.js @@ -59,6 +59,9 @@ wn.views.CommunicationList = Class.extend({ if(!subject && this.list.length) { // get subject from previous message subject = this.list[0].subject; + if(strip(subject.toLowerCase().split(":")[0])!="re") { + subject = "Re: " + subject; + } } new wn.views.CommunicationComposer({ doc: this.doc,