diff --git a/frappe/__init__.py b/frappe/__init__.py index 12f1157196..11378a1fa5 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -226,19 +226,23 @@ def get_request_header(key, default=None): return request.headers.get(key, default) def sendmail(recipients=(), sender="", subject="No Subject", message="No Message", - as_markdown=False, bulk=False): + as_markdown=False, bulk=False, ref_doctype=None, ref_docname=None, + add_unsubscribe_link=False): if bulk: import frappe.utils.email_lib.bulk frappe.utils.email_lib.bulk.send(recipients=recipients, sender=sender, - subject=subject, message=message, add_unsubscribe_link=False) + subject=subject, message=message, ref_doctype = ref_doctype, + ref_docname = ref_docname, add_unsubscribe_link=add_unsubscribe_link) else: import frappe.utils.email_lib if as_markdown: - frappe.utils.email_lib.sendmail_md(recipients, sender=sender, subject=subject, msg=message) + frappe.utils.email_lib.sendmail_md(recipients, sender=sender, + subject=subject, msg=message) else: - frappe.utils.email_lib.sendmail(recipients, sender=sender, subject=subject, msg=message) + frappe.utils.email_lib.sendmail(recipients, sender=sender, + subject=subject, msg=message) logger = None whitelisted = [] diff --git a/frappe/cli.py b/frappe/cli.py index 0116bed406..f874d31f96 100755 --- a/frappe/cli.py +++ b/frappe/cli.py @@ -772,7 +772,7 @@ def smtp_debug_server(): os.execv(python, [python, '-m', "smtpd", "-n", "-c", "DebuggingServer", "localhost:25"]) @cmd -def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driver=None): +def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driver=None, force=False): import frappe.test_runner from frappe.utils import sel @@ -781,7 +781,7 @@ def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driv ret = 1 try: ret = frappe.test_runner.main(app and app[0], module and module[0], doctype and doctype[0], verbose, - tests=tests) + tests=tests, force=force) if len(ret.failures) == 0 and len(ret.errors) == 0: ret = 0 finally: diff --git a/frappe/config/setup.py b/frappe/config/setup.py index ce0d4e08ab..e00aed270c 100644 --- a/frappe/config/setup.py +++ b/frappe/config/setup.py @@ -121,6 +121,11 @@ def get_data(): "name": "Outgoing Email Settings", "description": _("Set outgoing mail server.") }, + { + "type": "doctype", + "name": "Email Alert", + "description": _("Setup Email Alert based on various criteria.") + }, { "type": "doctype", "name": "Standard Reply", diff --git a/frappe/core/doctype/comment/comment.json b/frappe/core/doctype/comment/comment.json index 9dcff41adb..66a2736f9e 100644 --- a/frappe/core/doctype/comment/comment.json +++ b/frappe/core/doctype/comment/comment.json @@ -1,22 +1,25 @@ { "autoname": "CWR/.#####", - "creation": "2012-08-08 10:40:11.000000", + "creation": "2012-08-08 10:40:11", "docstatus": 0, "doctype": "DocType", "fields": [ { "fieldname": "comment", "fieldtype": "Text", + "in_list_view": 1, "label": "Comment", "no_copy": 0, "oldfieldname": "comment", "oldfieldtype": "Text", "permlevel": 0, + "reqd": 1, "search_index": 0 }, { "fieldname": "comment_by", "fieldtype": "Data", + "in_list_view": 1, "label": "Comment By", "no_copy": 0, "oldfieldname": "comment_by", @@ -27,6 +30,7 @@ { "fieldname": "comment_by_fullname", "fieldtype": "Data", + "in_list_view": 1, "label": "Comment By Fullname", "no_copy": 0, "oldfieldname": "comment_by_fullname", @@ -37,6 +41,7 @@ { "fieldname": "comment_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Comment Date", "no_copy": 0, "oldfieldname": "comment_date", @@ -47,6 +52,7 @@ { "fieldname": "comment_time", "fieldtype": "Data", + "in_list_view": 1, "label": "Comment Time", "no_copy": 0, "oldfieldname": "comment_time", @@ -94,7 +100,7 @@ "icon": "icon-comments", "idx": 1, "issingle": 0, - "modified": "2014-01-24 13:00:20.000000", + "modified": "2014-07-14 12:14:08.315217", "modified_by": "Administrator", "module": "Core", "name": "Comment", diff --git a/frappe/core/doctype/comment/test_records.json b/frappe/core/doctype/comment/test_records.json new file mode 100644 index 0000000000..29b35716bc --- /dev/null +++ b/frappe/core/doctype/comment/test_records.json @@ -0,0 +1,6 @@ +[ + { + "doctype": "Comment", + "name":"_Test Comment 1", + } +] diff --git a/frappe/core/doctype/doctype/doctype_template.py b/frappe/core/doctype/doctype/boilerplate/controller.py similarity index 74% rename from frappe/core/doctype/doctype/doctype_template.py rename to frappe/core/doctype/doctype/boilerplate/controller.py index 369bd592d8..1d5ae0895d 100644 --- a/frappe/core/doctype/doctype/doctype_template.py +++ b/frappe/core/doctype/doctype/boilerplate/controller.py @@ -1,4 +1,4 @@ -# Copyright (c) 2013, {app_publisher} +# Copyright (c) 2013, {app_publisher} and contributors # For license information, please see license.txt from __future__ import unicode_literals @@ -6,4 +6,4 @@ import frappe from frappe.model.document import Document class {classname}(Document): - pass \ No newline at end of file + pass diff --git a/frappe/core/doctype/doctype/boilerplate/test_controller.py b/frappe/core/doctype/doctype/boilerplate/test_controller.py new file mode 100644 index 0000000000..cc12c9b9c8 --- /dev/null +++ b/frappe/core/doctype/doctype/boilerplate/test_controller.py @@ -0,0 +1,10 @@ +# Copyright (c) 2013, {app_publisher} and Contributors +# See license.txt + +import frappe +import unittest + +test_records = frappe.get_test_records('{doctype}') + +class Test{classname}(unittest.TestCase): + pass diff --git a/frappe/core/doctype/doctype/boilerplate/test_records.json b/frappe/core/doctype/doctype/boilerplate/test_records.json new file mode 100644 index 0000000000..9c261424c3 --- /dev/null +++ b/frappe/core/doctype/doctype/boilerplate/test_records.json @@ -0,0 +1,6 @@ +[ + {{ + "doctype": "{doctype}", + "name":"_Test {doctype} 1", + }} +] diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 12333f289a..269bade04c 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -145,20 +145,24 @@ class DocType(Document): def make_controller_template(self): from frappe.modules import get_doc_path, get_module_path, scrub - pypath = os.path.join(get_doc_path(self.module, - self.doctype, self.name), scrub(self.name) + '.py') - - if not os.path.exists(pypath): - # get app publisher for copyright - app = frappe.local.module_app[frappe.scrub(self.module)] - if not app: - frappe.throw(_("App not found")) - app_publisher = frappe.get_hooks(hook="app_publisher", app_name=app)[0] - - with open(pypath, 'w') as pyfile: - with open(os.path.join(get_module_path("core"), "doctype", "doctype", - "doctype_template.py"), 'r') as srcfile: - pyfile.write(srcfile.read().format(app_publisher=app_publisher, classname=self.name.replace(" ", ""))) + target_path = get_doc_path(self.module, self.doctype, self.name) + + app = frappe.local.module_app[scrub(self.module)] + if not app: + frappe.throw(_("App not found")) + app_publisher = frappe.get_hooks(hook="app_publisher", app_name=app)[0] + + for template in ["controller.py", "test_controller.py", "test_records.json"]: + template_name = template.replace("controller", scrub(self.name)) + target_file_path = os.path.join(target_path, template_name) + if not os.path.exists(target_file_path): + + with open(target_file_path, 'w') as target: + with open(os.path.join(get_module_path("core"), "doctype", "doctype", + "boilerplate", template), 'r') as source: + target.write(source.read().format(app_publisher=app_publisher, + classname=self.name.replace(" ", ""), doctype=self.name)) + def make_amendable(self): """ diff --git a/frappe/core/doctype/email_alert/email_alert.js b/frappe/core/doctype/email_alert/email_alert.js index a7352296ca..b7fb717063 100644 --- a/frappe/core/doctype/email_alert/email_alert.js +++ b/frappe/core/doctype/email_alert/email_alert.js @@ -3,14 +3,23 @@ frappe.email_alert = { // get the doctype to update fields frappe.model.with_doctype(frm.doc.document_type, function() { - frm._doctype_fields = $.map(frappe.get_doc("DocType", frm.doc.document_type).fields, + var fields = frappe.get_doc("DocType", frm.doc.document_type).fields; + + var options = $.map(fields, function(d) { return in_list(frappe.model.no_value_type, d.fieldtype) ? null : d.fieldname; }); - var options = "\n" + frm._doctype_fields.join("\n"); + options = "\n" + options.join("\n"); + // set value changed options frm.set_df_property("value_changed", "options", options); + // set date changed options + frm.set_df_property("date_changed", "options", $.map(fields, + function(d) { return (d.fieldtype=="Date" || d.fieldtype=="Datetime") ? + d.fieldname : null; })); + + // set email recipient options frappe.meta.get_docfield("Email Alert Recipient", "email_by_document_field", frm.doc.name).options = options; @@ -19,9 +28,9 @@ frappe.email_alert = { } frappe.ui.form.on("Email Alert", "refresh", function(frm) { - frappe.email_alert.setup_fieldname_select(frm) + frappe.email_alert.setup_fieldname_select(frm); }); frappe.ui.form.on("Email Alert", "document_type", function(frm) { - frappe.email_alert.setup_fieldname_select(frm) + frappe.email_alert.setup_fieldname_select(frm); }); diff --git a/frappe/core/doctype/email_alert/email_alert.json b/frappe/core/doctype/email_alert/email_alert.json index ee32ead908..5e29174184 100644 --- a/frappe/core/doctype/email_alert/email_alert.json +++ b/frappe/core/doctype/email_alert/email_alert.json @@ -26,7 +26,8 @@ "label": "Document Type", "options": "DocType", "permlevel": 0, - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "fieldname": "event", @@ -35,16 +36,27 @@ "label": "Send Alert On", "options": "\nNew\nSave\nSubmit\nCancel\nDate Change\nValue Change", "permlevel": 0, - "reqd": 1 + "reqd": 1, + "search_index": 1 + }, + { + "depends_on": "eval:doc.event==\"Date Change\"", + "description": "Send alert if date matches this field's value", + "fieldname": "date_changed", + "fieldtype": "Select", + "label": "Date Changed", + "permlevel": 0 }, { "depends_on": "eval:doc.event==\"Value Change\"", + "description": "Send alert if this field's value changes", "fieldname": "value_changed", "fieldtype": "Select", "label": "Value Changed", "permlevel": 0 }, { + "depends_on": "", "description": "Optional: The alert will be sent if this expression is true", "fieldname": "condition", "fieldtype": "Data", @@ -99,7 +111,7 @@ } ], "icon": "icon-envelope", - "modified": "2014-07-11 17:57:11.471260", + "modified": "2014-07-14 12:55:10.467991", "modified_by": "Administrator", "module": "Core", "name": "Email Alert", diff --git a/frappe/core/doctype/email_alert/email_alert.py b/frappe/core/doctype/email_alert/email_alert.py index 9d397369ed..3e28dd9a32 100644 --- a/frappe/core/doctype/email_alert/email_alert.py +++ b/frappe/core/doctype/email_alert/email_alert.py @@ -3,7 +3,88 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document +from frappe.utils import validate_email_add class EmailAlert(Document): - pass \ No newline at end of file + def validate(self): + if self.event=="Date Changed" and not self.date_changed: + frappe.throw(_("Please specify which date field must be checked")) + + if self.event=="Value Changed" and not self.value_changed: + frappe.throw(_("Please specify which value field must be checked")) + +def trigger_daily_alerts(): + trigger_email_alerts(None, "Date Change") + +def trigger_email_alerts(doc, method=None): + if method=="Date Change": + for alert in frappe.db.sql_list("""select name from `tabEmail Alert` + where event='Date Change'"""): + + alert = frappe.get_doc("Email Alert", alert) + for name in frappe.db.sql_list("""select name from `tab%s` where + DATE(%s) = CURDATE()""" % (alert.document_type, alert.date_changed)): + + evaluate_alert(frappe.get_doc(alert.document_type, name), + alert, "Date Change") + else: + if method in ("on_update", "validate") and doc.get("__in_insert"): + # don't call email alerts multiple times for inserts + # on insert only "New" type alert must be called + return + + eevent = { + "on_update": "Save", + "after_insert": "New", + "validate": "Value Change", + "on_submit": "Submit", + "on_cancel": "Cancel", + }[method] + + for alert in frappe.db.sql_list("""select name from `tabEmail Alert` + where document_type=%s and event=%s""", (doc.doctype, eevent)): + evaluate_alert(doc, alert, eevent) + +def evaluate_alert(doc, alert, event): + if isinstance(alert, basestring): + alert = frappe.get_doc("Email Alert", alert) + if alert.condition: + if not eval(alert.condition): + return + + if event=="Value Change" and not doc.is_new(): + if doc.get(alert.value_changed) == frappe.db.get_value(doc.doctype, + doc.name, alert.value_changed): + return # value not changed + + for recipient in alert.email_alert_recipients: + recipients = [] + if recipient.condition: + if not eval(recipient.condition): + continue + if recipient.email_by_document_field: + if validate_email_add(doc.get(recipient.email_by_document_field)): + recipients.append(doc.get(recipient.email_by_document_field)) + # else: + # print "invalid email" + if recipient.cc: + recipient.cc = recipient.cc.replace(",", "\n") + recipients = recipients + recipient.cc.split("\n") + + if not recipients: + return + + template = alert.message + footer + # send alert + frappe.sendmail(recipients=recipients, subject=alert.subject, + message= frappe.render_template(template, {"doc": doc, "alert":alert}), + bulk=True, ref_doctype = doc.doctype, ref_docname = doc.name) + + +footer = """