@@ -309,7 +309,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message | |||
as_markdown=False, bulk=False, reference_doctype=None, reference_name=None, | |||
unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, | |||
attachments=None, content=None, doctype=None, name=None, reply_to=None, | |||
cc=(), message_id=None, as_bulk=False, send_after=None): | |||
cc=(), message_id=None, as_bulk=False, send_after=None, expose_recipients=False): | |||
"""Send email using user's default **Email Account** or global default **Email Account**. | |||
@@ -327,6 +327,7 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message | |||
:param reply_to: Reply-To email id. | |||
:param message_id: Used for threading. If a reply is received to this email, Message-Id is sent back as In-Reply-To in received email. | |||
:param send_after: Send after the given datetime. | |||
:param expose_recipients: Display all recipients in the footer message - "This email was sent to" | |||
""" | |||
if bulk or as_bulk: | |||
@@ -335,7 +336,8 @@ def sendmail(recipients=(), sender="", subject="No Subject", message="No Message | |||
subject=subject, message=content or message, | |||
reference_doctype = doctype or reference_doctype, reference_name = name or reference_name, | |||
unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message, | |||
attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, send_after=send_after) | |||
attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, send_after=send_after, | |||
expose_recipients=expose_recipients) | |||
else: | |||
import frappe.email | |||
if as_markdown: | |||
@@ -1,2 +1,2 @@ | |||
from __future__ import unicode_literals | |||
__version__ = "6.2.0" | |||
__version__ = "6.3.0" |
@@ -17,57 +17,38 @@ END_LINE = '<!-- frappe: end-file -->' | |||
TASK_LOG_MAX_AGE = 86400 # 1 day in seconds | |||
redis_server = None | |||
def handler(f): | |||
cmd = f.__module__ + '.' + f.__name__ | |||
def _run(args, set_in_response=True): | |||
def run(args, set_in_response=True): | |||
from frappe.tasks import run_async_task | |||
from frappe.handler import execute_cmd | |||
if frappe.conf.disable_async: | |||
return execute_cmd(cmd, from_async=True) | |||
args = frappe._dict(args) | |||
task = run_async_task.delay(frappe.local.site, | |||
(frappe.session and frappe.session.user) or 'Administrator', cmd, args) | |||
task = run_async_task.delay(site=frappe.local.site, | |||
user=(frappe.session and frappe.session.user) or 'Administrator', cmd=cmd, | |||
form_dict=args) | |||
if set_in_response: | |||
frappe.local.response['task_id'] = task.id | |||
return task.id | |||
@wraps(f) | |||
def queue(*args, **kwargs): | |||
from frappe.tasks import run_async_task | |||
from frappe.handler import execute_cmd | |||
if frappe.conf.disable_async: | |||
return execute_cmd(cmd, from_async=True) | |||
task = run_async_task.delay(frappe.local.site, | |||
(frappe.session and frappe.session.user) or 'Administrator', cmd, | |||
frappe.local.form_dict) | |||
frappe.local.response['task_id'] = task.id | |||
task_id = run(frappe.local.form_dict, set_in_response=True) | |||
return { | |||
"status": "queued", | |||
"task_id": task.id | |||
"task_id": task_id | |||
} | |||
queue.async = True | |||
queue.queue = f | |||
queue.run = _run | |||
queue.run = run | |||
frappe.whitelisted.append(f) | |||
frappe.whitelisted.append(queue) | |||
return queue | |||
def run_async_task(method, args, reference_doctype=None, reference_name=None, set_in_response=True): | |||
if frappe.local.request and frappe.local.request.method == "GET": | |||
frappe.throw("Cannot run task in a GET request") | |||
task_id = method.run(args, set_in_response=set_in_response) | |||
task = frappe.new_doc("Async Task") | |||
task.celery_task_id = task_id | |||
task.status = "Queued" | |||
task.reference_doctype = reference_doctype | |||
task.reference_name = reference_name | |||
task.save() | |||
return task_id | |||
@frappe.whitelist() | |||
def get_pending_tasks_for_doc(doctype, docname): | |||
return frappe.db.sql_list("select name from `tabAsync Task` where status in ('Queued', 'Running') and reference_doctype='%s' and reference_name='%s'" % (doctype, docname)) | |||
@@ -76,10 +57,9 @@ def get_pending_tasks_for_doc(doctype, docname): | |||
@handler | |||
def ping(): | |||
from time import sleep | |||
sleep(6) | |||
sleep(1) | |||
return "pong" | |||
@frappe.whitelist() | |||
def get_task_status(task_id): | |||
from frappe.celery_app import get_celery | |||
@@ -91,9 +71,7 @@ def get_task_status(task_id): | |||
"progress": 0 | |||
} | |||
def set_task_status(task_id, status, response=None): | |||
frappe.db.set_value("Async Task", task_id, "status", status) | |||
if not response: | |||
response = {} | |||
response.update({ | |||
@@ -167,6 +145,7 @@ def emit_via_redis(event, message, room): | |||
try: | |||
r.publish('events', frappe.as_json({'event': event, 'message': message, 'room': room})) | |||
except redis.exceptions.ConnectionError: | |||
# print frappe.get_traceback() | |||
pass | |||
def put_log(line_no, line, task_id=None): | |||
@@ -174,6 +174,10 @@ def files_dirty(): | |||
return False | |||
def compile_less(): | |||
from distutils.spawn import find_executable | |||
if not find_executable("lessc"): | |||
return | |||
for path in app_paths: | |||
less_path = os.path.join(path, "public", "less") | |||
if os.path.exists(less_path): | |||
@@ -189,4 +193,4 @@ def compile_less(): | |||
print "compiling {0}".format(fpath) | |||
css_path = os.path.join(path, "public", "css", fname.rsplit(".", 1)[0] + ".css") | |||
os.system("which lessc && lessc {0} > {1}".format(fpath, css_path)) | |||
os.system("lessc {0} > {1}".format(fpath, css_path)) |
@@ -10,8 +10,9 @@ task_logger = get_task_logger(__name__) | |||
from datetime import timedelta | |||
import frappe | |||
import json | |||
import os | |||
import threading | |||
import time | |||
SITES_PATH = os.environ.get('SITES_PATH', '.') | |||
@@ -26,35 +27,43 @@ _app = None | |||
def get_celery(): | |||
global _app | |||
if not _app: | |||
conf = frappe.get_site_config(sites_path=SITES_PATH) | |||
_app = Celery('frappe', | |||
broker=conf.celery_broker or DEFAULT_CELERY_BROKER, | |||
backend=conf.async_redis_server or DEFAULT_CELERY_BACKEND) | |||
setup_celery(_app, conf) | |||
_app = get_celery_app() | |||
return _app | |||
def setup_celery(app, conf): | |||
def get_celery_app(): | |||
conf = get_site_config() | |||
app = Celery('frappe', | |||
broker=conf.celery_broker or DEFAULT_CELERY_BROKER, | |||
backend=conf.async_redis_server or DEFAULT_CELERY_BACKEND) | |||
app.autodiscover_tasks(frappe.get_all_apps(with_frappe=True, with_internal_apps=False, | |||
sites_path=SITES_PATH)) | |||
app.conf.CELERY_TASK_SERIALIZER = 'json' | |||
app.conf.CELERY_ACCEPT_CONTENT = ['json'] | |||
app.conf.CELERY_TIMEZONE = 'UTC' | |||
app.conf.CELERY_RESULT_SERIALIZER = 'json' | |||
app.CELERY_TASK_RESULT_EXPIRES = timedelta(0, 3600) | |||
app.conf.CELERY_TASK_RESULT_EXPIRES = timedelta(0, 3600) | |||
if conf.monitory_celery: | |||
app.conf.CELERY_SEND_EVENTS = True | |||
app.conf.CELERY_SEND_TASK_SENT_EVENT = True | |||
if conf.celery_queue_per_site: | |||
app.conf.CELERY_ROUTES = (SiteRouter(), AsyncTaskRouter()) | |||
app.conf.CELERYBEAT_SCHEDULE = get_beat_schedule(conf) | |||
if conf.celery_error_emails: | |||
app.conf.CELERY_SEND_TASK_ERROR_EMAILS = True | |||
for k, v in conf.celery_error_emails.iteritems(): | |||
setattr(app.conf, k, v) | |||
return app | |||
def get_site_config(): | |||
return frappe.get_site_config(sites_path=SITES_PATH) | |||
class SiteRouter(object): | |||
def route_for_task(self, task, args=None, kwargs=None): | |||
if hasattr(frappe.local, 'site'): | |||
@@ -62,17 +71,17 @@ class SiteRouter(object): | |||
return get_queue(frappe.local.site, LONGJOBS_PREFIX) | |||
else: | |||
return get_queue(frappe.local.site) | |||
return None | |||
class AsyncTaskRouter(object): | |||
def route_for_task(self, task, args=None, kwargs=None): | |||
if task == "frappe.tasks.run_async_task" and hasattr(frappe.local, 'site'): | |||
return get_queue(frappe.local.site, ASYNC_TASKS_PREFIX) | |||
def get_queue(site, prefix=None): | |||
return {'queue': "{}{}".format(prefix or "", site)} | |||
def get_beat_schedule(conf): | |||
schedule = { | |||
'scheduler': { | |||
@@ -80,17 +89,136 @@ def get_beat_schedule(conf): | |||
'schedule': timedelta(seconds=conf.scheduler_interval or DEFAULT_SCHEDULER_INTERVAL) | |||
}, | |||
} | |||
if conf.celery_queue_per_site: | |||
schedule['sync_queues'] = { | |||
'task': 'frappe.tasks.sync_queues', | |||
'schedule': timedelta(seconds=conf.scheduler_interval or DEFAULT_SCHEDULER_INTERVAL) | |||
} | |||
return schedule | |||
def celery_task(*args, **kwargs): | |||
return get_celery().task(*args, **kwargs) | |||
def make_async_task(args): | |||
task = frappe.new_doc("Async Task") | |||
task.update(args) | |||
task.status = "Queued" | |||
task.set_docstatus_user_and_timestamp() | |||
task.db_insert() | |||
task.notify_update() | |||
def run_test(): | |||
for i in xrange(30): | |||
test.delay(site=frappe.local.site) | |||
@celery_task() | |||
def test(site=None): | |||
time.sleep(1) | |||
print "task" | |||
class MonitorThread(object): | |||
"""Thread manager for monitoring celery events""" | |||
def __init__(self, celery_app, interval=1): | |||
self.celery_app = celery_app | |||
self.interval = interval | |||
self.state = self.celery_app.events.State() | |||
self.thread = threading.Thread(target=self.run, args=()) | |||
self.thread.daemon = True | |||
self.thread.start() | |||
def catchall(self, event): | |||
if event['type'] != 'worker-heartbeat': | |||
self.state.event(event) | |||
if not 'uuid' in event: | |||
return | |||
task = self.state.tasks.get(event['uuid']) | |||
info = task.info() | |||
if 'name' in event and 'enqueue_events_for_site' in event['name']: | |||
return | |||
try: | |||
kwargs = eval(info.get('kwargs')) | |||
if 'site' in kwargs: | |||
frappe.connect(kwargs['site']) | |||
if event['type']=='task-sent': | |||
make_async_task({ | |||
'name': event['uuid'], | |||
'task_name': kwargs.get("cmd") or event['name'] | |||
}) | |||
elif event['type']=='task-received': | |||
try: | |||
task = frappe.get_doc("Async Task", event['uuid']) | |||
task.status = 'Started' | |||
task.set_docstatus_user_and_timestamp() | |||
task.db_update() | |||
task.notify_update() | |||
except frappe.DoesNotExistError: | |||
pass | |||
elif event['type']=='task-succeeded': | |||
try: | |||
task = frappe.get_doc("Async Task", event['uuid']) | |||
task.status = 'Succeeded' | |||
task.result = info.get('result') | |||
task.runtime = info.get('runtime') | |||
task.set_docstatus_user_and_timestamp() | |||
task.db_update() | |||
task.notify_update() | |||
except frappe.DoesNotExistError: | |||
pass | |||
elif event['type']=='task-failed': | |||
try: | |||
task = frappe.get_doc("Async Task", event['uuid']) | |||
task.status = 'Failed' | |||
task.traceback = event.get('traceback') or event.get('exception') | |||
task.traceback = frappe.as_json(info) + "\n\n" + task.traceback | |||
task.runtime = info.get('runtime') | |||
task.set_docstatus_user_and_timestamp() | |||
task.db_update() | |||
task.notify_update() | |||
except frappe.DoesNotExistError: | |||
pass | |||
frappe.db.commit() | |||
except Exception: | |||
print frappe.get_traceback() | |||
finally: | |||
frappe.destroy() | |||
def run(self): | |||
while True: | |||
try: | |||
with self.celery_app.connection() as connection: | |||
recv = self.celery_app.events.Receiver(connection, handlers={ | |||
'*': self.catchall | |||
}) | |||
recv.capture(limit=None, timeout=None, wakeup=True) | |||
except (KeyboardInterrupt, SystemExit): | |||
raise | |||
except Exception: | |||
# unable to capture | |||
print "unable to capture:" | |||
print frappe.get_traceback() | |||
time.sleep(self.interval) | |||
if __name__ == '__main__': | |||
get_celery().start() | |||
app = get_celery() | |||
if get_site_config().get("monitor_celery"): | |||
MonitorThread(app) | |||
app.start() |
@@ -0,0 +1,2 @@ | |||
- You can now add **CC** in Email | |||
- Show checkboxes in Print |
@@ -2,7 +2,7 @@ | |||
"allow_copy": 0, | |||
"allow_import": 0, | |||
"allow_rename": 0, | |||
"autoname": "field:celery_task_id", | |||
"autoname": "", | |||
"creation": "2015-07-03 11:28:03.496346", | |||
"custom": 0, | |||
"docstatus": 0, | |||
@@ -13,18 +13,63 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "celery_task_id", | |||
"fieldname": "status", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Status", | |||
"no_copy": 0, | |||
"options": "\nQueued\nRunning\nSucceeded\nFailed\n", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "task_name", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Task Name", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "runtime", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Celery Task ID", | |||
"label": "Runtime", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
@@ -35,19 +80,18 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "status", | |||
"fieldtype": "Select", | |||
"fieldname": "result", | |||
"fieldtype": "Code", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Status", | |||
"label": "Result", | |||
"no_copy": 0, | |||
"options": "\nQueued\nRunning\nFinished\nFailed\n", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
@@ -58,13 +102,13 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "stdout", | |||
"fieldtype": "Long Text", | |||
"fieldname": "traceback", | |||
"fieldtype": "Code", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "stdout", | |||
"label": "Traceback", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
@@ -80,18 +124,17 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "stderr", | |||
"fieldtype": "Long Text", | |||
"fieldname": "section_break_6", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "stderr", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 1, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
@@ -114,7 +157,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
@@ -137,7 +180,7 @@ | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
@@ -152,7 +195,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2015-07-28 16:18:11.344349", | |||
"modified": "2015-09-07 08:08:22.193911", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Async Task", | |||
@@ -183,5 +226,6 @@ | |||
"read_only": 0, | |||
"read_only_onload": 0, | |||
"sort_field": "modified", | |||
"sort_order": "DESC" | |||
"sort_order": "DESC", | |||
"title_field": "task_name" | |||
} |
@@ -0,0 +1,10 @@ | |||
frappe.listview_settings['Async Task'] = { | |||
add_fields: ["status"], | |||
get_indicator: function(doc) { | |||
if(doc.status==="Succeeded") { | |||
return [__("Succeeded"), "green", "status,=,Succeeded"]; | |||
} else if(doc.status==="Failed") { | |||
return [__("Failed"), "red", "status,=,Failed"]; | |||
} | |||
} | |||
}; |
@@ -42,7 +42,8 @@ class Comment(Document): | |||
message['broadcast'] = True | |||
frappe.publish_realtime('new_message', message) | |||
else: | |||
frappe.publish_realtime('new_message', self.as_dict(), user=frappe.session.user) | |||
# comment_docname contains the user who is addressed in the messages' page comment | |||
frappe.publish_realtime('new_message', self.as_dict(), user=self.comment_docname) | |||
else: | |||
frappe.publish_realtime('new_comment', self.as_dict(), doctype= self.comment_doctype, | |||
docname = self.comment_docname) | |||
@@ -37,20 +37,21 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "sent_or_received", | |||
"depends_on": "", | |||
"fieldname": "communication_medium", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Sent or Received", | |||
"label": "Communication Medium", | |||
"no_copy": 0, | |||
"options": "Sent\nReceived", | |||
"options": "\nChat\nPhone\nEmail\nSMS\nVisit\nOther", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
@@ -59,17 +60,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "status", | |||
"fieldtype": "Select", | |||
"fieldname": "recipients", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Status", | |||
"in_list_view": 0, | |||
"label": "Recipients", | |||
"no_copy": 0, | |||
"options": "Open\nReplied\nClosed\nLinked", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -82,16 +81,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"description": "Integrations can use this field to set email delivery status", | |||
"fieldname": "delivery_status", | |||
"fieldtype": "Select", | |||
"hidden": 1, | |||
"depends_on": "eval:doc.communication_medium===\"Email\"", | |||
"fieldname": "cc", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Delivery Status", | |||
"label": "CC", | |||
"no_copy": 0, | |||
"options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
@@ -106,19 +104,20 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "subject", | |||
"depends_on": "eval:doc.communication_medium!==\"Email\"", | |||
"fieldname": "phone_no", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Subject", | |||
"label": "Phone No.", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
@@ -148,15 +147,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "reference_doctype", | |||
"fieldtype": "Link", | |||
"fieldname": "status", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Reference DocType", | |||
"in_list_view": 1, | |||
"label": "Status", | |||
"no_copy": 0, | |||
"options": "DocType", | |||
"options": "Open\nReplied\nClosed\nLinked", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
@@ -171,21 +170,20 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "reference_name", | |||
"fieldtype": "Dynamic Link", | |||
"fieldname": "sent_or_received", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Reference Name", | |||
"in_list_view": 1, | |||
"label": "Sent or Received", | |||
"no_copy": 0, | |||
"options": "reference_doctype", | |||
"options": "Sent\nReceived", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
@@ -194,13 +192,16 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "section_break_8", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"description": "Integrations can use this field to set email delivery status", | |||
"fieldname": "delivery_status", | |||
"fieldtype": "Select", | |||
"hidden": 1, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Delivery Status", | |||
"no_copy": 0, | |||
"options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
@@ -215,37 +216,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "content", | |||
"fieldtype": "Text Editor", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Content", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0, | |||
"width": "400" | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "additional_info", | |||
"fieldname": "section_break_10", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Additional Info", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -258,19 +237,19 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "recipients", | |||
"fieldname": "subject", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Recipients", | |||
"label": "Subject", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
@@ -279,15 +258,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "phone_no", | |||
"fieldtype": "Data", | |||
"fieldname": "section_break_8", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Phone No.", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -300,15 +279,14 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "communication_medium", | |||
"fieldtype": "Select", | |||
"fieldname": "content", | |||
"fieldtype": "Text Editor", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Communication Medium", | |||
"in_list_view": 0, | |||
"label": "Content", | |||
"no_copy": 0, | |||
"options": "\nChat\nPhone\nEmail\nSMS\nVisit\nOther", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
@@ -316,21 +294,22 @@ | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
"unique": 0, | |||
"width": "400" | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "column_break_14", | |||
"fieldtype": "Column Break", | |||
"collapsible": 1, | |||
"fieldname": "additional_info", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "More Information", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -386,14 +365,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "section_break2", | |||
"fieldtype": "Section Break", | |||
"default": "Today", | |||
"fieldname": "communication_date", | |||
"fieldtype": "Datetime", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Date", | |||
"no_copy": 0, | |||
"options": "simple", | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
@@ -407,15 +387,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "column_break4", | |||
"fieldname": "column_break_14", | |||
"fieldtype": "Column Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "By", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -428,15 +408,15 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "email_account", | |||
"fieldname": "reference_doctype", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Email Account", | |||
"label": "Reference DocType", | |||
"no_copy": 0, | |||
"options": "Email Account", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
@@ -451,19 +431,19 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"default": "__user", | |||
"fieldname": "user", | |||
"fieldtype": "Link", | |||
"fieldname": "reference_name", | |||
"fieldtype": "Dynamic Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 1, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "User", | |||
"label": "Reference Name", | |||
"no_copy": 0, | |||
"options": "User", | |||
"options": "reference_doctype", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 1, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
@@ -474,17 +454,19 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "column_break5", | |||
"fieldtype": "Column Break", | |||
"fieldname": "in_reply_to", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "On", | |||
"label": "In Reply To", | |||
"no_copy": 0, | |||
"options": "Communication", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
@@ -495,16 +477,17 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"default": "Today", | |||
"fieldname": "communication_date", | |||
"fieldtype": "Datetime", | |||
"fieldname": "email_account", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Date", | |||
"label": "Email Account", | |||
"no_copy": 0, | |||
"options": "Email Account", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
@@ -517,17 +500,19 @@ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "_user_tags", | |||
"fieldtype": "Data", | |||
"hidden": 1, | |||
"ignore_user_permissions": 0, | |||
"default": "__user", | |||
"fieldname": "user", | |||
"fieldtype": "Link", | |||
"hidden": 0, | |||
"ignore_user_permissions": 1, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "User Tags", | |||
"no_copy": 1, | |||
"label": "User", | |||
"no_copy": 0, | |||
"options": "User", | |||
"permlevel": 0, | |||
"print_hide": 1, | |||
"read_only": 0, | |||
"print_hide": 0, | |||
"read_only": 1, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
@@ -556,6 +541,27 @@ | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "_user_tags", | |||
"fieldtype": "Data", | |||
"hidden": 1, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "User Tags", | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 1, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
} | |||
], | |||
"hide_heading": 0, | |||
@@ -567,7 +573,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2015-08-14 17:46:20.902296", | |||
"modified": "2015-09-15 05:51:16.112080", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Communication", | |||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals, absolute_import | |||
import frappe | |||
import json | |||
from email.utils import formataddr, parseaddr | |||
from frappe.utils import get_url, get_formatted_email, cstr, cint | |||
from frappe.utils import get_url, get_formatted_email, cstr, cint, validate_email_add, split_emails | |||
from frappe.utils.file_manager import get_file | |||
import frappe.email.smtp | |||
from frappe import _ | |||
@@ -30,10 +30,27 @@ class Communication(Document): | |||
if self.get("__islocal"): | |||
if self.reference_doctype and self.reference_name: | |||
self.status = "Linked" | |||
else: | |||
self.status = "Open" | |||
# validate recipients | |||
for email in split_emails(self.recipients): | |||
validate_email_add(email, throw=True) | |||
# validate CC | |||
for email in split_emails(self.cc): | |||
validate_email_add(email, throw=True) | |||
def after_insert(self): | |||
# send new comment to listening clients | |||
comment = self.as_dict() | |||
comment["comment"] = comment["content"] | |||
comment["comment_by"] = comment["sender"] | |||
comment["comment_type"] = comment["communication_medium"] | |||
frappe.publish_realtime('new_comment', comment, doctype = self.reference_doctype, | |||
docname = self.reference_name) | |||
def on_update(self): | |||
"""Update parent status as `Open` or `Replied`.""" | |||
self.update_parent() | |||
@@ -50,9 +67,9 @@ class Communication(Document): | |||
to_status = "Open" if self.sent_or_received=="Received" else "Replied" | |||
if to_status in status_field.options.splitlines(): | |||
frappe.db.set_value(parent.doctype, parent.name, "status", to_status) | |||
parent.db_set("status", to_status) | |||
parent.notify_modified() | |||
parent.notify_update() | |||
def send(self, print_html=None, print_format=None, attachments=None, | |||
send_me_a_copy=False, recipients=None): | |||
@@ -64,51 +81,41 @@ class Communication(Document): | |||
self.send_me_a_copy = send_me_a_copy | |||
self.notify(print_html, print_format, attachments, recipients) | |||
def set_incoming_outgoing_accounts(self): | |||
self.incoming_email_account = self.outgoing_email_account = None | |||
if self.reference_doctype: | |||
self.incoming_email_account = frappe.db.get_value("Email Account", | |||
{"append_to": self.reference_doctype, "enable_incoming": 1}, "email_id") | |||
self.outgoing_email_account = frappe.db.get_value("Email Account", | |||
{"append_to": self.reference_doctype, "enable_outgoing": 1}, | |||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) | |||
if not self.incoming_email_account: | |||
self.incoming_email_account = frappe.db.get_value("Email Account", {"default_incoming": 1}, "email_id") | |||
if not self.outgoing_email_account: | |||
self.outgoing_email_account = frappe.db.get_value("Email Account", {"default_outgoing": 1}, | |||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) or frappe._dict() | |||
def notify(self, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False): | |||
def notify(self, print_html=None, print_format=None, attachments=None, | |||
recipients=None, cc=None, fetched_from_email_account=False): | |||
"""Calls a delayed celery task 'sendmail' that enqueus email in Bulk Email queue | |||
:param print_html: Send given value as HTML attachment | |||
:param print_format: Attach print format of parent document | |||
:param attachments: A list of filenames that should be attached when sending this email | |||
:param recipients: Email recipients | |||
:param except_recipient: True when pulling email, the notification shouldn't go to the main recipient | |||
:param cc: Send email as CC to | |||
:param fetched_from_email_account: True when pulling email, the notification shouldn't go to the main recipient | |||
""" | |||
recipients, cc = self.get_recipients_and_cc(recipients, cc, | |||
fetched_from_email_account=fetched_from_email_account) | |||
self.emails_not_sent_to = set(self.all_email_addresses) - set(recipients) - set(cc) | |||
if frappe.flags.in_test: | |||
# for test cases, run synchronously | |||
self._notify(print_html=print_html, print_format=print_format, attachments=attachments, | |||
recipients=recipients, except_recipient=except_recipient) | |||
recipients=recipients, cc=cc) | |||
else: | |||
from frappe.tasks import sendmail | |||
sendmail.delay(frappe.local.site, self.name, | |||
print_html=print_html, print_format=print_format, attachments=attachments, | |||
recipients=recipients, except_recipient=except_recipient) | |||
recipients=recipients, cc=cc) | |||
def _notify(self, print_html=None, print_format=None, attachments=None, | |||
recipients=None, cc=None): | |||
def _notify(self, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False): | |||
self.prepare_to_notify(print_html, print_format, attachments) | |||
if not recipients: | |||
recipients = self.get_recipients(except_recipient=except_recipient) | |||
frappe.sendmail( | |||
recipients=recipients, | |||
recipients=(recipients or []) + (cc or []), | |||
expose_recipients=True, | |||
sender=self.sender, | |||
reply_to=self.incoming_email_account, | |||
subject=self.subject, | |||
@@ -121,6 +128,27 @@ class Communication(Document): | |||
bulk=True | |||
) | |||
def get_recipients_and_cc(self, recipients, cc, fetched_from_email_account=False): | |||
self.all_email_addresses = [] | |||
if not recipients: | |||
recipients = self.get_recipients() | |||
if not cc: | |||
cc = self.get_cc(recipients, fetched_from_email_account=fetched_from_email_account) | |||
if fetched_from_email_account: | |||
# email was already sent to the original recipient by the sender's email service | |||
original_recipients, recipients = recipients, [] | |||
# cc that was received in the email | |||
original_cc = split_emails(self.cc) | |||
# don't cc to people who already received the mail from sender's email service | |||
cc = list(set(cc) - set(original_cc) - set(original_recipients)) | |||
return recipients, cc | |||
def prepare_to_notify(self, print_html=None, print_format=None, attachments=None): | |||
"""Prepare to make multipart MIME Email | |||
@@ -156,78 +184,129 @@ class Communication(Document): | |||
else: | |||
self.attachments.append(a) | |||
def get_recipients(self, except_recipient=False): | |||
"""Build a list of users to which this email should go to""" | |||
def set_incoming_outgoing_accounts(self): | |||
self.incoming_email_account = self.outgoing_email_account = None | |||
if self.reference_doctype: | |||
self.incoming_email_account = frappe.db.get_value("Email Account", | |||
{"append_to": self.reference_doctype, "enable_incoming": 1}, "email_id") | |||
self.outgoing_email_account = frappe.db.get_value("Email Account", | |||
{"append_to": self.reference_doctype, "enable_outgoing": 1}, | |||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) | |||
if not self.incoming_email_account: | |||
self.incoming_email_account = frappe.db.get_value("Email Account", {"default_incoming": 1}, "email_id") | |||
if not self.outgoing_email_account: | |||
self.outgoing_email_account = frappe.db.get_value("Email Account", {"default_outgoing": 1}, | |||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) or frappe._dict() | |||
def get_recipients(self): | |||
"""Build a list of email addresses for To""" | |||
# [EDGE CASE] self.recipients can be None when an email is sent as BCC | |||
original_recipients = [s.strip() for s in cstr(self.recipients).split(",")] | |||
recipients = original_recipients[:] | |||
recipients = split_emails(self.recipients) | |||
if recipients: | |||
# this will be used to eventually find email addresses that aren't sent to | |||
self.all_email_addresses.extend(recipients) | |||
# exclude email accounts | |||
exclude = [d[0] for d in | |||
frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)] | |||
exclude += [d[0] for d in | |||
frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True) | |||
if d[0]] | |||
recipients = self.filter_email_list(recipients, exclude) | |||
return recipients | |||
def get_cc(self, recipients=None, fetched_from_email_account=False): | |||
"""Build a list of email addresses for CC""" | |||
# get a copy of CC list | |||
cc = split_emails(self.cc) | |||
if self.reference_doctype and self.reference_name: | |||
recipients += self.get_earlier_participants() | |||
recipients += self.get_commentors() | |||
recipients += self.get_assignees() | |||
recipients += self.get_starrers() | |||
if not cc or fetched_from_email_account: | |||
# if CC is not mentioned from the UI or is a fetched email, add follows to CC | |||
cc.append(self.get_owner_email()) | |||
cc += self.get_assignees() | |||
cc += self.get_starrers() | |||
if fetched_from_email_account and self.in_reply_to: | |||
# add sender of previous reply | |||
cc.append(frappe.db.get_value("Communication", self.in_reply_to, "sender")) | |||
if cc: | |||
# this will be used to eventually find email addresses that aren't sent to | |||
self.all_email_addresses.extend(cc) | |||
# exclude email accounts, unfollows, recipients and unsubscribes | |||
exclude = [d[0] for d in | |||
frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)] | |||
exclude += [d[0] for d in | |||
frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True) | |||
if d[0]] | |||
exclude += [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)] | |||
exclude += [parseaddr(email)[1] for email in recipients] | |||
if fetched_from_email_account: | |||
# exclude sender when pulling email | |||
exclude += [parseaddr(self.sender)[1]] | |||
# remove unsubscribed recipients | |||
unsubscribed = [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)] | |||
email_accounts = [d[0] for d in frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)] | |||
sender = parseaddr(self.sender)[1] | |||
if self.reference_doctype and self.reference_name: | |||
exclude += [d[0] for d in frappe.db.get_all("Email Unsubscribe", ["email"], | |||
{"reference_doctype": self.reference_doctype, "reference_name": self.reference_name}, as_list=True)] | |||
filtered = [] | |||
email_addresses = [] | |||
for e in list(set(recipients)): | |||
if (e=="Administrator") or ((e==self.sender) and (e not in original_recipients)) or \ | |||
(e in unsubscribed) or (e in email_accounts): | |||
continue | |||
cc = self.filter_email_list(cc, exclude) | |||
if getattr(self, "send_me_a_copy", False) and self.sender not in cc: | |||
self.all_email_addresses.append(self.sender) | |||
cc.append(self.sender) | |||
email_id = parseaddr(e)[1] | |||
return cc | |||
def filter_email_list(self, email_list, exclude): | |||
# temp variables | |||
filtered = [] | |||
email_address_list = [] | |||
if not email_id: | |||
for email in list(set(email_list)): | |||
if email in exclude: | |||
continue | |||
if email_id==sender or email_id in unsubscribed or email_id in email_accounts: | |||
email_address = (parseaddr(email)[1] or "").lower() | |||
if not email_address: | |||
continue | |||
if except_recipient and (e==self.recipients or email_id==self.recipients): | |||
# while pulling email, don't send email to current recipient | |||
if email_address in exclude: | |||
continue | |||
# make sure of case-insensitive uniqueness of email address | |||
if email_id.lower() not in email_addresses: | |||
if email_address not in email_address_list: | |||
# append the full email i.e. "Human <human@example.com>" | |||
filtered.append(e) | |||
email_addresses.append(email_id.lower()) | |||
if getattr(self, "send_me_a_copy", False): | |||
filtered.append(self.sender) | |||
filtered.append(email) | |||
email_address_list.append(email_address) | |||
return filtered | |||
def get_starrers(self): | |||
"""Return list of users who have starred this document.""" | |||
if self.reference_doctype and self.reference_name: | |||
return self.get_parent_doc().get_starred_by() | |||
else: | |||
return [] | |||
def get_earlier_participants(self): | |||
return frappe.db.sql_list(""" | |||
select distinct sender | |||
from tabCommunication where | |||
reference_doctype=%s and reference_name=%s""", | |||
(self.reference_doctype, self.reference_name)) | |||
def get_commentors(self): | |||
return frappe.db.sql_list(""" | |||
select distinct comment_by | |||
from tabComment where | |||
comment_doctype=%s and comment_docname=%s and | |||
ifnull(unsubscribed, 0)=0 and comment_by!='Administrator'""", | |||
(self.reference_doctype, self.reference_name)) | |||
return [( get_formatted_email(user) or user ) for user in self.get_parent_doc().get_starred_by()] | |||
def get_owner_email(self): | |||
owner = self.get_parent_doc().owner | |||
return get_formatted_email(owner) or owner | |||
def get_assignees(self): | |||
return [d.owner for d in frappe.db.get_all("ToDo", filters={"reference_type": self.reference_doctype, | |||
"reference_name": self.reference_name, "status": "Open"}, fields=["owner"])] | |||
return [( get_formatted_email(d.owner) or d.owner ) for d in | |||
frappe.db.get_all("ToDo", filters={ | |||
"reference_type": self.reference_doctype, | |||
"reference_name": self.reference_name, | |||
"status": "Open" | |||
}, fields=["owner"]) | |||
] | |||
def get_attach_link(self, print_format): | |||
"""Returns public link for the attachment via `templates/emails/print_link.html`.""" | |||
@@ -247,7 +326,7 @@ def on_doctype_update(): | |||
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", | |||
sender=None, recipients=None, communication_medium="Email", send_email=False, | |||
print_html=None, print_format=None, attachments='[]', ignore_doctype_permissions=False, | |||
send_me_a_copy=False): | |||
send_me_a_copy=False, cc=None): | |||
"""Make a new communication. | |||
:param doctype: Reference DocType. | |||
@@ -280,6 +359,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = | |||
"content": content, | |||
"sender": sender, | |||
"recipients": recipients, | |||
"cc": cc or None, | |||
"communication_medium": "Email", | |||
"sent_or_received": sent_or_received, | |||
"reference_doctype": doctype, | |||
@@ -291,15 +371,13 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = | |||
# if not committed, delayed task doesn't find the communication | |||
frappe.db.commit() | |||
recipients = None | |||
if send_email: | |||
comm.send_me_a_copy = send_me_a_copy | |||
recipients = comm.get_recipients() | |||
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy, recipients=recipients) | |||
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy) | |||
return { | |||
"name": comm.name, | |||
"recipients": ", ".join(recipients) if recipients else None | |||
"emails_not_sent_to": ", ".join(comm.emails_not_sent_to) if hasattr(comm, "emails_not_sent_to") else None | |||
} | |||
@frappe.whitelist() | |||
@@ -64,7 +64,7 @@ | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Title", | |||
"no_copy": 0, | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
@@ -217,7 +217,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2015-07-13 04:45:55.942795", | |||
"modified": "2015-09-11 12:19:55.121822", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Page", | |||
@@ -75,20 +75,35 @@ frappe.DataImportTool = Class.extend({ | |||
onerror: function(r) { | |||
me.onerror(r); | |||
}, | |||
start: function() { | |||
queued: function() { | |||
// async, show queued | |||
msg_dialog.clear(); | |||
msgprint(__("Import Request Queued. This may take a few moments, please be patient.")); | |||
}, | |||
running: function() { | |||
// update async status as running | |||
msg_dialog.clear(); | |||
msgprint(__("Importing...")); | |||
me.write_messages([__("Importing")]); | |||
me.has_progress = false; | |||
}, | |||
progress: function(data) { | |||
// show callback if async | |||
if(data.progress) { | |||
frappe.hide_msgprint(true); | |||
frappe.show_progress(__("Importing"), data.progress[0], data.progress[1]); | |||
me.has_progress = true; | |||
frappe.show_progress(__("Importing"), data.progress[0], | |||
data.progress[1]); | |||
} | |||
}, | |||
callback: function(attachment, r) { | |||
if(r.message.error) { | |||
me.onerror(r); | |||
} else { | |||
frappe.show_progress(__("Importing"), 1, 1); | |||
if(me.has_progress) { | |||
frappe.show_progress(__("Importing"), 1, 1); | |||
setTimeout(frappe.hide_progress, 1000); | |||
} | |||
r.messages = ["<h5 style='color:green'>" + __("Import Successful!") + "</h5>"]. | |||
concat(r.message.messages) | |||
@@ -98,6 +113,15 @@ frappe.DataImportTool = Class.extend({ | |||
} | |||
}); | |||
frappe.realtime.on("data_import_progress", function(data) { | |||
if(data.progress) { | |||
frappe.hide_msgprint(true); | |||
me.has_progress = true; | |||
frappe.show_progress(__("Importing"), data.progress[0], | |||
data.progress[1]); | |||
} | |||
}) | |||
}, | |||
write_messages: function(data) { | |||
this.page.main.find(".import-log").removeClass("hide"); | |||
@@ -16,7 +16,7 @@ from frappe.utils import cint, cstr, flt | |||
from frappe.core.page.data_import_tool.data_import_tool import get_data_keys | |||
#@frappe.async.handler | |||
@frappe.whitelist() | |||
frappe.whitelist() | |||
def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, overwrite=None, | |||
ignore_links=False, pre_process=None): | |||
"""upload data""" | |||
@@ -203,11 +203,11 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, | |||
row_idx = i + start_row | |||
doc = None | |||
frappe.publish_realtime(message = {"progress": [i, total]}) | |||
# publish task_update | |||
frappe.publish_realtime("data_import_progress", {"progress": [i, total]}, | |||
user=frappe.session.user, now=True) | |||
try: | |||
frappe.local.message_log = [] | |||
doc = get_doc(row_idx) | |||
if pre_process: | |||
pre_process(doc) | |||
@@ -243,6 +243,8 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, | |||
ret.append('Error for row (#%d) %s : %s' % (row_idx + 1, | |||
len(row)>1 and row[1] or "", err_msg)) | |||
frappe.errprint(frappe.get_traceback()) | |||
finally: | |||
frappe.local.message_log = [] | |||
if error: | |||
frappe.db.rollback() | |||
@@ -503,13 +503,6 @@ frappe.PermissionEngine = Class.extend({ | |||
get_perm: function(name) { | |||
return $.map(this.perm_list, function(d) { if(d.name==name) return d; })[0]; | |||
}, | |||
get_user_fields: function(doctype) { | |||
var user_fields = frappe.get_children("DocType", doctype, "fields", {fieldtype:"Link", options:"User"}) | |||
user_fields = user_fields.concat(frappe.get_children("DocType", doctype, "fields", | |||
{fieldtype:"Select", link_doctype:"User"})) | |||
return user_fields | |||
}, | |||
get_link_fields: function(doctype) { | |||
return frappe.get_children("DocType", doctype, "fields", | |||
{fieldtype:"Link", options:["not in", ["User", '[Select]']]}); | |||
@@ -20,7 +20,7 @@ | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Title", | |||
"no_copy": 0, | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 1, | |||
"read_only": 0, | |||
@@ -84,7 +84,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2015-09-07 15:51:26", | |||
"modified": "2015-09-11 12:20:04.912891", | |||
"modified_by": "Administrator", | |||
"module": "Desk", | |||
"name": "Note", | |||
@@ -33,6 +33,7 @@ class FormMeta(Meta): | |||
def load_assets(self): | |||
self.add_search_fields() | |||
self.add_linked_document_type() | |||
if not self.istable: | |||
self.add_linked_with() | |||
@@ -49,7 +50,7 @@ class FormMeta(Meta): | |||
d[k] = self.get(k) | |||
for i, df in enumerate(d.get("fields")): | |||
for k in ("link_doctype", "search_fields", "is_custom_field"): | |||
for k in ("search_fields", "is_custom_field", "linked_document_type"): | |||
df[k] = self.get("fields")[i].get(k) | |||
return d | |||
@@ -120,6 +121,15 @@ class FormMeta(Meta): | |||
if search_fields: | |||
df.search_fields = map(lambda sf: sf.strip(), search_fields.split(",")) | |||
def add_linked_document_type(self): | |||
for df in self.get("fields", {"fieldtype": "Link"}): | |||
if df.options: | |||
try: | |||
df.linked_document_type = frappe.get_meta(df.options).document_type | |||
except frappe.DoesNotExistError: | |||
# edge case where options="[Select]" | |||
pass | |||
def add_linked_with(self): | |||
"""add list of doctypes this doctype is 'linked' with. | |||
@@ -41,6 +41,7 @@ frappe.desk.pages.Messages = Class.extend({ | |||
}, | |||
setup_realtime: function() { | |||
var me = this; | |||
frappe.realtime.on('new_message', function(comment) { | |||
if(comment.modified_by !== user) { | |||
frappe.utils.notify(__("Message from {0}", [comment.comment_by_fullname]), comment.comment); | |||
@@ -48,16 +49,20 @@ frappe.desk.pages.Messages = Class.extend({ | |||
if (frappe.get_route()[0] === 'messages') { | |||
var current_contact = $(cur_page.page).find('[data-contact]').data('contact'); | |||
var on_broadcast_page = current_contact === user; | |||
if (current_contact == comment.owner || (on_broadcast_page && comment.broadcast)) { | |||
var $row = $('<div class="list-row"/>'); | |||
frappe.desk.pages.messages.list.data.unshift(comment); | |||
frappe.desk.pages.messages.list.render_row($row, comment); | |||
frappe.desk.pages.messages.list.parent.prepend($row); | |||
if ((current_contact == comment.owner) || (on_broadcast_page && comment.broadcast)) { | |||
me.prepend_comment(comment); | |||
} | |||
} | |||
}); | |||
}, | |||
prepend_comment: function(comment) { | |||
var $row = $('<div class="list-row"/>'); | |||
frappe.desk.pages.messages.list.data.unshift(comment); | |||
frappe.desk.pages.messages.list.render_row($row, comment); | |||
frappe.desk.pages.messages.list.$w.prepend($row); | |||
}, | |||
make_sidebar: function() { | |||
var me = this; | |||
return frappe.call({ | |||
@@ -124,7 +129,9 @@ frappe.desk.pages.Messages = Class.extend({ | |||
}, | |||
callback:function(r,rt) { | |||
textarea.val(''); | |||
me.list.run(); | |||
if (!r.exc) { | |||
me.prepend_comment(r.message); | |||
} | |||
}, | |||
btn: this | |||
}); | |||
@@ -89,6 +89,8 @@ def post(txt, contact, parenttype=None, notify=False, subject=None): | |||
else: | |||
_notify(contact, txt, subject) | |||
return d | |||
@frappe.whitelist() | |||
def delete(arg=None): | |||
frappe.get_doc("Comment", frappe.form_dict['name']).delete() | |||
@@ -10,13 +10,14 @@ from frappe.email.smtp import SMTPServer, get_outgoing_email_account | |||
from frappe.email.email_body import get_email, get_formatted_html | |||
from frappe.utils.verified_command import get_signed_params, verify_request | |||
from html2text import html2text | |||
from frappe.utils import get_url, nowdate, encode, now_datetime, add_days | |||
from frappe.utils import get_url, nowdate, encode, now_datetime, add_days, split_emails | |||
class BulkLimitCrossedError(frappe.ValidationError): pass | |||
def send(recipients=None, sender=None, subject=None, message=None, reference_doctype=None, | |||
reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, | |||
attachments=None, reply_to=None, cc=(), message_id=None, send_after=None): | |||
attachments=None, reply_to=None, cc=(), message_id=None, send_after=None, | |||
expose_recipients=False): | |||
"""Add email to sending queue (Bulk Email) | |||
:param recipients: List of recipients. | |||
@@ -39,7 +40,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc | |||
return | |||
if isinstance(recipients, basestring): | |||
recipients = recipients.split(",") | |||
recipients = split_emails(recipients) | |||
if isinstance(send_after, int): | |||
send_after = add_days(nowdate(), send_after) | |||
@@ -66,23 +67,30 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc | |||
else: | |||
unsubscribed = [] | |||
for email in filter(None, list(set(recipients))): | |||
if email not in unsubscribed: | |||
email_content = formatted | |||
email_text_context = text_content | |||
recipients = [r for r in list(set(recipients)) if r and r not in unsubscribed] | |||
if reference_doctype: | |||
unsubscribe_url = get_unsubcribed_url(reference_doctype, reference_name, email, | |||
unsubscribe_method, unsubscribe_params) | |||
for email in recipients: | |||
email_content = formatted | |||
email_text_context = text_content | |||
# add to queue | |||
email_content = add_unsubscribe_link(email_content, email, reference_doctype, | |||
reference_name, unsubscribe_url, unsubscribe_message) | |||
if reference_doctype: | |||
unsubscribe_link = get_unsubscribe_link( | |||
reference_doctype=reference_doctype, | |||
reference_name=reference_name, | |||
email=email, | |||
recipients=recipients, | |||
expose_recipients=expose_recipients, | |||
unsubscribe_method=unsubscribe_method, | |||
unsubscribe_params=unsubscribe_params, | |||
unsubscribe_message=unsubscribe_message | |||
) | |||
email_text_context += "\n" + _("This email was sent to {0}. To unsubscribe click on this link: {1}").format(email, unsubscribe_url) | |||
email_content = email_content.replace("<!--unsubscribe link here-->", unsubscribe_link.html) | |||
email_text_context += unsubscribe_link.text | |||
add(email, sender, subject, email_content, email_text_context, reference_doctype, | |||
reference_name, attachments, reply_to, cc, message_id, send_after) | |||
# add to queue | |||
add(email, sender, subject, email_content, email_text_context, reference_doctype, | |||
reference_name, attachments, reply_to, cc, message_id, send_after) | |||
def add(email, sender, subject, formatted, text_content=None, | |||
reference_doctype=None, reference_name=None, attachments=None, reply_to=None, | |||
@@ -129,18 +137,41 @@ def check_bulk_limit(recipients): | |||
throw(_("Email limit {0} crossed").format(monthly_bulk_mail_limit), | |||
BulkLimitCrossedError) | |||
def add_unsubscribe_link(message, email, reference_doctype, reference_name, unsubscribe_url, unsubscribe_message): | |||
unsubscribe_link = """<div style="padding: 7px; text-align: center; color: #8D99A6;"> | |||
{email}. <a href="{unsubscribe_url}" style="color: #8D99A6; text-decoration: underline; | |||
target="_blank">{unsubscribe_message}. | |||
</a> | |||
</div>""".format(unsubscribe_url = unsubscribe_url, | |||
email= _("This email was sent to {0}").format(email), | |||
unsubscribe_message = unsubscribe_message or _("Unsubscribe from this list")) | |||
message = message.replace("<!--unsubscribe link here-->", unsubscribe_link) | |||
return message | |||
def get_unsubscribe_link(reference_doctype, reference_name, | |||
email, recipients, expose_recipients, unsubscribe_method, unsubscribe_params, unsubscribe_message): | |||
unsubscribe_email = recipients if expose_recipients else [email] | |||
unsubscribe_email = _("This email was sent to {0}").format(", ".join(unsubscribe_email)) | |||
if not unsubscribe_message: | |||
unsubscribe_message = _("Unsubscribe from this list") | |||
unsubscribe_url = get_unsubcribed_url(reference_doctype, reference_name, email, | |||
unsubscribe_method, unsubscribe_params) | |||
html = """<div style="margin: 15px auto; padding: 0px 7px; text-align: center; color: #8d99a6;"> | |||
{email} | |||
<p style="margin: 15px auto;"> | |||
<a href="{unsubscribe_url}" style="color: #8d99a6; text-decoration: underline; | |||
target="_blank">{unsubscribe_message} | |||
</a> | |||
</p> | |||
</div>""".format( | |||
unsubscribe_url = unsubscribe_url, | |||
email=unsubscribe_email, | |||
unsubscribe_message=unsubscribe_message | |||
) | |||
text = "\n{email}\n\n{unsubscribe_message}: {unsubscribe_url}".format( | |||
email=unsubscribe_email, | |||
unsubscribe_message=unsubscribe_message, | |||
unsubscribe_url=unsubscribe_url | |||
) | |||
return frappe._dict({ | |||
"html": html, | |||
"text": text | |||
}) | |||
def get_unsubcribed_url(reference_doctype, reference_name, email, unsubscribe_method, unsubscribe_params): | |||
params = {"email": email.encode("utf-8"), | |||
@@ -137,7 +137,7 @@ class EmailAccount(Document): | |||
else: | |||
frappe.db.commit() | |||
communication.notify(attachments=communication._attachments, except_recipient=True) | |||
communication.notify(attachments=communication._attachments, fetched_from_email_account=True) | |||
if exceptions: | |||
raise Exception, frappe.as_json(exceptions) | |||
@@ -158,6 +158,7 @@ class EmailAccount(Document): | |||
"sender_full_name": email.from_real_name, | |||
"sender": email.from_email, | |||
"recipients": email.mail.get("To"), | |||
"cc": email.mail.get("CC"), | |||
"email_account": self.name, | |||
"communication_medium": "Email" | |||
}) | |||
@@ -208,6 +209,9 @@ class EmailAccount(Document): | |||
if frappe.db.exists("Communication", in_reply_to): | |||
parent = frappe.get_doc("Communication", in_reply_to) | |||
# set in_reply_to of current communication | |||
communication.in_reply_to = in_reply_to | |||
if parent.reference_name: | |||
parent = frappe.get_doc(parent.reference_doctype, | |||
parent.reference_name) | |||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals | |||
import frappe | |||
from frappe.utils.pdf import get_pdf | |||
from frappe.email.smtp import get_outgoing_email_account | |||
from frappe.utils import get_url, scrub_urls, strip, expand_relative_urls, cint | |||
from frappe.utils import get_url, scrub_urls, strip, expand_relative_urls, cint, split_emails | |||
import email.utils | |||
from markdown2 import markdown | |||
@@ -42,7 +42,7 @@ class EMail: | |||
if isinstance(recipients, basestring): | |||
recipients = recipients.replace(';', ',').replace('\n', '') | |||
recipients = recipients.split(',') | |||
recipients = split_emails(recipients) | |||
# remove null | |||
recipients = filter(None, (strip(r) for r in recipients)) | |||
@@ -238,18 +238,18 @@ def get_footer(email_account, footer=None): | |||
footer = footer or "" | |||
if email_account and email_account.footer: | |||
footer += email_account.footer | |||
footer += '<div style="margin: 15px auto;">{0}</div>'.format(email_account.footer) | |||
footer += "<!--unsubscribe link here-->" | |||
company_address = frappe.db.get_default("email_footer_address") | |||
if company_address: | |||
footer += '<div style="text-align: center; color: #8d99a6">{0}</div>'\ | |||
footer += '<div style="margin: 15px auto; text-align: center; color: #8d99a6">{0}</div>'\ | |||
.format(company_address.replace("\n", "<br>")) | |||
if not cint(frappe.db.get_default("disable_standard_email_footer")): | |||
for default_mail_footer in frappe.get_hooks("default_mail_footer"): | |||
footer += default_mail_footer | |||
footer += '<div style="margin: 15px auto;">{0}</div>'.format(default_mail_footer) | |||
return footer |
@@ -8,6 +8,7 @@ | |||
"description": "**Currency** Master", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "Setup", | |||
"fields": [ | |||
{ | |||
"allow_on_submit": 0, | |||
@@ -152,7 +153,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2015-09-07 15:51:26", | |||
"modified": "2015-09-14 03:17:04.837607", | |||
"modified_by": "Administrator", | |||
"module": "Geo", | |||
"name": "Currency", | |||
@@ -26,7 +26,7 @@ to ERPNext. | |||
""" | |||
app_icon = "octicon octicon-circuit-board" | |||
app_version = "6.2.0" | |||
app_version = "6.3.0" | |||
app_color = "orange" | |||
github_link = "https://github.com/frappe/frappe" | |||
@@ -65,8 +65,11 @@ def set_user_and_static_default_values(doc): | |||
def get_user_default_value(df, defaults, user_permissions): | |||
# don't set defaults for "User" link field using User Permissions! | |||
if df.fieldtype == "Link" and df.options != "User": | |||
# 1 - look in user permissions | |||
if user_permissions_exist(df, user_permissions) and len(user_permissions[df.options])==1: | |||
# 1 - look in user permissions only for document_type==Setup | |||
# We don't want to include permissions of transactions to be used for defaults. | |||
if (frappe.get_meta(df.options).document_type=="Setup" | |||
and user_permissions_exist(df, user_permissions) | |||
and len(user_permissions[df.options])==1): | |||
return user_permissions[df.options][0] | |||
# 2 - Look in user defaults | |||
@@ -201,56 +201,88 @@ class DatabaseQuery(object): | |||
"""build conditions from user filters""" | |||
if isinstance(filters, dict): | |||
filters = [filters] | |||
for f in filters: | |||
if isinstance(f, basestring): | |||
conditions.append(f) | |||
else: | |||
f = self.get_filter_tuple(f) | |||
tname = ('`tab' + f[0] + '`') | |||
if not tname in self.tables: | |||
self.append_table(tname) | |||
# prepare in condition | |||
if f[2] in ['in', 'not in']: | |||
opts = f[3] | |||
if not isinstance(opts, (list, tuple)): | |||
opts = f[3].split(",") | |||
opts = [frappe.db.escape(t.strip()) for t in opts] | |||
f[3] = '("{0}")'.format('", "'.join(opts)) | |||
conditions.append('ifnull({tname}.{fname}, "") {operator} {value}'.format( | |||
tname=tname, fname=f[1], operator=f[2], value=f[3])) | |||
else: | |||
df = frappe.get_meta(f[0]).get("fields", {"fieldname": f[1]}) | |||
df = df[0] if df else None | |||
if df and df.fieldtype=="Date": | |||
value, default_val = '"{0}"'.format(frappe.db.escape(getdate(f[3]).strftime("%Y-%m-%d"))), \ | |||
"'0000-00-00'" | |||
elif df and df.fieldtype=="Datetime": | |||
value, default_val = '"{0}"'.format(frappe.db.escape(get_datetime(f[3]).strftime("%Y-%m-%d %H:%M:%S.%f"))), \ | |||
"'0000-00-00 00:00:00'" | |||
elif df and df.fieldtype=="Time": | |||
value, default_val = '"{0}"'.format(frappe.db.escape(get_time(f[3]).strftime("%H:%M:%S.%f"))), \ | |||
"'00:00:00'" | |||
elif f[2] == "like" or (isinstance(f[3], basestring) and | |||
(not df or df.fieldtype not in ["Float", "Int", "Currency", "Percent", "Check"])): | |||
if f[2] == "like" and isinstance(f[3], basestring): | |||
# because "like" uses backslash (\) for escaping | |||
f[3] = f[3].replace("\\", "\\\\") | |||
value, default_val = '"{0}"'.format(frappe.db.escape(f[3])), '""' | |||
else: | |||
value, default_val = flt(f[3]), 0 | |||
conditions.append('ifnull({tname}.{fname}, {default_val}) {operator} {value}'.format( | |||
tname=tname, fname=f[1], default_val=default_val, operator=f[2], | |||
value=value)) | |||
def get_filter_tuple(self, f): | |||
conditions.append(self.prepare_filter_condition(f)) | |||
def prepare_filter_condition(self, f): | |||
"""Returns a filter condition in the format: | |||
ifnull(`tabDocType`.`fieldname`, fallback) operator "value" | |||
""" | |||
f = self.get_filter(f) | |||
tname = ('`tab' + f.doctype + '`') | |||
if not tname in self.tables: | |||
self.append_table(tname) | |||
# prepare in condition | |||
if f.operator in ('in', 'not in'): | |||
values = f.value | |||
if not isinstance(values, (list, tuple)): | |||
values = values.split(",") | |||
values = (frappe.db.escape(v.strip()) for v in values) | |||
values = '("{0}")'.format('", "'.join(values)) | |||
condition = 'ifnull({tname}.{fname}, "") {operator} {value}'.format( | |||
tname=tname, fname=f.fieldname, operator=f.operator, value=values) | |||
else: | |||
df = frappe.get_meta(f.doctype).get("fields", {"fieldname": f.fieldname}) | |||
df = df[0] if df else None | |||
if df and df.fieldtype=="Date": | |||
value = getdate(f.value).strftime("%Y-%m-%d") | |||
fallback = "'0000-00-00'" | |||
elif df and df.fieldtype=="Datetime": | |||
value = get_datetime(f.value).strftime("%Y-%m-%d %H:%M:%S.%f") | |||
fallback = "'0000-00-00 00:00:00'" | |||
elif df and df.fieldtype=="Time": | |||
value = get_time(f.value).strftime("%H:%M:%S.%f") | |||
fallback = "'00:00:00'" | |||
elif f.operator == "like" or (isinstance(f.value, basestring) and | |||
(not df or df.fieldtype not in ["Float", "Int", "Currency", "Percent", "Check"])): | |||
value = f.value | |||
fallback = '""' | |||
if f.operator == "like" and isinstance(value, basestring): | |||
# because "like" uses backslash (\) for escaping | |||
value = value.replace("\\", "\\\\") | |||
else: | |||
value = flt(f.value) | |||
fallback = 0 | |||
# put it inside double quotes | |||
if isinstance(value, basestring): | |||
value = '"{0}"'.format(frappe.db.escape(value)) | |||
condition = 'ifnull({tname}.{fname}, {fallback}) {operator} {value}'.format( | |||
tname=tname, fname=f.fieldname, fallback=fallback, operator=f.operator, | |||
value=value) | |||
# replace % with %% to prevent python format string error | |||
return condition.replace("%", "%%") | |||
def get_filter(self, f): | |||
"""Returns a _dict like | |||
{ | |||
"doctype": "DocType", | |||
"fieldname": "fieldname", | |||
"operator": "=", | |||
"value": "value" | |||
} | |||
""" | |||
if isinstance(f, dict): | |||
key, value = f.items()[0] | |||
f = self.make_filter_tuple(key, value) | |||
@@ -262,9 +294,14 @@ class DatabaseQuery(object): | |||
f = (self.doctype, f[0], f[1], f[2]) | |||
elif len(f) != 4: | |||
frappe.throw("Filter must have 4 values (doctype, fieldname, condition, value): " + str(f)) | |||
frappe.throw("Filter must have 4 values (doctype, fieldname, operator, value): " + str(f)) | |||
return list(f) | |||
return frappe._dict({ | |||
"doctype": f[0], | |||
"fieldname": f[1], | |||
"operator": f[2], | |||
"value": f[3] | |||
}) | |||
def build_match_conditions(self, as_condition=True): | |||
"""add match conditions if applicable""" | |||
@@ -313,7 +350,8 @@ class DatabaseQuery(object): | |||
conditions = "({conditions}) or ({shared_condition})".format( | |||
conditions=conditions, shared_condition=self.get_share_condition()) | |||
return conditions | |||
# replace % with %% to prevent python format string error | |||
return conditions.replace("%", "%%") | |||
else: | |||
return self.match_filters | |||
@@ -85,7 +85,7 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa | |||
if doc: | |||
try: | |||
doc.notify_modified() | |||
doc.notify_update() | |||
insert_feed(doc) | |||
except ImportError: | |||
pass | |||
@@ -167,7 +167,7 @@ class Document(BaseDocument): | |||
self.check_permission("create") | |||
self._set_defaults() | |||
self._set_docstatus_user_and_timestamp() | |||
self.set_docstatus_user_and_timestamp() | |||
self.check_if_latest() | |||
self.run_method("before_insert") | |||
self.set_new_name() | |||
@@ -218,7 +218,7 @@ class Document(BaseDocument): | |||
self.check_permission("write", "save") | |||
self._set_docstatus_user_and_timestamp() | |||
self.set_docstatus_user_and_timestamp() | |||
self.check_if_latest() | |||
self.set_parent_in_children() | |||
self.validate_higher_perm_levels() | |||
@@ -298,7 +298,7 @@ class Document(BaseDocument): | |||
if self.doctype in frappe.db.value_cache: | |||
del frappe.db.value_cache[self.doctype] | |||
def _set_docstatus_user_and_timestamp(self): | |||
def set_docstatus_user_and_timestamp(self): | |||
self._original_modified = self.modified | |||
self.modified = now() | |||
self.modified_by = frappe.session.user | |||
@@ -599,11 +599,11 @@ class Document(BaseDocument): | |||
self.run_method("on_update_after_submit") | |||
frappe.cache().hdel("last_modified", self.doctype) | |||
self.notify_modified() | |||
self.notify_update() | |||
self.latest = None | |||
def notify_modified(self): | |||
def notify_update(self): | |||
"""Publish realtime that the current document is modified""" | |||
frappe.publish_realtime("doc_update", {"modified": self.modified, "doctype": self.doctype, "name": self.name}, | |||
doctype=self.doctype, docname=self.name) | |||
@@ -281,6 +281,72 @@ | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "css_section", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "css", | |||
"fieldtype": "Code", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Custom CSS", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"fieldname": "custom_html_help", | |||
"fieldtype": "HTML", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Custom HTML Help", | |||
"no_copy": 0, | |||
"options": "<h3>Custom CSS Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>All field groups (label + value) are set attributes <code>data-fieldtype</code> and <code>data-fieldname</code></li>\n<li>All values are given class <code>value</code></li>\n<li>All Section Breaks are given class <code>section-break</code></li>\n<li>All Column Breaks are given class <code>column-break</code></li>\n</ol>\n\n<h4>Examples</h4>\n\n<p>1. Left align integers</p>\n\n<pre><code>[data-fieldtype=\"Int\"] .value { text-left: left; }</code></pre>\n\n<p>1. Add border to sections except the last section</p>\n\n<pre><code>.section-break { padding: 30px 0px; border-bottom: 1px solid #eee; }\n.section-break:last-child { padding-bottom: 0px; border-bottom: 0px; }</code></pre>\n", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
@@ -381,7 +447,7 @@ | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2015-07-15 08:01:06.284031", | |||
"modified": "2015-09-09 05:46:11.025962", | |||
"modified_by": "Administrator", | |||
"module": "Print", | |||
"name": "Print Format", | |||
@@ -12,7 +12,7 @@ class TestPrintFormat(unittest.TestCase): | |||
def test_print_user(self, style=None): | |||
print_html = frappe.get_print("User", "Administrator", style=style) | |||
self.assertTrue("<label>First Name</label>" in print_html) | |||
self.assertTrue(re.findall('<div class="col-xs-7[\s]*">[\s]*Administrator[\s]*</div>', print_html)) | |||
self.assertTrue(re.findall('<div class="col-xs-7[^"]*">[\s]*Administrator[\s]*</div>', print_html)) | |||
return print_html | |||
def test_print_user_standard(self): | |||
@@ -313,12 +313,6 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ | |||
if(this.only_input || this.df.label==this._label) | |||
return; | |||
// var icon = frappe.ui.form.fieldtype_icons[this.df.fieldtype]; | |||
// if(this.df.fieldtype==="Link") { | |||
// icon = frappe.boot.doctype_icons[this.df.options]; | |||
// } else if(this.df.link_doctype) { | |||
// icon = frappe.boot.doctype_icons[this.df.link_doctype]; | |||
// } | |||
var icon = ""; | |||
this.label_span.innerHTML = (icon ? '<i class="'+icon+'"></i> ' : "") + | |||
__(this.df.label) || " "; | |||
@@ -78,13 +78,24 @@ frappe.ui.form.ScriptManager = Class.extend({ | |||
var tmp = eval(cs); | |||
} | |||
// setup add fetch | |||
$.each(this.frm.fields, function(i, field) { | |||
var df = field.df; | |||
if((df.fieldtype==="Read Only" || df.read_only==1) && df.options && df.options.indexOf(".")!=-1) { | |||
function setup_add_fetch(df) { | |||
if(df.fieldname==="size") | |||
console.log(df.fieldname); | |||
if((df.fieldtype==="Read Only" || df.read_only==1) | |||
&& df.options && df.options.indexOf(".")!=-1) { | |||
var parts = df.options.split("."); | |||
me.frm.add_fetch(parts[0], parts[1], df.fieldname); | |||
} | |||
} | |||
// setup add fetch | |||
$.each(this.frm.fields, function(i, field) { | |||
setup_add_fetch(field.df); | |||
if(field.df.fieldtype==="Table") { | |||
$.each(frappe.meta.get_docfields(field.df.options, me.frm.docname), function(i, df) { | |||
setup_add_fetch(df); | |||
}); | |||
} | |||
}); | |||
// css | |||
@@ -193,6 +193,29 @@ $.extend(frappe.user, { | |||
is_report_manager: function() { | |||
return frappe.user.has_role(['Administrator', 'System Manager', 'Report Manager']); | |||
}, | |||
get_formatted_email: function(email) { | |||
var fullname = frappe.user.full_name(email); | |||
if (!fullname) { | |||
return email; | |||
} else { | |||
// to quote or to not | |||
var quote = ''; | |||
// only if these special characters are found | |||
// why? To make the output same as that in python! | |||
if (fullname.search(/[\[\]\\()<>@,:;".]/) !== -1) { | |||
quote = '"'; | |||
} | |||
return repl('%(quote)s%(fullname)s%(quote)s <%(email)s>', { | |||
fullname: fullname, | |||
email: email, | |||
quote: quote | |||
}); | |||
} | |||
} | |||
}); | |||
frappe.session_alive = true; | |||
@@ -43,6 +43,9 @@ frappe.utils = { | |||
}); | |||
return out.join(newline); | |||
}, | |||
escape_html: function(txt) { | |||
return $("<div></div>").text(txt || "").html(); | |||
}, | |||
is_url: function(txt) { | |||
return txt.toLowerCase().substr(0,7)=='http://' | |||
|| txt.toLowerCase().substr(0,8)=='https://' | |||
@@ -82,8 +82,10 @@ $.extend(frappe.model, { | |||
// don't set defaults for "User" link field using User Permissions! | |||
if (df.fieldtype==="Link" && df.options!=="User") { | |||
// 1 - look in user permissions | |||
if (has_user_permissions && user_permissions[df.options].length===1) { | |||
// 1 - look in user permissions for document_type=="Setup". | |||
// We don't want to include permissions of transactions to be used for defaults. | |||
if (df.linked_document_type==="Setup" | |||
&& has_user_permissions && user_permissions[df.options].length===1) { | |||
return user_permissions[df.options][0]; | |||
} | |||
@@ -154,10 +154,11 @@ $.extend(frappe.model, { | |||
}, | |||
new_comment: function(comment) { | |||
if (frappe.model.docinfo[comment.comment_doctype] | |||
&& frappe.model.docinfo[comment.comment_doctype][comment.comment_docname]) { | |||
var comments = frappe.model.docinfo[comment.comment_doctype][comment.comment_docname].comments; | |||
var reference_doctype = comment.comment_doctype || comment.reference_doctype; | |||
var reference_name = comment.comment_docname || comment.reference_name; | |||
if (frappe.model.docinfo[reference_doctype] && frappe.model.docinfo[reference_doctype][reference_name]) { | |||
var comments = frappe.model.docinfo[reference_doctype][reference_name].comments; | |||
var comment_exists = false; | |||
for (var i=0, l=comments.length; i<l; i++) { | |||
if (comments[i].name==comment.name) { | |||
@@ -165,12 +166,13 @@ $.extend(frappe.model, { | |||
break; | |||
} | |||
} | |||
if (!comment_exists) { | |||
frappe.model.docinfo[comment.comment_doctype][comment.comment_docname].comments = comments.concat([comment]); | |||
frappe.model.docinfo[reference_doctype][reference_name].comments = comments.concat([comment]); | |||
} | |||
} | |||
if (cur_frm.doctype === comment.comment_doctype && cur_frm.docname === comment.comment_docname) { | |||
cur_frm.comments.refresh(); | |||
if (cur_frm.doctype === reference_doctype && cur_frm.docname === reference_name) { | |||
cur_frm.comments.refresh(); | |||
} | |||
}, | |||
@@ -32,6 +32,10 @@ frappe.call = function(opts) { | |||
if(data.task_id) { | |||
// async call, subscribe | |||
frappe.socket.subscribe(data.task_id, opts); | |||
if(opts.queued) { | |||
opts.queued(data); | |||
} | |||
} | |||
else if (opts.callback) { | |||
// ajax | |||
@@ -73,13 +73,7 @@ frappe.socket = { | |||
}, | |||
setup_listeners: function() { | |||
frappe.socket.socket.on('task_status_change', function(data) { | |||
if(data.status==="Running") { | |||
frappe.socket.process_response(data, "running"); | |||
} else { | |||
// failed or finished | |||
frappe.socket.process_response(data, "callback"); | |||
// delete frappe.socket.open_tasks[data.task_id]; | |||
} | |||
frappe.socket.process_response(data, data.status.toLowerCase()); | |||
}); | |||
frappe.socket.socket.on('task_progress', function(data) { | |||
frappe.socket.process_response(data, "progress"); | |||
@@ -110,6 +104,11 @@ frappe.socket = { | |||
if(data) { | |||
var opts = frappe.socket.open_tasks[data.task_id]; | |||
if(opts[method]) opts[method](data); | |||
// "callback" is std frappe term | |||
if(method==="success") { | |||
if(opts.callback) opts.callback(data); | |||
} | |||
} | |||
// always | |||
@@ -127,5 +126,7 @@ frappe.socket = { | |||
frappe.provide("frappe.realtime"); | |||
frappe.realtime.on = function(event, callback) { | |||
frappe.socket.socket.on(event, callback); | |||
if(frappe.socket.socket) { | |||
frappe.socket.socket.on(event, callback); | |||
} | |||
} |
@@ -102,6 +102,9 @@ frappe.msgprint = function(msg, title) { | |||
}); | |||
msg_dialog.msg_area = $('<div class="msgprint">') | |||
.appendTo(msg_dialog.body); | |||
msg_dialog.clear = function() { | |||
msg_dialog.msg_area.empty(); | |||
} | |||
} | |||
if(msg.search(/<br>|<p>|<li>/)==-1) | |||
@@ -175,6 +178,13 @@ frappe.show_progress = function(title, count, total) { | |||
dialog.progress_bar.css({"width": cint(flt(count) * 100 / total) + "%" }); | |||
} | |||
frappe.hide_progress = function() { | |||
if(frappe.cur_progress) { | |||
frappe.cur_progress.hide(); | |||
frappe.cur_progress = null; | |||
} | |||
} | |||
// Floating Message | |||
function show_alert(txt, seconds) { | |||
if(!$('#dialog-container').length) { | |||
@@ -2,12 +2,17 @@ | |||
// MIT License. See license.txt | |||
frappe.ui.is_starred = function(doc) { | |||
var starred = frappe.ui.get_starred_by(doc); | |||
return starred.indexOf(user)===-1 ? false : true; | |||
} | |||
frappe.ui.get_starred_by = function(doc) { | |||
var starred = doc._starred_by; | |||
if(starred) { | |||
starred = JSON.parse(starred); | |||
return starred.indexOf(user)===-1 ? false : true; | |||
} | |||
return false; | |||
return starred || []; | |||
} | |||
frappe.ui.toggle_star = function($btn, doctype, name) { | |||
@@ -81,7 +81,7 @@ frappe.upload = { | |||
if(opts.start) { | |||
opts.start(); | |||
} | |||
return frappe.call({ | |||
ajax_args = { | |||
"method": "uploadfile", | |||
args: args, | |||
callback: function(r) { | |||
@@ -96,23 +96,18 @@ frappe.upload = { | |||
opts.callback(attachment, r); | |||
$(document).trigger("upload_complete", attachment); | |||
}, | |||
progress: function(data) { | |||
if(opts.progress) { | |||
opts.progress(data); | |||
} | |||
}, | |||
always: function(data) { | |||
if(opts.always) { | |||
opts.always(data); | |||
} | |||
}, | |||
error: function(r) { | |||
// if no onerror, assume callback will handle errors | |||
opts.onerror ? opts.onerror(r) : opts.callback(null, null, r); | |||
return; | |||
}, | |||
btn: opts.btn | |||
} | |||
} | |||
// copy handlers etc from opts | |||
$.each(['queued', 'running', "progress", "always", "btn"], function(i, key) { | |||
if(opts[key]) ajax_args[key] = opts[key]; | |||
}); | |||
return frappe.call(ajax_args); | |||
} | |||
} | |||
@@ -14,41 +14,7 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
this.dialog = new frappe.ui.Dialog({ | |||
title: __("Add Reply") + ": " + (this.subject || ""), | |||
no_submit_on_enter: true, | |||
fields: [ | |||
{label:__("To"), fieldtype:"Data", reqd: 1, fieldname:"recipients"}, | |||
{fieldtype: "Section Break"}, | |||
{fieldtype: "Column Break"}, | |||
{label:__("Subject"), fieldtype:"Data", reqd: 1, | |||
fieldname:"subject"}, | |||
{fieldtype: "Column Break"}, | |||
{label:__("Standard Reply"), fieldtype:"Link", options:"Standard Reply", | |||
fieldname:"standard_reply"}, | |||
{fieldtype: "Section Break"}, | |||
{label:__("Message"), fieldtype:"Text Editor", reqd: 1, | |||
fieldname:"content"}, | |||
{fieldtype: "Section Break"}, | |||
{fieldtype: "Column Break"}, | |||
{label:__("Send As Email"), fieldtype:"Check", | |||
fieldname:"send_email"}, | |||
{label:__("Send me a copy"), fieldtype:"Check", | |||
fieldname:"send_me_a_copy"}, | |||
{label:__("Communication Medium"), fieldtype:"Select", | |||
options: ["Phone", "Chat", "Email", "SMS", "Visit", "Other"], | |||
fieldname:"communication_medium"}, | |||
{label:__("Sent or Received"), fieldtype:"Select", | |||
options: ["Received", "Sent"], | |||
fieldname:"sent_or_received"}, | |||
{label:__("Attach Document Print"), fieldtype:"Check", | |||
fieldname:"attach_document_print"}, | |||
{label:__("Select Print Format"), fieldtype:"Select", | |||
fieldname:"select_print_format"}, | |||
{fieldtype: "Column Break"}, | |||
{label:__("Select Attachments"), fieldtype:"HTML", | |||
fieldname:"select_attachments"} | |||
], | |||
fields: this.get_fields(), | |||
primary_action_label: "Send", | |||
primary_action: function() { | |||
me.send_action(); | |||
@@ -79,6 +45,100 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
this.dialog.show(); | |||
}, | |||
get_fields: function() { | |||
var cc_fields = this.get_cc_fields(); | |||
var fields_before_cc = [ | |||
{fieldtype: "Section Break"}, | |||
{label:__("To"), fieldtype:"Data", reqd: 1, fieldname:"recipients"}, | |||
{fieldtype: "Section Break", collapsible: 1, label: "CC & Standard Reply"}, | |||
{label:__("CC"), fieldtype:"Data", fieldname:"cc"}, | |||
]; | |||
var fields_after_cc = [ | |||
{label:__("Standard Reply"), fieldtype:"Link", options:"Standard Reply", | |||
fieldname:"standard_reply"}, | |||
{fieldtype: "Section Break"}, | |||
{label:__("Subject"), fieldtype:"Data", reqd: 1, | |||
fieldname:"subject"}, | |||
{fieldtype: "Section Break"}, | |||
{label:__("Message"), fieldtype:"Text Editor", reqd: 1, | |||
fieldname:"content"}, | |||
{fieldtype: "Section Break"}, | |||
{fieldtype: "Column Break"}, | |||
{label:__("Send As Email"), fieldtype:"Check", | |||
fieldname:"send_email"}, | |||
{label:__("Send me a copy"), fieldtype:"Check", | |||
fieldname:"send_me_a_copy"}, | |||
{label:__("Communication Medium"), fieldtype:"Select", | |||
options: ["Phone", "Chat", "Email", "SMS", "Visit", "Other"], | |||
fieldname:"communication_medium"}, | |||
{label:__("Sent or Received"), fieldtype:"Select", | |||
options: ["Received", "Sent"], | |||
fieldname:"sent_or_received"}, | |||
{label:__("Attach Document Print"), fieldtype:"Check", | |||
fieldname:"attach_document_print"}, | |||
{label:__("Select Print Format"), fieldtype:"Select", | |||
fieldname:"select_print_format"}, | |||
{fieldtype: "Column Break"}, | |||
{label:__("Select Attachments"), fieldtype:"HTML", | |||
fieldname:"select_attachments"} | |||
]; | |||
return fields_before_cc.concat(cc_fields).concat(fields_after_cc); | |||
}, | |||
get_cc_fields: function() { | |||
var cc = [ [this.frm.doc.owner, 1] ]; | |||
var starred_by = frappe.ui.get_starred_by(this.frm.doc); | |||
if (starred_by) { | |||
for ( var i=0, l=starred_by.length; i<l; i++ ) { | |||
cc.push( [starred_by[i], 1] ); | |||
} | |||
} | |||
var assignments = this.frm.get_docinfo().assignments; | |||
if (assignments) { | |||
for ( var i=0, l=assignments.length; i<l; i++ ) { | |||
cc.push( [assignments[i].owner, 1] ); | |||
} | |||
} | |||
var comments = this.frm.get_docinfo().comments; | |||
if (comments) { | |||
for ( var i=0, l=comments.length; i<l; i++ ) { | |||
cc.push( [comments[i].comment_by, 0] ); | |||
} | |||
} | |||
var added = []; | |||
var cc_fields = []; | |||
for ( var i=0, l=cc.length; i<l; i++ ) { | |||
var email = cc[i][0]; | |||
var default_value = cc[i][1]; | |||
if ( !email || added.indexOf(email)!==-1 || email.indexOf("@")===-1 ) { | |||
continue; | |||
} | |||
// for deduplication | |||
added.push(email); | |||
email = frappe.user.get_formatted_email(email); | |||
cc_fields.push({ | |||
"label": frappe.utils.escape_html(email), | |||
"fieldtype": "Check", | |||
"fieldname": email, | |||
"is_cc_checkbox": 1, | |||
"default": default_value | |||
}); | |||
} | |||
return cc_fields; | |||
}, | |||
prepare: function() { | |||
this.setup_subject_and_recipients(); | |||
this.setup_print(); | |||
@@ -284,10 +344,10 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
}, | |||
send_action: function() { | |||
var me = this, | |||
form_values = me.dialog.get_values(), | |||
btn = me.dialog.get_primary_btn(); | |||
var me = this; | |||
var btn = me.dialog.get_primary_btn(); | |||
var form_values = this.get_values(); | |||
if(!form_values) return; | |||
var selected_attachments = $.map($(me.dialog.wrapper) | |||
@@ -312,6 +372,26 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
} | |||
}, | |||
get_values: function() { | |||
var form_values = this.dialog.get_values(); | |||
// cc | |||
for ( var i=0, l=this.dialog.fields.length; i < l; i++ ) { | |||
var df = this.dialog.fields[i]; | |||
if ( df.is_cc_checkbox ) { | |||
// concat in cc | |||
if ( form_values[df.fieldname] ) { | |||
form_values.cc = ( form_values.cc ? (form_values.cc + ", ") : "" ) + df.fieldname; | |||
} | |||
delete form_values[df.fieldname]; | |||
} | |||
} | |||
return form_values; | |||
}, | |||
send_email: function(btn, form_values, selected_attachments, print_html, print_format) { | |||
var me = this; | |||
@@ -334,6 +414,7 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
method:"frappe.core.doctype.communication.communication.make", | |||
args: { | |||
recipients: form_values.recipients, | |||
cc: form_values.cc, | |||
subject: form_values.subject, | |||
content: form_values.content, | |||
doctype: me.doc.doctype, | |||
@@ -349,8 +430,11 @@ frappe.views.CommunicationComposer = Class.extend({ | |||
btn: btn, | |||
callback: function(r) { | |||
if(!r.exc) { | |||
if(form_values.send_email && r.message["recipients"]) | |||
msgprint(__("Email sent to {0}", [r.message["recipients"]])); | |||
if(form_values.send_email && r.message["emails_not_sent_to"]) { | |||
msgprint( __("Email not sent to {0}", | |||
[ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]) ); | |||
} | |||
me.dialog.hide(); | |||
if (cur_frm) { | |||
@@ -62,7 +62,6 @@ def sync_worker(app, worker, prefix=''): | |||
'queue': queue | |||
}, reply=True, destination=[worker]) | |||
def get_active_queues(app, worker): | |||
active_queues = app.control.inspect().active_queues() | |||
if not (active_queues and active_queues.get(worker)): | |||
@@ -137,16 +136,18 @@ def pull_from_email_account(site, email_account): | |||
frappe.destroy() | |||
@celery_task(bind=True) | |||
def run_async_task(self, site, user, cmd, form_dict): | |||
def run_async_task(self, site=None, user=None, cmd=None, form_dict=None, hijack_std=False): | |||
ret = {} | |||
frappe.init(site) | |||
frappe.connect() | |||
original_stdout, original_stderr = sys.stdout, sys.stderr | |||
sys.stdout, sys.stderr = get_std_streams(self.request.id) | |||
frappe.local.stdout, frappe.local.stderr = sys.stdout, sys.stderr | |||
frappe.local.task_id = self.request.id | |||
if hijack_std: | |||
original_stdout, original_stderr = sys.stdout, sys.stderr | |||
sys.stdout, sys.stderr = get_std_streams(self.request.id) | |||
frappe.local.stdout, frappe.local.stderr = sys.stdout, sys.stderr | |||
try: | |||
set_task_status(self.request.id, "Running") | |||
frappe.db.commit() | |||
@@ -163,27 +164,29 @@ def run_async_task(self, site, user, cmd, form_dict): | |||
ret['status_code'] = http_status_code | |||
frappe.errprint(frappe.get_traceback()) | |||
frappe.utils.response.make_logs() | |||
set_task_status(self.request.id, "Failed", response=ret) | |||
set_task_status(self.request.id, "Error", response=ret) | |||
task_logger.error('Exception in running {}: {}'.format(cmd, ret['exc'])) | |||
else: | |||
set_task_status(self.request.id, "Finished", response=ret) | |||
set_task_status(self.request.id, "Success", response=ret) | |||
if not frappe.flags.in_test: | |||
frappe.db.commit() | |||
finally: | |||
sys.stdout.write('\n' + END_LINE) | |||
sys.stderr.write('\n' + END_LINE) | |||
if not frappe.flags.in_test: | |||
frappe.destroy() | |||
sys.stdout.close() | |||
sys.stderr.close() | |||
if hijack_std: | |||
sys.stdout.write('\n' + END_LINE) | |||
sys.stderr.write('\n' + END_LINE) | |||
sys.stdout.close() | |||
sys.stderr.close() | |||
sys.stdout, sys.stderr = original_stdout, original_stderr | |||
sys.stdout, sys.stderr = original_stdout, original_stderr | |||
return ret | |||
@celery_task() | |||
def sendmail(site, communication_name, print_html=None, print_format=None, attachments=None, recipients=None, except_recipient=False): | |||
def sendmail(site, communication_name, print_html=None, print_format=None, attachments=None, | |||
recipients=None, cc=None): | |||
try: | |||
frappe.connect(site=site) | |||
@@ -191,7 +194,8 @@ def sendmail(site, communication_name, print_html=None, print_format=None, attac | |||
for i in xrange(3): | |||
try: | |||
communication = frappe.get_doc("Communication", communication_name) | |||
communication._notify(print_html=print_html, print_format=print_format, attachments=attachments, recipients=recipients, except_recipient=except_recipient) | |||
communication._notify(print_html=print_html, print_format=print_format, attachments=attachments, | |||
recipients=recipients, cc=cc) | |||
except MySQLdb.OperationalError, e: | |||
# deadlock, try again | |||
if e.args[0]==1213: | |||
@@ -19,7 +19,7 @@ | |||
{%- endfor -%} | |||
</head> | |||
<body> | |||
<div class="centered splash"> | |||
<div class="centered splash" style="max-width: 360px;"> | |||
<img src="{{ splash_image or "/assets/frappe/images/splash.png" }}"> | |||
</div> | |||
<div class="offcanvas-container"> | |||
@@ -38,7 +38,9 @@ | |||
</div> | |||
<!-- hack! load background image asap, before desktop is rendered --> | |||
{% if background_image %} | |||
<img src="{{ background_image }}" style="height: 1px; width: 1px; margin-bottom: -1px;"> | |||
{% endif %} | |||
<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script> | |||
@@ -5,7 +5,10 @@ | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||
<title>{{ title }}</title> | |||
<meta name="generator" content="frappe"> | |||
<link type="text/css" rel="stylesheet" href="/assets/frappe/css/bootstrap.css"> | |||
<link type="text/css" rel="stylesheet" | |||
href="/assets/frappe/css/bootstrap.css"> | |||
<link type="text/css" rel="stylesheet" | |||
href="/assets/frappe/css/font-awesome.css"> | |||
<style> | |||
{{ css }} | |||
</style> | |||
@@ -18,9 +18,6 @@ base_template_path = "templates/pages/print.html" | |||
standard_format = "templates/print_formats/standard.html" | |||
def get_context(context): | |||
if not frappe.form_dict.format: | |||
frappe.form_dict.format = standard_format | |||
if not frappe.form_dict.doctype or not frappe.form_dict.name: | |||
return { | |||
"body": """<h1>Error</h1> | |||
@@ -31,14 +28,30 @@ def get_context(context): | |||
doc = frappe.get_doc(frappe.form_dict.doctype, frappe.form_dict.name) | |||
meta = frappe.get_meta(doc.doctype) | |||
print_format = get_print_format_doc() | |||
return { | |||
"body": get_html(doc, print_format = frappe.form_dict.format, | |||
meta=meta, trigger_print = frappe.form_dict.trigger_print, no_letterhead=frappe.form_dict.no_letterhead), | |||
"css": get_print_style(frappe.form_dict.style, frappe.form_dict.format), | |||
"body": get_html(doc, print_format = print_format, | |||
meta=meta, trigger_print = frappe.form_dict.trigger_print, | |||
no_letterhead=frappe.form_dict.no_letterhead), | |||
"css": get_print_style(frappe.form_dict.style, print_format), | |||
"comment": frappe.session.user, | |||
"title": doc.get(meta.title_field) if meta.title_field else doc.name | |||
} | |||
def get_print_format_doc(print_format_name=None): | |||
"""Returns print format document""" | |||
if not print_format_name: | |||
print_format_name = frappe.form_dict.format or "Standard" | |||
if not print_format_name: | |||
print_format_name = "Standard" | |||
if print_format_name == "Standard": | |||
return None | |||
else: | |||
return frappe.get_doc("Print Format", print_format_name) | |||
def get_html(doc, name=None, print_format=None, meta=None, | |||
no_letterhead=None, trigger_print=False): | |||
@@ -71,11 +84,7 @@ def get_html(doc, name=None, print_format=None, meta=None, | |||
format_data, format_data_map = [], {} | |||
# determine template | |||
if print_format in ("Standard", standard_format): | |||
template = "standard" | |||
else: | |||
print_format = frappe.get_doc("Print Format", print_format) | |||
if print_format: | |||
if print_format.standard=="Yes" or print_format.custom_format: | |||
template = jenv.from_string(get_print_format(doc.doctype, | |||
print_format)) | |||
@@ -97,8 +106,12 @@ def get_html(doc, name=None, print_format=None, meta=None, | |||
# fallback | |||
template = "standard" | |||
else: | |||
template = "standard" | |||
if template == "standard": | |||
template = jenv.get_template("templates/print_formats/standard.html") | |||
template = jenv.get_template(standard_format) | |||
args = { | |||
"doc": doc, | |||
@@ -119,6 +132,7 @@ def get_html(doc, name=None, print_format=None, meta=None, | |||
@frappe.whitelist() | |||
def get_html_and_style(doc, name=None, print_format=None, meta=None, | |||
no_letterhead=None, trigger_print=False): | |||
print_format = get_print_format_doc(print_format) | |||
return { | |||
"html": get_html(doc, name=name, print_format=print_format, meta=meta, | |||
no_letterhead=no_letterhead, trigger_print=trigger_print), | |||
@@ -200,6 +214,10 @@ def make_layout(doc, meta, format_data=None): | |||
df.print_hide = 0 | |||
if df.fieldtype=="Section Break" or page==[]: | |||
if len(page) > 1 and not any(page[-1]): | |||
# truncate prev section if empty | |||
del page[-1] | |||
page.append([]) | |||
if df.fieldtype=="Column Break" or (page[-1]==[] and df.fieldtype!="Section Break"): | |||
@@ -232,9 +250,6 @@ def make_layout(doc, meta, format_data=None): | |||
df.end = None | |||
page[-1][-1].append(df) | |||
# filter empty sections | |||
layout = [filter(lambda s: any(filter(lambda c: any(c), s)), pg) for pg in layout] | |||
return layout | |||
def is_visible(df, doc): | |||
@@ -256,6 +271,9 @@ def has_value(df, doc): | |||
elif isinstance(value, basestring) and not strip_html(value).strip(): | |||
return False | |||
elif isinstance(value, list) and not len(value): | |||
return False | |||
return True | |||
def get_print_style(style=None, print_format=None, for_legacy=False): | |||
@@ -284,6 +302,9 @@ def get_print_style(style=None, print_format=None, for_legacy=False): | |||
# prepend css with at_import | |||
css = at_import + css | |||
if print_format and print_format.css: | |||
css += "\n\n" + print_format.css | |||
return css | |||
def get_font(print_settings, print_format=None, for_legacy=False): | |||
@@ -292,8 +313,7 @@ def get_font(print_settings, print_format=None, for_legacy=False): | |||
return default | |||
font = None | |||
if print_format and print_format not in ("Standard", standard_format): | |||
print_format = frappe.get_doc("Print Format", print_format) | |||
if print_format: | |||
if print_format.font and print_format.font!="Default": | |||
font = '{0}, sans-serif'.format(print_format.font) | |||
@@ -25,7 +25,7 @@ def get_context(context): | |||
for route, data in get_generator_routes().iteritems(): | |||
links.append({ | |||
"loc": urllib.basejoin(host, urllib.quote(route.encode("utf-8"))), | |||
"loc": urllib.basejoin(host, urllib.quote((route or "").encode("utf-8"))), | |||
"lastmod": get_datetime(data.get("modified")).strftime("%Y-%m-%d") | |||
}) | |||
@@ -5,9 +5,9 @@ | |||
<div class="page-break"> | |||
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead) }} | |||
{% for section in page %} | |||
<div class="row"> | |||
<div class="row section-break"> | |||
{% for column in section %} | |||
<div class="col-xs-{{ (12 / section|len)|int }}"> | |||
<div class="col-xs-{{ (12 / section|len)|int }} column-break"> | |||
{% for df in column %} | |||
{{ render_field(df, doc) }} | |||
{% endfor %} | |||
@@ -20,13 +20,13 @@ | |||
{%- if data -%} | |||
{%- set visible_columns = get_visible_columns(doc.get(df.fieldname), | |||
table_meta, df) -%} | |||
<div> | |||
<div {{ fieldmeta(df) }}> | |||
<table class="table table-bordered table-condensed"> | |||
<thead> | |||
<tr> | |||
<th style="width: 40px">Sr</th> | |||
<th style="width: 40px" class="table-sr">Sr</th> | |||
{% for tdf in visible_columns %} | |||
<th style="width: {{ get_width(tdf) }}px;" class="{{ get_align_class(tdf.fieldtype) }}"> | |||
<th style="width: {{ get_width(tdf) }}px;" class="{{ get_align_class(tdf.fieldtype) }}" {{ fieldmeta(df) }}> | |||
{{ _(tdf.label) }}</th> | |||
{% endfor %} | |||
</tr> | |||
@@ -34,10 +34,10 @@ | |||
<tbody> | |||
{% for d in data %} | |||
<tr> | |||
<td>{{ d.idx }}</td> | |||
<td class="table-sr">{{ d.idx }}</td> | |||
{% for tdf in visible_columns %} | |||
<td class="{{ get_align_class(tdf.fieldtype) }}"> | |||
<div>{{ print_value(tdf, d, doc) }}</div></td> | |||
<td class="{{ get_align_class(tdf.fieldtype) }}" {{ fieldmeta(df) }}> | |||
<div class="value">{{ print_value(tdf, d, doc) }}</div></td> | |||
{% endfor %} | |||
</tr> | |||
{% endfor %} | |||
@@ -48,15 +48,19 @@ | |||
{%- endif -%} | |||
{%- endmacro -%} | |||
{% macro fieldmeta(df) -%} | |||
data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}" | |||
{%- endmacro %} | |||
{%- macro render_field_with_label(df, doc) -%} | |||
<div class="row"> | |||
<div class="row" {{ fieldmeta(df) }}> | |||
<div class="col-xs-5 text-right"> | |||
{% if df.fieldtype not in ("Image","HTML") and | |||
doc.get(df.fieldname) != None %} | |||
<label>{{ _(df.label) }}</label> | |||
{% endif %} | |||
</div> | |||
<div class="col-xs-7 {{ get_align_class(df.fieldtype) }}"> | |||
<div class="col-xs-7 {{ get_align_class(df.fieldtype) }} value"> | |||
{% if doc.get(df.fieldname) != None -%} | |||
{{ print_value(df, doc) }}{% endif %} | |||
</div> | |||
@@ -65,10 +69,10 @@ | |||
{%- macro render_text_field(df, doc) -%} | |||
{%- if doc.get(df.fieldname) != None -%} | |||
<div style="padding: 10px 0px"> | |||
<div style="padding: 10px 0px" {{ fieldmeta(df) }}> | |||
{%- if df.fieldtype in ("Text", "Code") %}<label>{{ _(df.label) }}</label>{%- endif %} | |||
{%- if df.fieldtype=="Code" %} | |||
<pre>{{ doc.get(df.fieldname) }}</pre> | |||
<pre class="value">{{ doc.get(df.fieldname) }}</pre> | |||
{% else -%} | |||
{{ doc.get_formatted(df.fieldname, parent_doc or doc) }} | |||
{% endif -%} | |||
@@ -98,7 +102,7 @@ | |||
{%- endmacro %} | |||
{% macro get_align_class(fieldtype) %} | |||
{%- if fieldtype in ("Int", "Check", "Float", "Currency") -%}{{ "text-right" }} | |||
{%- if fieldtype in ("Int", "Float", "Currency") -%}{{ "text-right" }} | |||
{%- else -%}{{ "" }} | |||
{%- endif -%} | |||
{% endmacro %} | |||
@@ -11,7 +11,7 @@ from frappe.tasks import run_async_task | |||
class TestAsync(unittest.TestCase): | |||
def test_response(self): | |||
result = run_async_task.delay(frappe.local.site, 'Administrator', 'async_ping', | |||
frappe._dict()) | |||
result = run_async_task.delay(site=frappe.local.site, user='Administrator', cmd='async_ping', | |||
form_dict=frappe._dict()) | |||
result = result.get() | |||
self.assertEquals(result.get("message"), "pong") |
@@ -46,7 +46,7 @@ DocType: Feed,Color,Farbe | |||
DocType: Workflow State,indent-right,Einzug rechts | |||
sites/assets/js/desk.min.js +846,Web Link,Weblink | |||
apps/frappe/frappe/core/page/data_import_tool/data_import_main.html +33,"Recommended bulk editing records via import, or understanding the import format.",Empfohlene Groß Bearbeiten von Datensätzen über die Einfuhr oder das Verständnis der Importformat. | |||
apps/frappe/frappe/core/page/permission_manager/permission_manager_help.html +36,"Apart from System Manager, roles with Set User Permissions right can set permissions for other users for that Document Type.",Neben dem System-Manager-Rollen mit Set Benutzerberechtigungen können mit der rechten Berechtigungen für andere Benutzer für diesen Dokumenttyp eingestellt. | |||
apps/frappe/frappe/core/page/permission_manager/permission_manager_help.html +36,"Apart from System Manager, roles with Set User Permissions right can set permissions for other users for that Document Type.",Neben dem System-Manager können Rollen mit der Erlaubnis Benutzer anzulegen Berechtigungen für andere Nutzer für diesen Dokumententyp setzen. | |||
DocType: Company History,Company History,Unternehmensgeschichte | |||
DocType: Workflow State,volume-up,Lautstärke erhöhen | |||
apps/frappe/frappe/core/page/data_import_tool/importer.py +43,Only allowed {0} rows in one import,Nur darf {0} Zeilen in einer Import- | |||
@@ -109,7 +109,7 @@ DocType: System Settings,Run scheduled jobs only if checked,"Führen Sie geplant | |||
DocType: User,"Enter default value fields (keys) and values. If you add multiple values for a field, the first one will be picked. These defaults are also used to set ""match"" permission rules. To see list of fields, go to ""Customize Form"".","Geben Sie Standardwert Felder (Schlüssel) und Werte. Wenn Sie mehrere Werte für ein Feld hinzufügen, wird der erste, abgeholt werden. Diese Standardwerte werden auch zur "match" Berechtigungsregeln festgelegt. Zur Liste der Felder zu sehen, gehen Sie auf "Customize Form"." | |||
DocType: Customize Form Field,"Print Width of the field, if the field is a column in a table","Druckbreite des Feldes, wenn das Feld eine Spalte aus einer Tabelle ist" | |||
DocType: Workflow State,headphones,Kopfhörer | |||
DocType: Bulk Email,Bulk Email records.,Spam-Aufzeichnungen | |||
DocType: Bulk Email,Bulk Email records.,Massen-Email-Aufzeichnungen | |||
DocType: Email Account,e.g. replies@yourcomany.com. All replies will come to this inbox.,zB replies@yourcomany.com. Alle Antworten werden zu diesem Posteingang zu kommen. | |||
apps/frappe/frappe/templates/includes/login/login.js +32,Valid email and name required,Gültige E-Mail und Name erforderlich | |||
DocType: DocType,Hide Heading,Überschrift ausblenden | |||
@@ -146,7 +146,7 @@ DocType: Website Settings,Add a banner to the site. (small banners are usually g | |||
DocType: Website Settings,Set Banner from Image,Banner von Bild einrichten | |||
DocType: Print Format,Verdana,Verdana | |||
apps/frappe/frappe/core/doctype/user/user.py +203,User {0} cannot be deleted,Benutzer {0} kann nicht gelöscht werden | |||
sites/assets/js/desk.min.js +265,Another transaction is blocking this one. Please try again in a few seconds.,Eine weitere Transaktion blockiert diese. Bitte versuchen Sie es in ein paar Sekunden. | |||
sites/assets/js/desk.min.js +265,Another transaction is blocking this one. Please try again in a few seconds.,Eine andere Transaktion blockiert die aktuelle. Bitte versuchen Sie es in ein paar Sekunden noch einmal. | |||
DocType: Property Setter,Field Name,Feldname | |||
sites/assets/js/desk.min.js +771,or,oder | |||
sites/assets/js/desk.min.js +925,module name...,Modulnamen ... | |||
@@ -210,14 +210,14 @@ sites/assets/js/desk.min.js +921,"document type..., e.g. customer","Dokumenttyp | |||
DocType: Country,Country Name,Ländername | |||
DocType: About Us Team Member,About Us Team Member,"""Über uns"" Teammitglieder" | |||
apps/frappe/frappe/core/page/permission_manager/permission_manager_help.html +5,"Permissions are set on Roles and Document Types (called DocTypes) by setting rights like Read, Write, Create, Delete, Submit, Cancel, Amend, Report, Import, Export, Print, Email and Set User Permissions.","Berechtigungen werden auf Rollen und Dokumenttypen (genannt DocTypes ) , indem Sie Rechte wie Lesen, Schreiben, Erstellen, Löschen , Senden , Abbrechen Amend , Bericht, Import, Export, Drucken , E-Mail und Set- Benutzerberechtigungen festlegen." | |||
apps/frappe/frappe/core/page/user_permissions/user_permissions.js +19,"Apart from Role based Permission Rules, you can apply User Permissions based on DocTypes.","Neben Rollenbasierte Berechtigungsregeln , können Sie Benutzerberechtigungen auf der Grundlage DocTypes gelten ." | |||
apps/frappe/frappe/core/page/user_permissions/user_permissions.js +19,"Apart from Role based Permission Rules, you can apply User Permissions based on DocTypes.",Neben rollenbasierten Berechtigungsregeln können Sie Benutzerberechtigungen auf der Grundlage von DocTypes einsetzen. | |||
apps/frappe/frappe/core/page/user_permissions/user_permissions.js +23,"These permissions will apply for all transactions where the permitted record is linked. For example, if Company C is added to User Permissions of user X, user X will only be able to see transactions that has company C as a linked value.","Diese Berechtigungen werden für alle Transaktionen, bei denen die zulässige Datensatz verknüpft anzuwenden." | |||
DocType: Property Setter,ID (name) of the entity whose property is to be set,"ID (Name) der Einheit, deren Eigenschaft festgelegt werden muss" | |||
DocType: Website Settings,Website Theme Image Link,Website Theme Bild Link | |||
DocType: Website Settings,Sidebar Items,Sidebar Artikel | |||
DocType: Workflow State,exclamation-sign,Ausrufezeichen | |||
DocType: Website Theme,Hide Sidebar,Sidebar ausblenden | |||
sites/assets/js/list.min.js +100,Gantt,Gantt | |||
sites/assets/js/list.min.js +100,Gantt,Gantt-Diagramm | |||
DocType: About Us Settings,Introduce your company to the website visitor.,Präsentieren Sie Ihr Unternehmen dem Besucher der Website. | |||
apps/frappe/frappe/print/doctype/print_format/print_format.js +14,Please duplicate this to make changes,Bitte vervielfältigen Sie diese Änderungen vornehmen | |||
apps/frappe/frappe/utils/pdf.py +35,PDF generation failed because of broken image links,"PDF-Generierung ist fehlgeschlagen, weil der gebrochenen Bild Links" | |||
@@ -250,10 +250,10 @@ apps/frappe/frappe/core/page/data_import_tool/data_import_tool.py +15,Parent Tab | |||
apps/frappe/frappe/website/doctype/website_settings/website_settings.py +38,{0} in row {1} cannot have both URL and child items,{0} in Zeile {1} kann nicht sowohl die URL als auch Unterpunkte haben | |||
apps/frappe/frappe/utils/nestedset.py +194,Root {0} cannot be deleted,Wurzel {0} kann nicht gelöscht werden | |||
apps/frappe/frappe/website/doctype/blog_post/blog_post.py +162,No comments yet,Noch keine Kommentare | |||
apps/frappe/frappe/print/page/print_format_builder/print_format_builder.js +120,Both DocType and Name required,Sowohl DocType und Name erforderlich | |||
apps/frappe/frappe/print/page/print_format_builder/print_format_builder.js +120,Both DocType and Name required,DocType und Name sind beide erforderlich | |||
apps/frappe/frappe/model/document.py +424,Cannot change docstatus from 1 to 0,DocStatus kann nicht geändert werden 1-0 | |||
DocType: Workflow Transition,Defines actions on states and the next step and allowed roles.,"Definiert Aktionen bei bestimmten Status, den nächsten Schritt und erlaubte Rollen." | |||
apps/frappe/frappe/core/page/permission_manager/permission_manager_help.html +11,"As a best practice, do not assign the same set of permission rule to different Roles. Instead, set multiple Roles to the same User.","Als bewährte Methode , nicht den gleichen Satz von Berechtigungs Vorschrift auf unterschiedliche Rollen zuzuweisen. Stattdessen legen mehrere Rollen auf den gleichen Benutzer ." | |||
apps/frappe/frappe/core/page/permission_manager/permission_manager_help.html +11,"As a best practice, do not assign the same set of permission rule to different Roles. Instead, set multiple Roles to the same User.","Ein bewährtes Verfahren ist es, den gleichen Satz von Berechtigungen nicht unterschiedlichen Rollen zuzuweisen. Legen Sie stattdessen mehrere Rollen für den gleichen Benutzer an." | |||
DocType: Web Form,Message to be displayed on successful completion,Nachricht an den erfolgreichen Abschluss angezeigt | |||
DocType: Website Settings,Footer Items,Inhalte der Fußzeile | |||
sites/assets/js/desk.min.js +407,Menu,Menü | |||
@@ -296,7 +296,7 @@ sites/assets/js/editor.min.js +103,Bold (Ctrl/Cmd+B),Fett (Strg / Cmd + B) | |||
apps/frappe/frappe/core/page/user_permissions/user_permissions.js +140,Upload and Sync,Upload und Sync | |||
sites/assets/js/form.min.js +280,Shared with {0},Mit Gemeinschaftsbad {0} | |||
DocType: DocType,Single Types have only one record no tables associated. Values are stored in tabSingles,Einzelne Arten haben nur ein Datensatz keine Tabellen verbunden. Die Werte werden in tabSingles gespeichert | |||
DocType: Bulk Email,Bulk Email,Massenmail | |||
DocType: Bulk Email,Bulk Email,Massen-Email | |||
DocType: Workflow,Rules defining transition of state in the workflow.,"Regeln, die den Übergang des Status in den Workflow definieren." | |||
DocType: DocField,Index,Index | |||
apps/frappe/frappe/custom/doctype/custom_field/custom_field.js +55,Option 1,Option 1 | |||
@@ -356,7 +356,7 @@ DocType: Workflow State,circle-arrow-right,Kreis-Pfeil-nach-rechts | |||
DocType: Scheduler Log,Method,Methode | |||
DocType: Report,Script Report,Skriptbericht | |||
DocType: About Us Settings,Company Introduction,Vorstellung des Unternehmens | |||
DocType: DocPerm,Apply User Permissions,Anwenden von Benutzerberechtigungen | |||
DocType: DocPerm,Apply User Permissions,Benutzerberechtigungen anwenden | |||
DocType: User,Modules HTML,Module HTML | |||
sites/assets/js/desk.min.js +484,Missing Values Required,Angaben zu erforderlichen Werten fehlen | |||
sites/assets/js/list.min.js +107,{0} is not set,{0} ist nicht gesetzt | |||
@@ -412,7 +412,7 @@ DocType: DocType,Hide Toolbar,Symbolleiste ausblenden | |||
DocType: Email Account,SMTP Settings for outgoing emails,SMTP-Einstellungen für ausgehende E-Mails | |||
apps/frappe/frappe/core/page/data_import_tool/data_import_tool.js +125,Import Failed,Import fehlgeschlagen | |||
apps/frappe/frappe/templates/emails/password_update.html +3,Your password has been updated. Here is your new password,Ihr Passwort wurde aktualisiert. Hier ist Ihr neues Passwort | |||
DocType: Email Account,Auto Reply Message,Automatische Antwortnachricht | |||
DocType: Email Account,Auto Reply Message,Automatische Rückantwort | |||
DocType: Email Alert,Condition,Zustand | |||
sites/assets/js/desk.min.js +924,Open a module or tool,Öffnen Sie ein Modul oder Werkzeug | |||
DocType: Module Def,App Name,App-Name | |||
@@ -473,7 +473,7 @@ DocType: System Settings,yyyy-mm-dd,JJJJ-MM-TT | |||
apps/frappe/frappe/email/doctype/email_account/email_account.py +37,Login Id is required,Login-ID wird benötigt | |||
DocType: Website Slideshow,Website Slideshow,Webseite Diaschau | |||
DocType: Website Settings,"Link that is the website home page. Standard Links (index, login, products, blog, about, contact)","Link, ist die Homepage der Website . Standard- Verbindungen (Index , Login, Artikel , Blog , über, Kontakt)" | |||
sites/assets/js/editor.min.js +105,Bullet list,Bullet list | |||
sites/assets/js/editor.min.js +105,Bullet list,Liste der Gliederungspunkte | |||
DocType: Website Settings,Banner Image,Banner Bild | |||
DocType: Custom Field,Custom Field,Benutzerdefiniertes Feld | |||
apps/frappe/frappe/email/doctype/email_alert/email_alert.py +13,Please specify which date field must be checked,Bitte geben Sie die Datumsfeld überprüft werden muss | |||
@@ -587,7 +587,7 @@ DocType: Website Theme,Link to your Bootstrap theme,Link zu Ihrer Bootstrap Them | |||
sites/assets/js/desk.min.js +593,Edit as {0},Anpassen als {0} | |||
apps/frappe/frappe/core/page/user_permissions/user_permissions.js +194,No User Restrictions found.,Keine Benutzer Einschränkungen gefunden. | |||
apps/frappe/frappe/email/doctype/email_account/email_account_list.js +10,Default Inbox,Standard-Posteingang | |||
sites/assets/js/desk.min.js +607,Make a new,erstellen Sie einen neuen Eintrag für: | |||
sites/assets/js/desk.min.js +607,Make a new,Neu anlegen | |||
DocType: Print Settings,PDF Page Size,PDF-Seitengröße | |||
sites/assets/js/desk.min.js +947,About,Information | |||
apps/frappe/frappe/core/page/data_import_tool/exporter.py +66,"For updating, you can update only selective columns.",Für die Aktualisierung können Sie nur selektive Spalten aktualisieren. | |||
@@ -598,7 +598,7 @@ DocType: Workflow State,list-alt,Liste-Alt | |||
apps/frappe/frappe/templates/pages/update-password.html +63,Password Updated,Aktualisiert Passwort | |||
apps/frappe/frappe/core/page/modules_setup/modules_setup.js +18,"Select modules to be shown (based on permission). If hidden, they will be hidden for all users.","Wählen Sie Module gezeigt werden (basierend auf Genehmigung) . Wenn ausgeblendet , werden sie für alle Benutzer ausgeblendet werden." | |||
apps/frappe/frappe/core/page/user_permissions/user_permissions.js +149,Permissions Updated,Berechtigungen aktualisiert | |||
apps/frappe/frappe/email/doctype/email_account/email_account.py +45,Append To is mandatory for incoming mails,Anhängen ist für eingehende E-Mails | |||
apps/frappe/frappe/email/doctype/email_account/email_account.py +45,Append To is mandatory for incoming mails,Anhängen an ist für eingehende E-Mails zwingend | |||
apps/frappe/frappe/core/doctype/doctype/doctype.py +239,Options requried for Link or Table type field {0} in row {1},Optionen für Link -oder Tabellenfeldrequried {0} in Zeile {1} | |||
DocType: Report,Query Report,Abfragebericht | |||
DocType: Communication,On,Auf | |||
@@ -649,7 +649,7 @@ apps/frappe/frappe/core/page/permission_manager/permission_manager_help.html +14 | |||
apps/frappe/frappe/desk/doctype/todo/todo_list.js +7,To Do,Aufgaben | |||
sites/assets/js/editor.min.js +94,Paragraph,Absatz | |||
apps/frappe/frappe/core/page/user_permissions/user_permissions.js +133,Any existing permission will be deleted / overwritten.,Alle vorhandenen Genehmigung wird gelöscht / überschrieben werden. | |||
apps/frappe/frappe/desk/doctype/todo/todo.py +17,Assigned to {0}: {1},Zugeordnet {0}: {1} | |||
apps/frappe/frappe/desk/doctype/todo/todo.py +17,Assigned to {0}: {1},Zugewiesen zu {0}: {1} | |||
DocType: DocField,Percent,Prozent | |||
DocType: Workflow State,book,Buch | |||
DocType: Website Settings,Landing Page,Landingpage | |||
@@ -776,7 +776,7 @@ apps/frappe/frappe/core/doctype/user/user.py +81,New password emailed,Neues Pass | |||
apps/frappe/frappe/auth.py +209,Login not allowed at this time,Anmelden zu diesem Zeitpunkt nicht erlaubt | |||
DocType: DocType,Permissions Settings,Berechtigungseinstellungen | |||
sites/assets/js/desk.min.js +931,{0} List,{0} Liste | |||
apps/frappe/frappe/desk/form/assign_to.py +39,Already in user's To Do list,Bereits im Benutzer- To -Do-Liste | |||
apps/frappe/frappe/desk/form/assign_to.py +39,Already in user's To Do list,Bereits in der ToDo-Liste des Benutzers | |||
DocType: Email Account,Enable Outgoing,Abgehende aktivieren | |||
DocType: System Settings,Email Footer Address,Email Address Footer | |||
DocType: DocField,Text,Text | |||
@@ -858,8 +858,8 @@ DocType: Event,Participants,Teilnehmer | |||
DocType: Bulk Email,Reference DocName,Referenz-Dokumentenname | |||
DocType: Web Form,Success Message,Erfolgsmeldung | |||
DocType: DocType,User Cannot Search,Benutzer kann nicht suchen | |||
DocType: DocPerm,Apply this rule if the User is the Owner,"Regel anwenden, wenn der Nutzer den Besitzer" | |||
apps/frappe/frappe/desk/page/activity/activity.js +47,Build Report,Bauen Bericht | |||
DocType: DocPerm,Apply this rule if the User is the Owner,"Diese Regel anwenden, wenn der Nutzer gleich dem Besitzer ist" | |||
apps/frappe/frappe/desk/page/activity/activity.js +47,Build Report,Bericht erstellen | |||
apps/frappe/frappe/model/rename_doc.py +91,"{0} {1} does not exist, select a new target to merge",{0} {1} existiert nicht. Bitte ein anderes Ziel wählen | |||
apps/frappe/frappe/core/page/user_permissions/user_permissions.py +66,Cannot set permission for DocType: {0} and Name: {1},Kann Erlaubnis nicht gesetzt für DocType : {0} und Name: {1} | |||
DocType: Comment,Comment Doctype,Dokumententyp Kommentar | |||
@@ -892,7 +892,7 @@ DocType: Event,Every Week,Wöchentlich | |||
DocType: Custom Field,Is Mandatory Field,Ist Pflichtfeld | |||
DocType: User,Website User,Webseitenbenutzer | |||
DocType: Website Script,Script to attach to all web pages.,"Skript, das allen Webseiten hinzugefügt wird." | |||
DocType: Web Form,Allow Multiple,Allow Multiple | |||
DocType: Web Form,Allow Multiple,mehrere zulassen | |||
sites/assets/js/form.min.js +291,Assign,Zuweisen | |||
apps/frappe/frappe/config/setup.py +93,Import / Export Data from .csv files.,Import / Export von Daten aus .CSV -Dateien. | |||
DocType: Workflow State,Icon will appear on the button,Symbol wird auf der Schaltfläche angezeigt | |||
@@ -912,7 +912,7 @@ apps/frappe/frappe/model/delete_doc.py +153,Cannot delete or cancel because {0} | |||
DocType: Website Settings,Twitter Share,Twitter-Freigabe | |||
DocType: Workflow State,Workflow State,Workflow-Status | |||
apps/frappe/frappe/templates/pages/me.html +1,My Account,Ihr Konto | |||
DocType: ToDo,Allocated To,Zugeordnet | |||
DocType: ToDo,Allocated To,Zugewiesen zu | |||
apps/frappe/frappe/templates/emails/password_reset.html +4,Please click on the following link to set your new password,Bitte klicken Sie auf den folgenden Link um ein neues Passwort festzulegen | |||
DocType: Email Alert,Days After,Tage nach | |||
DocType: Contact Us Settings,Settings for Contact Us Page,Einstellungen für die Kontaktseite | |||
@@ -933,7 +933,7 @@ DocType: DocType,In Dialog,Im Dialog | |||
apps/frappe/frappe/custom/doctype/customize_form/customize_form.js +142,Help,Hilfe | |||
DocType: User,Login Before,Anmelden vor | |||
DocType: Web Page,Insert Style,Stil einfügen | |||
apps/frappe/frappe/desk/page/applications/applications.js +94,Application Installer,Application Installer | |||
apps/frappe/frappe/desk/page/applications/applications.js +94,Application Installer,Einrichtungsprogramm für Anwendung | |||
apps/frappe/frappe/core/page/user_permissions/user_permissions.js +246,Is,Ist | |||
DocType: Workflow State,info-sign,Info-Zeichen | |||
DocType: Currency,"How should this currency be formatted? If not set, will use system defaults","Wie soll diese Währung formatiert werden? Wenn nicht festgelegt, werden die Systemstands verwendet" | |||
@@ -967,7 +967,7 @@ apps/frappe/frappe/desk/moduleview.py +57,Module Not Found,Modul nicht gefunden | |||
DocType: User,Location,Lage | |||
,Permitted Documents For User,Zulässige Dokumente Nach Benutzer | |||
apps/frappe/frappe/core/doctype/docshare/docshare.py +40,"You need to have ""Share"" permission","Sie müssen ""Teilen"" Berechtigung haben" | |||
sites/assets/js/form.min.js +213,Bulk Edit {0},Massenbearbeitung {0} | |||
sites/assets/js/form.min.js +213,Bulk Edit {0},Massen-Bearbeitung {0} | |||
DocType: Email Alert Recipient,Email Alert Recipient,E-Mail-Benachrichtigungsempfänger | |||
DocType: About Us Settings,Settings for the About Us Page,"Einstellungen für die Seite ""Über uns""" | |||
apps/frappe/frappe/core/page/data_import_tool/data_import_main.html +5,Select Type of Document to Download,Wählen Sie die Art des Dokumentes zum Download | |||
@@ -995,10 +995,10 @@ sites/assets/js/desk.min.js +874,Expand,erweitern | |||
DocType: Workflow State,align-right,rechtsbündig ausrichten | |||
DocType: Page,Roles,Rollen | |||
DocType: System Settings,Session Expiry,Sitzungsende | |||
DocType: Workflow State,ban-circle,ban-circle | |||
DocType: Workflow State,ban-circle,Bannkreis | |||
DocType: Event,Desk,Schreibtisch | |||
apps/frappe/frappe/core/doctype/report/report.js +8,Write a SELECT query. Note result is not paged (all data is sent in one go).,Eine SELECT-Abfrage schreiben. Das Ergebnis wird nicht ausgelagert (alle Daten werden in einem Rutsch gesendet). | |||
DocType: Email Account,Attachment Limit (MB),Limit Anhangsgröße (MB) | |||
DocType: Email Account,Attachment Limit (MB),Beschränkung der Größe des Anhangs (MB) | |||
sites/assets/js/form.min.js +199,Ctrl + Down,Ctrl + Down | |||
DocType: User,User Defaults,Profil Defaults | |||
DocType: Workflow State,chevron-down,Chevron-nach-unten | |||
@@ -1007,8 +1007,8 @@ DocType: Web Page,Enable Comments,Kommentare aktivieren | |||
DocType: User,Restrict user from this IP address only. Multiple IP addresses can be added by separating with commas. Also accepts partial IP addresses like (111.111.111),Nur Benutzer von dieser IP-Adresse beschränken. Mehrere IP-Adressen können durch Trennung mit Komma hinzugefügt werden. Auch übernimmt teilweise IP-Adressen wie (111.111.111) | |||
DocType: Website Theme,Google Font (Heading),Google Font (Heading) | |||
sites/assets/js/desk.min.js +931,Find {0} in {1},Finden Sie in {0} {1} | |||
DocType: Email Account,"Append as communication against this DocType (must have fields, ""Status"", ""Subject"")","Hängen Sie die Kommunikation gegen diese DocType (müssen Felder, ""Status"" haben, ""Subject"")" | |||
DocType: DocType,Allow Import via Data Import Tool,Import über Datenimport-Tool zulassen | |||
DocType: Email Account,"Append as communication against this DocType (must have fields, ""Status"", ""Subject"")","Als Kommunikation mit diesem Dokumententyp anhängen (muss die Felder, ""Status"" und ""Betreff"" beinhalten)" | |||
DocType: DocType,Allow Import via Data Import Tool,Import über Datenimport-Werkzeug zulassen | |||
apps/frappe/frappe/model/base_document.py +432,Not allowed to change {0} after submission,"Nicht erlaubt, um nach Vorlage ändern {0}" | |||
DocType: Comment,Comment Type,Kommentarart | |||
apps/frappe/frappe/config/setup.py +8,Users,Benutzer | |||
@@ -1024,14 +1024,14 @@ sites/assets/js/desk.min.js +965,Send As Email,Senden als E-Mail | |||
DocType: Website Theme,Link Color,Linkfarbe | |||
apps/frappe/frappe/core/doctype/user/user.py +47,User {0} cannot be disabled,Benutzer {0} kann nicht deaktiviert werden | |||
apps/frappe/frappe/core/doctype/user/user.py +479,"Dear System Manager,","Lieber System Manager," | |||
sites/assets/js/form.min.js +182,Amending,Zur Änderung der | |||
sites/assets/js/form.min.js +182,Amending,Änderung | |||
sites/assets/js/desk.min.js +598,Dialog box to select a Link Value,Dialog-Box um einen verknpften Wert auszuwählen | |||
DocType: Contact Us Settings,Send enquiries to this email address,Senden Sie Anfragen an diese E-Mail -Adresse | |||
DocType: Letter Head,Letter Head Name,Briefkopf Name | |||
apps/frappe/frappe/config/website.py +23,User editable form on Website.,Benutzer bearbeitbare Formular auf der Website. | |||
DocType: Workflow State,file,Datei | |||
apps/frappe/frappe/model/rename_doc.py +97,You need write permission to rename,Zum Umbenennen benötigen Sie eine Schreibberechtigung | |||
apps/frappe/frappe/core/page/permission_manager/permission_manager.js +433,Apply Rule,Apply Rule | |||
apps/frappe/frappe/core/page/permission_manager/permission_manager.js +433,Apply Rule,Regel anwenden | |||
DocType: User,Karma,Karma | |||
DocType: DocField,Table,Tabelle | |||
DocType: File Data,File Size,Dateigröße | |||
@@ -1045,7 +1045,7 @@ DocType: Web Form,Allow Edit,Bearbeitung zulassen | |||
apps/frappe/frappe/workflow/doctype/workflow/workflow.py +64,Cannot change state of Cancelled Document. Transition row {0},Zustand der Cancelled Dokument kann nicht geändert werden . | |||
DocType: Workflow,"Rules for how states are transitions, like next state and which role is allowed to change state etc.","Regeln dafür, wann Status zu Übergängen zählen und welche Rolle Status ändern darf usw." | |||
apps/frappe/frappe/desk/form/save.py +21,{0} {1} already exists,{0} {1} existiert bereits | |||
apps/frappe/frappe/email/doctype/email_account/email_account.py +61,Append To can be one of {0},"Hängen, um einen von {0}" | |||
apps/frappe/frappe/email/doctype/email_account/email_account.py +61,Append To can be one of {0},Anhängen an kann ein Wert aus {0} sein | |||
DocType: User,Github Username,github Benutzername | |||
DocType: Web Page,Title / headline of your page,Titel/Überschrift Ihrer Seite | |||
DocType: DocType,Plugin,Plugin | |||
@@ -1066,7 +1066,7 @@ sites/assets/js/desk.min.js +579,Advanced Search,Erweiterte Suche | |||
apps/frappe/frappe/core/doctype/user/user.py +390,Password reset instructions have been sent to your email,Kennworts Hinweise wurden an die E-Mail verschickt worden | |||
apps/frappe/frappe/config/setup.py +214,Manage cloud backups on Dropbox,Verwalten Cloud -Backups auf Dropbox | |||
DocType: Workflow,States,Länder | |||
DocType: Email Alert,Attach Print,Attach Print | |||
DocType: Email Alert,Attach Print,Ausdruck anhängen | |||
apps/frappe/frappe/core/page/permission_manager/permission_manager_help.html +18,"When you Amend a document after Cancel and save it, it will get a new number that is a version of the old number.","Wenn Sie ein Dokument ändern, nachdem Sie auf Abbrechen und Speichern geklickt haben, wird es eine neue Nummer erhalten, die eine Version der alten Nummer ist, bekommen." | |||
apps/frappe/frappe/core/doctype/doctype/doctype.py +36,{0} not allowed in name,{0} nicht im Namen erlaubt | |||
DocType: Workflow State,circle-arrow-left,Kreis-Pfeil-nach-links | |||
@@ -1094,7 +1094,7 @@ DocType: Workflow State,folder-close,geschlossener Ordner | |||
DocType: Email Alert Recipient,Optional: Alert will only be sent if value is a valid email id.,"Optional: Alarm wird nur gesendet werden, wenn der Wert eine gültige E-Mail-Adresse." | |||
apps/frappe/frappe/model/rename_doc.py +100,{0} not allowed to be renamed,{0} darf nicht umbenannt werden | |||
DocType: Custom Script,Custom Script,Benutzerdefiniertes Skript | |||
sites/assets/js/desk.min.js +622,Assigned To,zugewiesen an | |||
sites/assets/js/desk.min.js +622,Assigned To,Zugewiesen zu | |||
apps/frappe/frappe/core/doctype/user/user.py +166,Verify Your Account,Überprüfen Sie Ihr Konto | |||
DocType: Workflow Transition,Action,Aktion | |||
apps/frappe/frappe/core/page/data_import_tool/exporter.py +231,Info:,Info: | |||
@@ -1157,7 +1157,7 @@ DocType: DocType,Name Case,Name in Großbuchstaben | |||
sites/assets/js/form.min.js +278,Shared with everyone,Mit allen geteilt | |||
apps/frappe/frappe/model/base_document.py +327,Data missing in table,In der Tabelle fehlen Daten | |||
DocType: Web Form,Success URL,Erfolgs-URL | |||
DocType: Email Account,Append To,Anhängen Um | |||
DocType: Email Account,Append To,Anhängen an | |||
DocType: Workflow Document State,Only Allow Edit For,Änderungen nur zulassen für | |||
DocType: DocType,DocType,Dokumententyp | |||
apps/frappe/frappe/core/doctype/user/user.py +393,User {0} does not exist,Benutzer {0} existiert nicht | |||
@@ -1180,7 +1180,7 @@ DocType: DocField,Set non-standard precision for a Float or Currency field,Stell | |||
DocType: Email Account,Ignore attachments over this size,Ignorieren Anhänge über dieser Größe | |||
apps/frappe/frappe/database.py +217,Too many writes in one request. Please send smaller requests,Zu viele schreibt in einer Anfrage. Bitte senden Sie Anfragen kleiner | |||
DocType: Workflow State,arrow-up,Pfeil-nach-oben | |||
DocType: DocField,Allow on Submit,Beim Absenden zulassen | |||
DocType: DocField,Allow on Submit,Beim Versenden zulassen | |||
apps/frappe/frappe/core/page/desktop/desktop.js +48,All Applications,Alle Anwendungen | |||
DocType: Web Page,Add code as <script>,Hinzufügen von Code als <script> | |||
apps/frappe/frappe/core/page/data_import_tool/data_import_main.html +7,Select Type,Typ auswählen | |||
@@ -1217,7 +1217,7 @@ DocType: Workflow State,star-empty,Stern-leer | |||
DocType: Workflow State,ok,OK | |||
DocType: User,These values will be automatically updated in transactions and also will be useful to restrict permissions for this user on transactions containing these values.,"Diese Werte werden automatisch bei Transaktionen aktualisiert. Außerdem sind sie nützlich, um die Berechtigungen dieses Benutzers bei Transaktionen mit diesen Werten zu beschränken." | |||
apps/frappe/frappe/desk/page/applications/application_row.html +15,Publisher,Herausgeber | |||
sites/assets/js/desk.min.js +846,Browse,Blättern | |||
sites/assets/js/desk.min.js +846,Browse,Durchsuchen | |||
apps/frappe/frappe/templates/includes/comments/comments.py +52,View it in your browser,Sehen Sie es in Ihrem Browser | |||
apps/frappe/frappe/templates/pages/update-password.html +1,Reset Password,Kennwort zurücksetzen | |||
DocType: Workflow State,hand-left,Pfeil-nach-links | |||
@@ -1247,7 +1247,7 @@ apps/frappe/frappe/core/page/permission_manager/permission_manager.js +474,Did n | |||
DocType: ToDo,ToDo,Aufgabenliste | |||
DocType: DocField,No Copy,Keine Kopie | |||
DocType: Workflow State,qrcode,qrcode | |||
DocType: Web Form,Breadcrumbs,Paniermehl | |||
DocType: Web Form,Breadcrumbs,Brösel | |||
apps/frappe/frappe/core/doctype/doctype/doctype.py +373,If Owner,Wenn Besitzer | |||
apps/frappe/frappe/website/doctype/web_form/web_form.py +28,You need to be logged in to access this {0}.,"Sie müssen angemeldet sein, um diesen Zugang {0}." | |||
apps/frappe/frappe/templates/includes/comments/comments.py +50,{0} by {1},{0} von {1} | |||
@@ -1282,7 +1282,7 @@ apps/frappe/frappe/print/page/print_format_builder/print_format_builder.js +102, | |||
DocType: Module Def,Module Def,Modul-Def | |||
sites/assets/js/form.min.js +199,Done,Erledigt | |||
sites/assets/js/form.min.js +294,Reply,Antworten | |||
DocType: Communication,By,Nach | |||
DocType: Communication,By,mit | |||
DocType: Email Account,SMTP Server,SMTP-Server | |||
DocType: Print Format,Print Format Help,Print-Format Hilfe | |||
apps/frappe/frappe/core/page/data_import_tool/exporter.py +70,"If you are updating, please select ""Overwrite"" else existing rows will not be deleted.","Wenn Sie aktualisieren, wählen Sie ""Überschreiben"" sonst vorhandene Zeilen werden nicht gelöscht." | |||
@@ -1297,7 +1297,7 @@ DocType: Event,Every Year,Jährlich | |||
apps/frappe/frappe/core/doctype/user/user.py +359,Registered but disabled.,"Registriert, aber deaktiviert." | |||
sites/assets/js/list.min.js +95,Delete permanently?,Dauerhaft löschen? | |||
sites/assets/js/desk.min.js +922,Search in a document type,Suche in einem Dokumenttyp | |||
apps/frappe/frappe/custom/doctype/customize_form/customize_form.js +201,Allow field to remain editable even after submission,Lassen Feld auch nach Vorlage bearbeitet bleiben | |||
apps/frappe/frappe/custom/doctype/customize_form/customize_form.js +201,Allow field to remain editable even after submission,"Zulassen, dass Feld bearbeitbar bleibt, auch nach Einreichung" | |||
DocType: DocPerm,Role and Level,Rolle und Ebene | |||
apps/frappe/frappe/desk/moduleview.py +31,Custom Reports,Benutzerdefinierte Berichte | |||
DocType: Website Script,Website Script,Webseitenskript | |||
@@ -1322,7 +1322,7 @@ apps/frappe/frappe/core/page/permission_manager/permission_manager_help.html +32 | |||
apps/frappe/frappe/workflow/doctype/workflow/workflow.py +70,Cannot cancel before submitting. See Transition {0},Kann nicht vor der Vorlage zu stornieren. | |||
apps/frappe/frappe/templates/pages/print.py +146,Print Format {0} is disabled,Druckformat {0} ist deaktiviert | |||
DocType: Email Alert,Send days before or after the reference date,Senden Sie Tage vor oder nach dem Stichtag | |||
DocType: User,Allow user to login only after this hour (0-24),"Benutzer erlauben, sich nach dieser Stunde anzumelden (0-24)" | |||
DocType: User,Allow user to login only after this hour (0-24),"Benutzer erlauben, sich erst nach dieser Stunde anzumelden (0-24)" | |||
apps/frappe/frappe/core/doctype/doctype/doctype.py +33,Not in Developer Mode! Set in site_config.json or make 'Custom' DocType.,"Nicht in Entwicklermodus! In site_config.json erstellen können und ""Benutzerdefiniert"" DocType." | |||
DocType: Workflow State,globe,Globus | |||
DocType: System Settings,dd.mm.yyyy,TT.MM.JJJJ | |||
@@ -1368,7 +1368,7 @@ DocType: Web Form,"In JSON as <pre>[{""title"":""Jobs"", ""name"":""jobs""}]</pr | |||
apps/frappe/frappe/core/doctype/doctype/doctype.py +284,Fieldtype {0} for {1} cannot be indexed,Feldtyp {0} für {1} können nicht indiziert werden | |||
DocType: Communication,Email Account,E-Mail-Konto | |||
DocType: Workflow State,Download,Download | |||
DocType: Blog Post,Blog Intro,Blog-Intro | |||
DocType: Blog Post,Blog Intro,Blog-Einleitung | |||
apps/frappe/frappe/core/doctype/report/report.js +37,Enable Report,Bericht aktivieren | |||
DocType: User,Check / Uncheck roles assigned to the User. Click on the Role to find out what permissions that Role has.,"Aktivieren / Deaktivieren zugewiesenen Rollen der User. Klicken Sie auf die Rolle, um herauszufinden, welche Berechtigungen dieser Rolle hat." | |||
DocType: Web Page,Insert Code,Code einfügen | |||
@@ -1391,7 +1391,7 @@ apps/frappe/frappe/core/doctype/doctype/doctype.py +291,There can be only one Fo | |||
apps/frappe/frappe/website/doctype/website_settings/website_settings.py +23,Invalid Home Page,Ungültige Startseite | |||
apps/frappe/frappe/core/doctype/doctype/doctype.py +390,{0}: Permission at level 0 must be set before higher levels are set,{0} : Die Erlaubnis für Ebene 0 muss gesetzt werden bevor höhere Ebenen eingestellt werden können | |||
DocType: Website Settings,Home Page is Products,Startseite zeigt Produkte | |||
apps/frappe/frappe/desk/doctype/todo/todo.py +21,Assignment closed by {0},Zuordnung von geschlossen {0} | |||
apps/frappe/frappe/desk/doctype/todo/todo.py +21,Assignment closed by {0},Zuordnung geschlossen von {0} | |||
sites/assets/js/desk.min.js +926,Calculate,Berechnen | |||
apps/frappe/frappe/print/doctype/print_format/print_format.js +20,Please select DocType first,Bitte wählen Sie zunächst DocType | |||
sites/assets/js/desk.min.js +927,e.g. (55 + 434) / 4 or =Math.sin(Math.PI/2)...,zB (55 + 434) / 4 oder = Math.sin (Math.PI / 2) ... | |||
@@ -1495,7 +1495,7 @@ apps/frappe/frappe/custom/doctype/customize_form/customize_form.js +158,"Change | |||
DocType: Website Theme,Link to Bootstrap CSS,Link zur CSS Bootstrap | |||
DocType: Workflow State,camera,Kamera | |||
DocType: Website Settings,Brand HTML,Marke HTML | |||
apps/frappe/frappe/templates/includes/login/login.js +18,Both login and password required,Sowohl Login als auch Passwort erforderlich | |||
apps/frappe/frappe/templates/includes/login/login.js +18,Both login and password required,Login und Passwort sind beide erforderlich | |||
apps/frappe/frappe/model/document.py +392,Please refresh to get the latest document.,"Aktualisieren Sie, um zum neuesten Dokument zu gelangen." | |||
DocType: User,Security Settings,Sicherheitseinstellungen | |||
,Desktop,Desktop | |||
@@ -1508,6 +1508,6 @@ DocType: User,Background Style,Hintergrundstil | |||
DocType: System Settings,mm-dd-yyyy,MM-TT-JJJJ | |||
apps/frappe/frappe/desk/doctype/feed/feed.py +91,{0} logged in,{0} angemeldet | |||
DocType: Workflow,"All possible Workflow States and roles of the workflow. | |||
Docstatus Options: 0 is""Saved"", 1 is ""Submitted"" and 2 is ""Cancelled""","Alle mögliche Workflow-Status und Rollen des Workflows. DocStatus Optionen: 0 ist "gerettet", 1 "Eingereicht" und 2 "abgebrochen" ist" | |||
Docstatus Options: 0 is""Saved"", 1 is ""Submitted"" and 2 is ""Cancelled""","Mögliche Workflow-Zustände und -funktionen sind: (DocStatus Optionen) 0 ist ""Gespeichert"", 1 ist ""Eingereicht"" und 2 ist ""Abgebrochen""" | |||
apps/frappe/frappe/templates/emails/new_user.html +4,Your login id is,Ihre Login-ID ist | |||
DocType: DocField,Ignore User Permissions,Ignorieren von Benutzerberechtigungen |
@@ -284,7 +284,7 @@ DocType: Website Theme,Google Font (Text),گوگل قلم (متن) | |||
apps/frappe/frappe/core/page/permission_manager/permission_manager.js +323,Did not remove,آیا حذف کنید | |||
DocType: Report,Query,پرس و جو | |||
DocType: DocType,Sort Order,ترتیب | |||
apps/frappe/frappe/custom/doctype/customize_form/customize_form.py +130,'In List View' not allowed for type {0} in row {1},'نمایش لیستی' برای نوع {0} در ردیف مجاز {1} نیست | |||
apps/frappe/frappe/custom/doctype/customize_form/customize_form.py +130,'In List View' not allowed for type {0} in row {1},'نمایش لیستی' برای نوع {0} در ردیف {1} مجاز نیست | |||
DocType: Custom Field,Select the label after which you want to insert new field.,برچسب و پس از آن شما می خواهید برای وارد کردن زمینه های جدید را انتخاب کنید. | |||
DocType: Website Settings,Tweet will be shared via your user account (if specified),صدای جیر جیر از طریق حساب کاربری خود را به اشتراک گذاشته (اگر مشخص شده) | |||
,Document Share Report,سند اشتراک گزارش | |||
@@ -1189,7 +1189,7 @@ sites/assets/js/editor.min.js +128,Horizontal Line Break,خط افقی | |||
DocType: Top Bar Item,Right,راست | |||
DocType: User,User Type,نوع کاربر | |||
apps/frappe/frappe/core/page/user_permissions/user_permissions.js +68,Select User,انتخاب کاربر | |||
DocType: Communication,Keep a track of all communications,حفظ سابقه از تمام ارتباطات | |||
DocType: Communication,Keep a track of all communications,حفظ سابقه ی تمام ارتباطات | |||
apps/frappe/frappe/desk/form/save.py +30,Did not save,نجات نداد | |||
DocType: Property Setter,Property,خاصیت | |||
DocType: Website Slideshow,"Note: For best results, images must be of the same size and width must be greater than height.",توجه: برای بهترین نتایج، تصاویر باید از همان اندازه است و عرض باید بیشتر از ارتفاع باشد. | |||
@@ -608,7 +608,7 @@ DocType: User,Github User ID,Github ID utilisateur | |||
apps/frappe/frappe/core/page/user_permissions/user_permissions.js +246,If Document Type,Si Type de document | |||
DocType: Communication,Chat,Chat | |||
apps/frappe/frappe/core/doctype/doctype/doctype.py +230,Fieldname {0} appears multiple times in rows {1},Fieldname {0} apparaît plusieurs fois dans les lignes {1} | |||
DocType: Workflow State,arrow-down,arrow-down | |||
DocType: Workflow State,arrow-down,flèche bas | |||
sites/assets/js/desk.min.js +874,Collapse,Plier | |||
apps/frappe/frappe/model/delete_doc.py +131,User not allowed to delete {0}: {1},Utilisateur non autorisé à supprimer {0}: {1} | |||
DocType: Website Settings,Linked In Share,Linked In Partager | |||
@@ -865,7 +865,7 @@ apps/frappe/frappe/model/rename_doc.py +91,"{0} {1} does not exist, select a new | |||
apps/frappe/frappe/core/page/user_permissions/user_permissions.py +66,Cannot set permission for DocType: {0} and Name: {1},Vous ne pouvez pas définir des autorisations pour DocType : {0} et Nom : {1} | |||
DocType: Comment,Comment Doctype,Commentaire Doctype | |||
sites/assets/js/desk.min.js +257,Verify Password,Vérifier Le Mot De Passe | |||
apps/frappe/frappe/core/page/modules_setup/modules_setup.js +55,There were errors,Poster existe déjà . Vous ne pouvez pas ajouter de nouveau ! | |||
apps/frappe/frappe/core/page/modules_setup/modules_setup.js +55,There were errors,Il y avait des erreurs | |||
apps/frappe/frappe/desk/doctype/todo/todo.js +22,Close,Fermer | |||
apps/frappe/frappe/model/document.py +414,Cannot change docstatus from 0 to 2,Vous ne pouvez pas changer docstatus 0-2 | |||
apps/frappe/frappe/core/doctype/doctype/doctype.py +245,Options must be a valid DocType for field {0} in row {1},Les options doivent être un DOCTYPE valide pour le champ {0} à la ligne {1} | |||
@@ -1180,7 +1180,7 @@ DocType: Block Module,Core,Noyau | |||
DocType: DocField,Set non-standard precision for a Float or Currency field,Définir la précision non standard pour un champ de flotteur ou Devise | |||
DocType: Email Account,Ignore attachments over this size,Ignorer les pièces jointes plus de cette taille | |||
apps/frappe/frappe/database.py +217,Too many writes in one request. Please send smaller requests,Trop écrit en une seule requête . S'il vous plaît envoyer des demandes plus petites | |||
DocType: Workflow State,arrow-up,arrow-up | |||
DocType: Workflow State,arrow-up,flèche-haut | |||
DocType: DocField,Allow on Submit,Permettre de Soumettre | |||
apps/frappe/frappe/core/page/desktop/desktop.js +48,All Applications,Toutes les applications | |||
DocType: Web Page,Add code as <script>,Ajoutez le code en tant que <script> | |||
@@ -1228,7 +1228,7 @@ DocType: Workflow State,play-circle,play-cercle | |||
apps/frappe/frappe/print/page/print_format_builder/print_format_builder.js +75,Select Print Format to Edit,Sélectionnez Format d'impression à Modifier | |||
DocType: Workflow State,circle-arrow-down,cercle flèche vers le bas | |||
DocType: DocField,Datetime,Datetime | |||
DocType: Workflow State,arrow-right,arrow-right | |||
DocType: Workflow State,arrow-right,flèche-droite | |||
DocType: Workflow State,Workflow state represents the current state of a document.,État Workflow représente l'état actuel d'un document. | |||
sites/assets/js/editor.min.js +152,Open Link in a new Window,Ouvrir le lien dans une nouvelle fenêtre | |||
apps/frappe/frappe/utils/file_manager.py +235,Removed {0},Suppression de {0} | |||
@@ -1384,7 +1384,7 @@ DocType: DocType,Custom?,Custom? | |||
DocType: Website Settings,Website Theme Image,Thème site image | |||
DocType: Workflow State,road,route | |||
DocType: User,Timezone,Fuseau horaire | |||
sites/assets/js/desk.min.js +624,Unable to load: {0},Vous n'êtes pas autorisé à imprimer ce document | |||
sites/assets/js/desk.min.js +624,Unable to load: {0},Incapable de charger: {0} | |||
DocType: DocField,Read Only,Lecture seule | |||
DocType: Print Settings,Send Print as PDF,Envoyer Imprimer en PDF | |||
DocType: Workflow Transition,Allowed,Authorisé | |||
@@ -1446,7 +1446,7 @@ DocType: Custom Field,Insert After,Insérer après | |||
DocType: Social Login Keys,GitHub Client Secret,GitHub client secret | |||
DocType: Report,Report Name,Nom du rapport | |||
DocType: Email Alert,Save,Enregistrer | |||
DocType: Website Settings,Title Prefix,Title Prefix | |||
DocType: Website Settings,Title Prefix,Préfixe de titre | |||
DocType: Email Account,Notifications and bulk mails will be sent from this outgoing server.,Notifications et mails en vrac seront envoyés à partir de ce serveur sortant. | |||
DocType: Workflow State,cog,dent | |||
sites/assets/js/desk.min.js +608,{0} added,{0} ajoutée | |||
@@ -774,7 +774,7 @@ apps/frappe/frappe/model/base_document.py +403,Row #{0}:,Row # {0}: | |||
apps/frappe/frappe/core/doctype/user/user.py +81,New password emailed,Nova zaporka poslana mailom | |||
apps/frappe/frappe/auth.py +209,Login not allowed at this time,Prijava nije dopuštena u ovom trenutku | |||
DocType: DocType,Permissions Settings,Postavke ovlasti | |||
sites/assets/js/desk.min.js +931,{0} List,{0} List | |||
sites/assets/js/desk.min.js +931,{0} List,{0} lista | |||
apps/frappe/frappe/desk/form/assign_to.py +39,Already in user's To Do list,Već u user -a Da li popis | |||
DocType: Email Account,Enable Outgoing,Omogući odlazni | |||
DocType: System Settings,Email Footer Address,E-mail adresa podnožje | |||
@@ -869,7 +869,7 @@ apps/frappe/frappe/model/document.py +414,Cannot change docstatus from 0 to 2,Ne | |||
apps/frappe/frappe/core/doctype/doctype/doctype.py +245,Options must be a valid DocType for field {0} in row {1},Opcije mora bitivaljana DOCTYPE za polje {0} je u redu {1} | |||
apps/frappe/frappe/print/page/print_format_builder/print_format_builder.js +160,Edit Properties,Uredi Nekretnine | |||
DocType: Patch Log,List of patches executed,Popis izvršenih zakrpa | |||
DocType: Communication,Communication Medium,Komunikacija srednje | |||
DocType: Communication,Communication Medium,Komunikacijski medij | |||
DocType: Website Settings,Banner HTML,HTML baner | |||
apps/frappe/frappe/model/base_document.py +407,"{0} {1} cannot be ""{2}"". It should be one of ""{3}""","{0} {1} Ne može biti ""{2}"". To bi trebao biti jedan od ""{3}""" | |||
apps/frappe/frappe/utils/data.py +486,{0} or {1},{0} ili {1} | |||
@@ -52,7 +52,7 @@ DocType: Workflow State,volume-up,aumentar volume | |||
apps/frappe/frappe/core/page/data_import_tool/importer.py +43,Only allowed {0} rows in one import,Apenas permitidos {0} linhas em uma importação | |||
apps/frappe/frappe/core/page/data_import_tool/data_import_main.html +4,"To import or update records, you must first download the template for importing.","Para importação ou atualizar registros, você deve primeiro fazer o download do modelo para a importação." | |||
DocType: DocType,Default Print Format,Formato de impressão padrão | |||
DocType: Workflow State,Tags,Tag | |||
DocType: Workflow State,Tags,Etiquetas | |||
apps/frappe/frappe/core/page/permission_manager/permission_manager.js +47,Document Types,Tipos de documento | |||
DocType: Workflow,Workflow State Field,Campo do Estado do Fluxo de Trabalho | |||
DocType: DocType,Title Field,campo Título | |||
@@ -9,7 +9,6 @@ import os, sys, re, urllib | |||
import frappe | |||
import requests | |||
# utility functions like cint, int, flt, etc. | |||
from frappe.utils.data import * | |||
@@ -89,6 +88,15 @@ def validate_email_add(email_str, throw=False): | |||
return matched | |||
def split_emails(txt): | |||
email_list = [] | |||
for email in re.split(''',(?=(?:[^"]|"[^"]*")*$)''', cstr(txt)): | |||
email = strip(cstr(email)) | |||
if email: | |||
email_list.append(email) | |||
return email_list | |||
def random_string(length): | |||
"""generate a random string""" | |||
import string | |||
@@ -68,11 +68,11 @@ def get_allowed_functions_for_jenv(): | |||
"get_list": frappe.get_list, | |||
"get_all": frappe.get_all, | |||
"utils": datautils, | |||
"user": hasattr(frappe.local, "session") and frappe.local.session.user or "Guest", | |||
"user": getattr(frappe.local, "session", None) and frappe.local.session.user or "Guest", | |||
"date_format": frappe.db.get_default("date_format") or "yyyy-mm-dd", | |||
"get_fullname": frappe.utils.get_fullname, | |||
"get_gravatar": frappe.utils.get_gravatar, | |||
"full_name": hasattr(frappe.local, "session") and frappe.local.session.data.full_name or "Guest", | |||
"full_name": getattr(frappe.local, "session", None) and frappe.local.session.data.full_name or "Guest", | |||
"render_template": frappe.render_template | |||
}, | |||
"autodoc": { | |||
@@ -41,7 +41,7 @@ | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Title", | |||
"no_copy": 0, | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
@@ -126,7 +126,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2015-09-07 15:51:26", | |||
"modified": "2015-09-11 12:20:05.555186", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Blog Category", | |||
@@ -18,7 +18,7 @@ | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Title", | |||
"no_copy": 0, | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
@@ -272,7 +272,7 @@ | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 5, | |||
"modified": "2015-09-07 15:51:26", | |||
"modified": "2015-09-11 12:19:54.226902", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Blog Post", | |||
@@ -20,7 +20,7 @@ | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Title", | |||
"no_copy": 0, | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
@@ -431,7 +431,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2015-07-28 16:18:12.772231", | |||
"modified": "2015-09-11 12:20:17.264419", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Web Form", | |||
@@ -42,7 +42,7 @@ | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Title", | |||
"no_copy": 0, | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
@@ -608,7 +608,7 @@ | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 20, | |||
"modified": "2015-08-17 14:18:12.887565", | |||
"modified": "2015-09-11 12:19:37.342904", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Web Page", | |||
@@ -7,7 +7,7 @@ | |||
"custom": 0, | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "Master", | |||
"document_type": "Setup", | |||
"fields": [ | |||
{ | |||
"allow_on_submit": 0, | |||
@@ -643,7 +643,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2015-07-27 01:00:32.901851", | |||
"modified": "2015-09-14 02:56:01.143058", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Website Theme", | |||
@@ -8,7 +8,7 @@ | |||
"description": "Defines workflow states and rules for a document.", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "Transaction", | |||
"document_type": "Document", | |||
"fields": [ | |||
{ | |||
"allow_on_submit": 0, | |||
@@ -199,7 +199,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2015-07-27 01:00:32.901851", | |||
"modified": "2015-09-14 02:56:01.362929", | |||
"modified_by": "Administrator", | |||
"module": "Workflow", | |||
"name": "Workflow", | |||
@@ -1,6 +1,6 @@ | |||
from setuptools import setup, find_packages | |||
version = "6.2.0" | |||
version = "6.3.0" | |||
with open("requirements.txt", "r") as f: | |||
install_requires = f.readlines() | |||
@@ -25,10 +25,13 @@ io.on('connection', function(socket){ | |||
if (get_hostname(socket.request.headers.host) != get_hostname(socket.request.headers.origin)) { | |||
return; | |||
} | |||
// console.log("connection!"); | |||
var sid = cookie.parse(socket.request.headers.cookie).sid | |||
if(!sid) { | |||
return; | |||
} | |||
// console.log("firing get_user_info"); | |||
request.post(get_url(socket, '/api/method/frappe.async.get_user_info')) | |||
.type('form') | |||
.send({ | |||
@@ -45,16 +48,19 @@ io.on('connection', function(socket){ | |||
socket.join(room); | |||
socket.join(get_site_room(socket)); | |||
} | |||
}) | |||
}); | |||
socket.on('task_subscribe', function(task_id) { | |||
var room = 'task:' + task_id; | |||
socket.join(room); | |||
}) | |||
}); | |||
socket.on('progress_subscribe', function(task_id) { | |||
var room = 'task_progress:' + task_id; | |||
socket.join(room); | |||
send_existing_lines(task_id, socket); | |||
}) | |||
}); | |||
socket.on('doc_subscribe', function(doctype, docname) { | |||
// console.log('trying to subscribe', doctype, docname) | |||
request.post(get_url(socket, '/api/method/frappe.async.can_subscribe_doc')) | |||
@@ -66,6 +72,10 @@ io.on('connection', function(socket){ | |||
}) | |||
.end(function(err, res) { | |||
if(err) console.log(err); | |||
if(!res) { | |||
console.log("No response for doc_subscribe"); | |||
return; | |||
} | |||
if(res.status == 200) { | |||
var room = get_doc_room(socket, doctype, docname); | |||
// console.log('joining', room) | |||
@@ -73,10 +83,15 @@ io.on('connection', function(socket){ | |||
} | |||
}) | |||
}); | |||
socket.on('doc_unsubscribe', function(doctype, docname) { | |||
var room = get_doc_room(socket, doctype, docname); | |||
socket.leave(room); | |||
}); | |||
// socket.on('disconnect', function (arguments) { | |||
// console.log("user disconnected", arguments); | |||
// }); | |||
}); | |||
function send_existing_lines(task_id, socket) { | |||