@@ -226,19 +226,23 @@ def get_request_header(key, default=None): | |||||
return request.headers.get(key, default) | return request.headers.get(key, default) | ||||
def sendmail(recipients=(), sender="", subject="No Subject", message="No Message", | 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: | if bulk: | ||||
import frappe.utils.email_lib.bulk | import frappe.utils.email_lib.bulk | ||||
frappe.utils.email_lib.bulk.send(recipients=recipients, sender=sender, | 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: | else: | ||||
import frappe.utils.email_lib | import frappe.utils.email_lib | ||||
if as_markdown: | 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: | 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 | logger = None | ||||
whitelisted = [] | whitelisted = [] | ||||
@@ -772,7 +772,7 @@ def smtp_debug_server(): | |||||
os.execv(python, [python, '-m', "smtpd", "-n", "-c", "DebuggingServer", "localhost:25"]) | os.execv(python, [python, '-m', "smtpd", "-n", "-c", "DebuggingServer", "localhost:25"]) | ||||
@cmd | @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 | import frappe.test_runner | ||||
from frappe.utils import sel | from frappe.utils import sel | ||||
@@ -781,7 +781,7 @@ def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driv | |||||
ret = 1 | ret = 1 | ||||
try: | try: | ||||
ret = frappe.test_runner.main(app and app[0], module and module[0], doctype and doctype[0], verbose, | 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: | if len(ret.failures) == 0 and len(ret.errors) == 0: | ||||
ret = 0 | ret = 0 | ||||
finally: | finally: | ||||
@@ -121,6 +121,11 @@ def get_data(): | |||||
"name": "Outgoing Email Settings", | "name": "Outgoing Email Settings", | ||||
"description": _("Set outgoing mail server.") | "description": _("Set outgoing mail server.") | ||||
}, | }, | ||||
{ | |||||
"type": "doctype", | |||||
"name": "Email Alert", | |||||
"description": _("Setup Email Alert based on various criteria.") | |||||
}, | |||||
{ | { | ||||
"type": "doctype", | "type": "doctype", | ||||
"name": "Standard Reply", | "name": "Standard Reply", | ||||
@@ -1,22 +1,25 @@ | |||||
{ | { | ||||
"autoname": "CWR/.#####", | "autoname": "CWR/.#####", | ||||
"creation": "2012-08-08 10:40:11.000000", | |||||
"creation": "2012-08-08 10:40:11", | |||||
"docstatus": 0, | "docstatus": 0, | ||||
"doctype": "DocType", | "doctype": "DocType", | ||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "comment", | "fieldname": "comment", | ||||
"fieldtype": "Text", | "fieldtype": "Text", | ||||
"in_list_view": 1, | |||||
"label": "Comment", | "label": "Comment", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "comment", | "oldfieldname": "comment", | ||||
"oldfieldtype": "Text", | "oldfieldtype": "Text", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"reqd": 1, | |||||
"search_index": 0 | "search_index": 0 | ||||
}, | }, | ||||
{ | { | ||||
"fieldname": "comment_by", | "fieldname": "comment_by", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Comment By", | "label": "Comment By", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "comment_by", | "oldfieldname": "comment_by", | ||||
@@ -27,6 +30,7 @@ | |||||
{ | { | ||||
"fieldname": "comment_by_fullname", | "fieldname": "comment_by_fullname", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Comment By Fullname", | "label": "Comment By Fullname", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "comment_by_fullname", | "oldfieldname": "comment_by_fullname", | ||||
@@ -37,6 +41,7 @@ | |||||
{ | { | ||||
"fieldname": "comment_date", | "fieldname": "comment_date", | ||||
"fieldtype": "Date", | "fieldtype": "Date", | ||||
"in_list_view": 1, | |||||
"label": "Comment Date", | "label": "Comment Date", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "comment_date", | "oldfieldname": "comment_date", | ||||
@@ -47,6 +52,7 @@ | |||||
{ | { | ||||
"fieldname": "comment_time", | "fieldname": "comment_time", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_list_view": 1, | |||||
"label": "Comment Time", | "label": "Comment Time", | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"oldfieldname": "comment_time", | "oldfieldname": "comment_time", | ||||
@@ -94,7 +100,7 @@ | |||||
"icon": "icon-comments", | "icon": "icon-comments", | ||||
"idx": 1, | "idx": 1, | ||||
"issingle": 0, | "issingle": 0, | ||||
"modified": "2014-01-24 13:00:20.000000", | |||||
"modified": "2014-07-14 12:14:08.315217", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Comment", | "name": "Comment", | ||||
@@ -0,0 +1,6 @@ | |||||
[ | |||||
{ | |||||
"doctype": "Comment", | |||||
"name":"_Test Comment 1", | |||||
} | |||||
] |
@@ -1,4 +1,4 @@ | |||||
# Copyright (c) 2013, {app_publisher} | |||||
# Copyright (c) 2013, {app_publisher} and contributors | |||||
# For license information, please see license.txt | # For license information, please see license.txt | ||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
@@ -6,4 +6,4 @@ import frappe | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
class {classname}(Document): | class {classname}(Document): | ||||
pass | |||||
pass |
@@ -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 |
@@ -0,0 +1,6 @@ | |||||
[ | |||||
{{ | |||||
"doctype": "{doctype}", | |||||
"name":"_Test {doctype} 1", | |||||
}} | |||||
] |
@@ -145,20 +145,24 @@ class DocType(Document): | |||||
def make_controller_template(self): | def make_controller_template(self): | ||||
from frappe.modules import get_doc_path, get_module_path, scrub | 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): | def make_amendable(self): | ||||
""" | """ | ||||
@@ -3,14 +3,23 @@ frappe.email_alert = { | |||||
// get the doctype to update fields | // get the doctype to update fields | ||||
frappe.model.with_doctype(frm.doc.document_type, function() { | 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) ? | function(d) { return in_list(frappe.model.no_value_type, d.fieldtype) ? | ||||
null : d.fieldname; }); | 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); | 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", | frappe.meta.get_docfield("Email Alert Recipient", "email_by_document_field", | ||||
frm.doc.name).options = options; | frm.doc.name).options = options; | ||||
@@ -19,9 +28,9 @@ frappe.email_alert = { | |||||
} | } | ||||
frappe.ui.form.on("Email Alert", "refresh", function(frm) { | 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.ui.form.on("Email Alert", "document_type", function(frm) { | ||||
frappe.email_alert.setup_fieldname_select(frm) | |||||
frappe.email_alert.setup_fieldname_select(frm); | |||||
}); | }); |
@@ -26,7 +26,8 @@ | |||||
"label": "Document Type", | "label": "Document Type", | ||||
"options": "DocType", | "options": "DocType", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"reqd": 1 | |||||
"reqd": 1, | |||||
"search_index": 1 | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "event", | "fieldname": "event", | ||||
@@ -35,16 +36,27 @@ | |||||
"label": "Send Alert On", | "label": "Send Alert On", | ||||
"options": "\nNew\nSave\nSubmit\nCancel\nDate Change\nValue Change", | "options": "\nNew\nSave\nSubmit\nCancel\nDate Change\nValue Change", | ||||
"permlevel": 0, | "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\"", | "depends_on": "eval:doc.event==\"Value Change\"", | ||||
"description": "Send alert if this field's value changes", | |||||
"fieldname": "value_changed", | "fieldname": "value_changed", | ||||
"fieldtype": "Select", | "fieldtype": "Select", | ||||
"label": "Value Changed", | "label": "Value Changed", | ||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
"depends_on": "", | |||||
"description": "Optional: The alert will be sent if this expression is true", | "description": "Optional: The alert will be sent if this expression is true", | ||||
"fieldname": "condition", | "fieldname": "condition", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
@@ -99,7 +111,7 @@ | |||||
} | } | ||||
], | ], | ||||
"icon": "icon-envelope", | "icon": "icon-envelope", | ||||
"modified": "2014-07-11 17:57:11.471260", | |||||
"modified": "2014-07-14 12:55:10.467991", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Email Alert", | "name": "Email Alert", | ||||
@@ -3,7 +3,88 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
from frappe import _ | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
from frappe.utils import validate_email_add | |||||
class EmailAlert(Document): | class EmailAlert(Document): | ||||
pass | |||||
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 = """<div style='margin-top: 20px; font-size: 80%; color: #888'> | |||||
This Email Alert {{alert.name}} was autogenerated for | |||||
{{ doc.doctype }} <a href="/desk#Form/{{doc.doctype}}/{{doc.name}}">{{doc.name}}</a>. | |||||
To update, modify it, go to Setup > Email > <a href="/desk#List/Email Alert">Email Alert</a> | |||||
""" |
@@ -0,0 +1,97 @@ | |||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||||
# See license.txt | |||||
import frappe, frappe.utils, frappe.utils.scheduler | |||||
import unittest | |||||
test_records = frappe.get_test_records('Email Alert') | |||||
class TestEmailAlert(unittest.TestCase): | |||||
def setUp(self): | |||||
frappe.db.sql("""delete from `tabBulk Email`""") | |||||
def test_new_and_save(self): | |||||
frappe.set_user("test1@example.com") | |||||
comment = frappe.new_doc("Comment") | |||||
comment.comment = "test" | |||||
comment.insert(ignore_permissions=True) | |||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Comment", | |||||
"ref_docname": comment.name, "status":"Not Sent"})) | |||||
frappe.db.sql("""delete from `tabBulk Email`""") | |||||
comment.description = "test" | |||||
comment.save() | |||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Comment", | |||||
"ref_docname": comment.name, "status":"Not Sent"})) | |||||
def test_condition(self): | |||||
frappe.set_user("test1@example.com") | |||||
event = frappe.new_doc("Event") | |||||
event.subject = "test", | |||||
event.event_type = "Private" | |||||
event.starts_on = "2014-06-06 12:00:00" | |||||
event.insert() | |||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
event.event_type = "Public" | |||||
event.save() | |||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
def test_value_changed(self): | |||||
frappe.set_user("test1@example.com") | |||||
event = frappe.new_doc("Event") | |||||
event.subject = "test", | |||||
event.event_type = "Private" | |||||
event.starts_on = "2014-06-06 12:00:00" | |||||
event.insert() | |||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
event.subject = "test 1" | |||||
event.save() | |||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
event.description = "test" | |||||
event.save() | |||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
def test_date_changed(self): | |||||
frappe.set_user("test1@example.com") | |||||
event = frappe.new_doc("Event") | |||||
event.subject = "test", | |||||
event.event_type = "Private" | |||||
event.starts_on = "2014-01-01 12:00:00" | |||||
event.insert() | |||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True) | |||||
# not today, so no alert | |||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
event.starts_on = frappe.utils.now() | |||||
event.save() | |||||
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) | |||||
frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True) | |||||
# today so show alert | |||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event", | |||||
"ref_docname": event.name, "status":"Not Sent"})) |
@@ -0,0 +1,55 @@ | |||||
[ | |||||
{ | |||||
"doctype": "Email Alert", | |||||
"subject":"_Test Email Alert 1", | |||||
"document_type": "Comment", | |||||
"event": "New", | |||||
"message": "New comment {{ doc.comment }} created", | |||||
"email_alert_recipients": [ | |||||
{ "email_by_document_field": "owner" } | |||||
] | |||||
}, | |||||
{ | |||||
"doctype": "Email Alert", | |||||
"subject":"_Test Email Alert 2", | |||||
"document_type": "Comment", | |||||
"event": "Save", | |||||
"message": "New comment {{ doc.comment }} saved", | |||||
"email_alert_recipients": [ | |||||
{ "email_by_document_field": "owner" } | |||||
] | |||||
}, | |||||
{ | |||||
"doctype": "Email Alert", | |||||
"subject":"_Test Email Alert 3", | |||||
"document_type": "Event", | |||||
"event": "Save", | |||||
"condition": "doc.event_type=='Public'", | |||||
"message": "A new public event {{ doc.subject }} on {{ doc.starts_on }} is created", | |||||
"email_alert_recipients": [ | |||||
{ "email_by_document_field": "owner" } | |||||
] | |||||
}, | |||||
{ | |||||
"doctype": "Email Alert", | |||||
"subject":"_Test Email Alert 4", | |||||
"document_type": "Event", | |||||
"event": "Value Change", | |||||
"value_changed": "description", | |||||
"message": "Description changed", | |||||
"email_alert_recipients": [ | |||||
{ "email_by_document_field": "owner" } | |||||
] | |||||
}, | |||||
{ | |||||
"doctype": "Email Alert", | |||||
"subject":"_Test Email Alert 5", | |||||
"document_type": "Event", | |||||
"event": "Date Change", | |||||
"date_changed": "starts_on", | |||||
"message": "Description changed", | |||||
"email_alert_recipients": [ | |||||
{ "email_by_document_field": "owner" } | |||||
] | |||||
} | |||||
] |
@@ -2,7 +2,7 @@ from frappe.__version__ import __version__ | |||||
app_name = "frappe" | app_name = "frappe" | ||||
app_title = "Frappe Framework" | app_title = "Frappe Framework" | ||||
app_publisher = "Web Notes Technologies Pvt. Ltd. and Contributors" | |||||
app_publisher = "Web Notes Technologies Pvt. Ltd." | |||||
app_description = "Full Stack Web Application Framwork in Python" | app_description = "Full Stack Web Application Framwork in Python" | ||||
app_icon = "assets/frappe/images/frappe.svg" | app_icon = "assets/frappe/images/frappe.svg" | ||||
app_version = __version__ | app_version = __version__ | ||||
@@ -51,8 +51,17 @@ has_permission = { | |||||
doc_events = { | doc_events = { | ||||
"*": { | "*": { | ||||
"on_update": "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications", | |||||
"on_cancel": "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications", | |||||
"after_insert": "frappe.core.doctype.email_alert.email_alert.trigger_email_alerts", | |||||
"validate": "frappe.core.doctype.email_alert.email_alert.trigger_email_alerts", | |||||
"on_update": [ | |||||
"frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications", | |||||
"frappe.core.doctype.email_alert.email_alert.trigger_email_alerts" | |||||
], | |||||
"on_submit": "frappe.core.doctype.email_alert.email_alert.trigger_email_alerts", | |||||
"on_cancel": [ | |||||
"frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications", | |||||
"frappe.core.doctype.email_alert.email_alert.trigger_email_alerts" | |||||
], | |||||
"on_trash": "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications" | "on_trash": "frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications" | ||||
}, | }, | ||||
"User Vote": { | "User Vote": { | ||||
@@ -70,6 +79,7 @@ scheduler_events = { | |||||
"frappe.core.doctype.notification_count.notification_count.delete_event_notification_count", | "frappe.core.doctype.notification_count.notification_count.delete_event_notification_count", | ||||
"frappe.core.doctype.event.event.send_event_digest", | "frappe.core.doctype.event.event.send_event_digest", | ||||
"frappe.sessions.clear_expired_sessions", | "frappe.sessions.clear_expired_sessions", | ||||
"frappe.core.doctype.email_alert.email_alert.trigger_daily_alerts", | |||||
], | ], | ||||
"hourly": [ | "hourly": [ | ||||
"frappe.website.doctype.website_group.website_group.clear_event_cache" | "frappe.website.doctype.website_group.website_group.clear_event_cache" | ||||
@@ -83,6 +83,10 @@ class BaseDocument(object): | |||||
else: | else: | ||||
self.__dict__[key] = value | self.__dict__[key] = value | ||||
def delete(self, key): | |||||
if key in self.__dict__: | |||||
del self.__dict__[key] | |||||
def append(self, key, value=None): | def append(self, key, value=None): | ||||
if value==None: | if value==None: | ||||
value={} | value={} | ||||
@@ -124,8 +124,11 @@ class Document(BaseDocument): | |||||
self.set_new_name() | self.set_new_name() | ||||
self.run_method("before_insert") | self.run_method("before_insert") | ||||
self.set_parent_in_children() | self.set_parent_in_children() | ||||
self.set("__in_insert", True) | |||||
self.run_before_save_methods() | self.run_before_save_methods() | ||||
self._validate() | self._validate() | ||||
self.delete("__in_insert") | |||||
# run validate, on update etc. | # run validate, on update etc. | ||||
@@ -140,7 +143,9 @@ class Document(BaseDocument): | |||||
d.db_insert() | d.db_insert() | ||||
self.run_method("after_insert") | self.run_method("after_insert") | ||||
self.set("__in_insert", True) | |||||
self.run_post_save_methods() | self.run_post_save_methods() | ||||
self.delete("__in_insert") | |||||
return self | return self | ||||
@@ -51,7 +51,13 @@ frappe.Application = Class.extend({ | |||||
// control panel startup code | // control panel startup code | ||||
this.run_startup_js(); | this.run_startup_js(); | ||||
if(frappe.boot) { | if(frappe.boot) { | ||||
if(localStorage.getItem("session_lost_route")) { | |||||
window.location.hash = localStorage.getItem("session_lost_route"); | |||||
localStorage.removeItem("session_lost_route"); | |||||
} | |||||
// route to home page | // route to home page | ||||
frappe.route(); | frappe.route(); | ||||
} | } | ||||
@@ -176,6 +176,7 @@ frappe.request.cleanup = function(opts, r) { | |||||
// session expired? - Guest has no business here! | // session expired? - Guest has no business here! | ||||
if(r.session_expired || frappe.get_cookie("sid")==="Guest") { | if(r.session_expired || frappe.get_cookie("sid")==="Guest") { | ||||
if(!frappe.app.logged_out) { | if(!frappe.app.logged_out) { | ||||
localStorage.setItem("session_lost_route", location.hash); | |||||
msgprint(__('Session Expired. Logging you out')); | msgprint(__('Session Expired. Logging you out')); | ||||
frappe.app.logout(); | frappe.app.logout(); | ||||
} | } | ||||
@@ -10,7 +10,7 @@ from frappe.modules import load_doctype_module, get_module_name | |||||
from frappe.utils import cstr | from frappe.utils import cstr | ||||
def main(app=None, module=None, doctype=None, verbose=False, tests=()): | |||||
def main(app=None, module=None, doctype=None, verbose=False, tests=(), force=False): | |||||
frappe.flags.print_messages = verbose | frappe.flags.print_messages = verbose | ||||
frappe.flags.in_test = True | frappe.flags.in_test = True | ||||
@@ -29,7 +29,7 @@ def main(app=None, module=None, doctype=None, verbose=False, tests=()): | |||||
frappe.get_attr(fn)() | frappe.get_attr(fn)() | ||||
if doctype: | if doctype: | ||||
ret = run_tests_for_doctype(doctype, verbose=verbose, tests=tests) | |||||
ret = run_tests_for_doctype(doctype, verbose=verbose, tests=tests, force=force) | |||||
elif module: | elif module: | ||||
ret = run_tests_for_module(module, verbose=verbose, tests=tests) | ret = run_tests_for_module(module, verbose=verbose, tests=tests) | ||||
else: | else: | ||||
@@ -63,10 +63,13 @@ def run_all_tests(app=None, verbose=False): | |||||
return unittest.TextTestRunner(verbosity=1+(verbose and 1 or 0)).run(test_suite) | return unittest.TextTestRunner(verbosity=1+(verbose and 1 or 0)).run(test_suite) | ||||
def run_tests_for_doctype(doctype, verbose=False, tests=()): | |||||
def run_tests_for_doctype(doctype, verbose=False, tests=(), force=False): | |||||
module = frappe.db.get_value("DocType", doctype, "module") | module = frappe.db.get_value("DocType", doctype, "module") | ||||
test_module = get_module_name(doctype, module, "test_") | test_module = get_module_name(doctype, module, "test_") | ||||
make_test_records(doctype, verbose=verbose) | |||||
if force: | |||||
for name in frappe.db.sql_list("select name from `tab%s`" % doctype): | |||||
frappe.delete_doc(doctype, name, force=True) | |||||
make_test_records(doctype, verbose=verbose, force=force) | |||||
module = frappe.get_module(test_module) | module = frappe.get_module(test_module) | ||||
return _run_unittest(module, verbose=verbose, tests=tests) | return _run_unittest(module, verbose=verbose, tests=tests) | ||||
@@ -107,7 +110,7 @@ def _add_test(path, filename, verbose, test_suite=None): | |||||
module = imp.load_source(filename[:-3], os.path.join(path, filename)) | module = imp.load_source(filename[:-3], os.path.join(path, filename)) | ||||
test_suite.addTest(unittest.TestLoader().loadTestsFromModule(module)) | test_suite.addTest(unittest.TestLoader().loadTestsFromModule(module)) | ||||
def make_test_records(doctype, verbose=0): | |||||
def make_test_records(doctype, verbose=0, force=False): | |||||
frappe.flags.mute_emails = True | frappe.flags.mute_emails = True | ||||
if not frappe.db: | if not frappe.db: | ||||
@@ -119,8 +122,8 @@ def make_test_records(doctype, verbose=0): | |||||
if options not in frappe.local.test_objects: | if options not in frappe.local.test_objects: | ||||
frappe.local.test_objects[options] = [] | frappe.local.test_objects[options] = [] | ||||
make_test_records(options, verbose) | |||||
make_test_records_for_doctype(options, verbose) | |||||
make_test_records(options, verbose, force) | |||||
make_test_records_for_doctype(options, verbose, force) | |||||
def get_modules(doctype): | def get_modules(doctype): | ||||
module = frappe.db.get_value("DocType", doctype, "module") | module = frappe.db.get_value("DocType", doctype, "module") | ||||
@@ -155,7 +158,7 @@ def get_dependencies(doctype): | |||||
return options_list | return options_list | ||||
def make_test_records_for_doctype(doctype, verbose=0): | |||||
def make_test_records_for_doctype(doctype, verbose=0, force=False): | |||||
module, test_module = get_modules(doctype) | module, test_module = get_modules(doctype) | ||||
if verbose: | if verbose: | ||||
@@ -14,8 +14,8 @@ from frappe.utils import cint, get_url, nowdate | |||||
class BulkLimitCrossedError(frappe.ValidationError): pass | class BulkLimitCrossedError(frappe.ValidationError): pass | ||||
def send(recipients=None, sender=None, doctype='User', email_field='email', | def send(recipients=None, sender=None, doctype='User', email_field='email', | ||||
subject='[No Subject]', message='[No Content]', ref_doctype=None, ref_docname=None, | |||||
add_unsubscribe_link=True): | |||||
subject='[No Subject]', message='[No Content]', ref_doctype=None, | |||||
ref_docname=None, add_unsubscribe_link=True): | |||||
def is_unsubscribed(rdata): | def is_unsubscribed(rdata): | ||||
if not rdata: | if not rdata: | ||||
@@ -12,7 +12,7 @@ from __future__ import unicode_literals | |||||
import frappe | import frappe | ||||
import frappe.utils | import frappe.utils | ||||
from frappe.utils.file_lock import create_lock, check_lock, delete_lock, LockTimeoutError | |||||
from frappe.utils.file_lock import create_lock, check_lock, delete_lock | |||||
from datetime import datetime | from datetime import datetime | ||||
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' | DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' | ||||
@@ -95,7 +95,7 @@ def log(method, message=None): | |||||
d = frappe.new_doc("Scheduler Log") | d = frappe.new_doc("Scheduler Log") | ||||
d.method = method | d.method = method | ||||
d.error = message | d.error = message | ||||
d.insert() | |||||
d.insert(ignore_permissions=True) | |||||
frappe.db.commit() | frappe.db.commit() | ||||