Przeglądaj źródła

Merge branch 'develop'

version-14
Anand Doshi 9 lat temu
rodzic
commit
4e827056c9
65 zmienionych plików z 1216 dodań i 591 usunięć
  1. +4
    -2
      frappe/__init__.py
  2. +1
    -1
      frappe/__version__.py
  3. +10
    -31
      frappe/async.py
  4. +5
    -1
      frappe/build.py
  5. +151
    -23
      frappe/celery_app.py
  6. +2
    -0
      frappe/change_log/v6/v6_3_0.md
  7. +64
    -20
      frappe/core/doctype/async_task/async_task.json
  8. +10
    -0
      frappe/core/doctype/async_task/async_task_list.js
  9. +2
    -1
      frappe/core/doctype/comment/comment.py
  10. +112
    -106
      frappe/core/doctype/communication/communication.json
  11. +163
    -85
      frappe/core/doctype/communication/communication.py
  12. +2
    -2
      frappe/core/doctype/page/page.json
  13. +27
    -3
      frappe/core/page/data_import_tool/data_import_tool.js
  14. +6
    -4
      frappe/core/page/data_import_tool/importer.py
  15. +0
    -7
      frappe/core/page/permission_manager/permission_manager.js
  16. +2
    -2
      frappe/desk/doctype/note/note.json
  17. +11
    -1
      frappe/desk/form/meta.py
  18. +13
    -6
      frappe/desk/page/messages/messages.js
  19. +2
    -0
      frappe/desk/page/messages/messages.py
  20. +59
    -28
      frappe/email/bulk.py
  21. +5
    -1
      frappe/email/doctype/email_account/email_account.py
  22. +5
    -5
      frappe/email/email_body.py
  23. +2
    -1
      frappe/geo/doctype/currency/currency.json
  24. +1
    -1
      frappe/hooks.py
  25. +5
    -2
      frappe/model/create_new.py
  26. +87
    -49
      frappe/model/db_query.py
  27. +1
    -1
      frappe/model/delete_doc.py
  28. +5
    -5
      frappe/model/document.py
  29. +67
    -1
      frappe/print/doctype/print_format/print_format.json
  30. +1
    -1
      frappe/print/doctype/print_format/test_print_format.py
  31. +0
    -6
      frappe/public/js/frappe/form/control.js
  32. +15
    -4
      frappe/public/js/frappe/form/script_manager.js
  33. +23
    -0
      frappe/public/js/frappe/misc/user.js
  34. +3
    -0
      frappe/public/js/frappe/misc/utils.js
  35. +4
    -2
      frappe/public/js/frappe/model/create_new.js
  36. +8
    -6
      frappe/public/js/frappe/model/model.js
  37. +4
    -0
      frappe/public/js/frappe/request.js
  38. +9
    -8
      frappe/public/js/frappe/socket.js
  39. +10
    -0
      frappe/public/js/frappe/ui/messages.js
  40. +7
    -2
      frappe/public/js/frappe/ui/star.js
  41. +8
    -13
      frappe/public/js/frappe/upload.js
  42. +124
    -40
      frappe/public/js/frappe/views/communication.js
  43. +18
    -14
      frappe/tasks.py
  44. +3
    -1
      frappe/templates/pages/desk.html
  45. +4
    -1
      frappe/templates/pages/print.html
  46. +37
    -17
      frappe/templates/pages/print.py
  47. +1
    -1
      frappe/templates/pages/sitemap.py
  48. +2
    -2
      frappe/templates/print_formats/standard.html
  49. +15
    -11
      frappe/templates/print_formats/standard_macros.html
  50. +2
    -2
      frappe/tests/test_async.py
  51. +41
    -41
      frappe/translations/de.csv
  52. +2
    -2
      frappe/translations/fa.csv
  53. +6
    -6
      frappe/translations/fr.csv
  54. +2
    -2
      frappe/translations/hr.csv
  55. +1
    -1
      frappe/translations/pt-BR.csv
  56. +9
    -1
      frappe/utils/__init__.py
  57. +2
    -2
      frappe/utils/jinja.py
  58. +2
    -2
      frappe/website/doctype/blog_category/blog_category.json
  59. +2
    -2
      frappe/website/doctype/blog_post/blog_post.json
  60. +2
    -2
      frappe/website/doctype/web_form/web_form.json
  61. +2
    -2
      frappe/website/doctype/web_page/web_page.json
  62. +2
    -2
      frappe/website/doctype/website_theme/website_theme.json
  63. +2
    -2
      frappe/workflow/doctype/workflow/workflow.json
  64. +1
    -1
      setup.py
  65. +18
    -3
      socketio.js

+ 4
- 2
frappe/__init__.py Wyświetl plik

@@ -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
- 1
frappe/__version__.py Wyświetl plik

@@ -1,2 +1,2 @@
from __future__ import unicode_literals
__version__ = "6.2.0"
__version__ = "6.3.0"

+ 10
- 31
frappe/async.py Wyświetl plik

@@ -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):


+ 5
- 1
frappe/build.py Wyświetl plik

@@ -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))

+ 151
- 23
frappe/celery_app.py Wyświetl plik

@@ -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()

+ 2
- 0
frappe/change_log/v6/v6_3_0.md Wyświetl plik

@@ -0,0 +1,2 @@
- You can now add **CC** in Email
- Show checkboxes in Print

+ 64
- 20
frappe/core/doctype/async_task/async_task.json Wyświetl plik

@@ -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"
}

+ 10
- 0
frappe/core/doctype/async_task/async_task_list.js Wyświetl plik

@@ -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"];
}
}
};

+ 2
- 1
frappe/core/doctype/comment/comment.py Wyświetl plik

@@ -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)


+ 112
- 106
frappe/core/doctype/communication/communication.json Wyświetl plik

@@ -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",


+ 163
- 85
frappe/core/doctype/communication/communication.py Wyświetl plik

@@ -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()


+ 2
- 2
frappe/core/doctype/page/page.json Wyświetl plik

@@ -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",


+ 27
- 3
frappe/core/page/data_import_tool/data_import_tool.js Wyświetl plik

@@ -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");


+ 6
- 4
frappe/core/page/data_import_tool/importer.py Wyświetl plik

@@ -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()


+ 0
- 7
frappe/core/page/permission_manager/permission_manager.js Wyświetl plik

@@ -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]']]});


+ 2
- 2
frappe/desk/doctype/note/note.json Wyświetl plik

@@ -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",


+ 11
- 1
frappe/desk/form/meta.py Wyświetl plik

@@ -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.



+ 13
- 6
frappe/desk/page/messages/messages.js Wyświetl plik

@@ -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
});


+ 2
- 0
frappe/desk/page/messages/messages.py Wyświetl plik

@@ -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()


+ 59
- 28
frappe/email/bulk.py Wyświetl plik

@@ -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"),


+ 5
- 1
frappe/email/doctype/email_account/email_account.py Wyświetl plik

@@ -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
- 5
frappe/email/email_body.py Wyświetl plik

@@ -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

+ 2
- 1
frappe/geo/doctype/currency/currency.json Wyświetl plik

@@ -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",


+ 1
- 1
frappe/hooks.py Wyświetl plik

@@ -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"



+ 5
- 2
frappe/model/create_new.py Wyświetl plik

@@ -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


+ 87
- 49
frappe/model/db_query.py Wyświetl plik

@@ -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


+ 1
- 1
frappe/model/delete_doc.py Wyświetl plik

@@ -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


+ 5
- 5
frappe/model/document.py Wyświetl plik

@@ -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)


+ 67
- 1
frappe/print/doctype/print_format/print_format.json Wyświetl plik

@@ -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",


+ 1
- 1
frappe/print/doctype/print_format/test_print_format.py Wyświetl plik

@@ -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):


+ 0
- 6
frappe/public/js/frappe/form/control.js Wyświetl plik

@@ -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) || "&nbsp;";


+ 15
- 4
frappe/public/js/frappe/form/script_manager.js Wyświetl plik

@@ -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


+ 23
- 0
frappe/public/js/frappe/misc/user.js Wyświetl plik

@@ -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;


+ 3
- 0
frappe/public/js/frappe/misc/utils.js Wyświetl plik

@@ -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://'


+ 4
- 2
frappe/public/js/frappe/model/create_new.js Wyświetl plik

@@ -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];
}



+ 8
- 6
frappe/public/js/frappe/model/model.js Wyświetl plik

@@ -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();
}
},



+ 4
- 0
frappe/public/js/frappe/request.js Wyświetl plik

@@ -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


+ 9
- 8
frappe/public/js/frappe/socket.js Wyświetl plik

@@ -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);
}
}

+ 10
- 0
frappe/public/js/frappe/ui/messages.js Wyświetl plik

@@ -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) {


+ 7
- 2
frappe/public/js/frappe/ui/star.js Wyświetl plik

@@ -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) {


+ 8
- 13
frappe/public/js/frappe/upload.js Wyświetl plik

@@ -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);
}
}



+ 124
- 40
frappe/public/js/frappe/views/communication.js Wyświetl plik

@@ -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) {


+ 18
- 14
frappe/tasks.py Wyświetl plik

@@ -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:


+ 3
- 1
frappe/templates/pages/desk.html Wyświetl plik

@@ -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>



+ 4
- 1
frappe/templates/pages/print.html Wyświetl plik

@@ -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>


+ 37
- 17
frappe/templates/pages/print.py Wyświetl plik

@@ -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)



+ 1
- 1
frappe/templates/pages/sitemap.py Wyświetl plik

@@ -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")
})



+ 2
- 2
frappe/templates/print_formats/standard.html Wyświetl plik

@@ -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 %}


+ 15
- 11
frappe/templates/print_formats/standard_macros.html Wyświetl plik

@@ -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 %}


+ 2
- 2
frappe/tests/test_async.py Wyświetl plik

@@ -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")

+ 41
- 41
frappe/translations/de.csv Wyświetl plik

@@ -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 &quot;match&quot; Berechtigungsregeln festgelegt. Zur Liste der Felder zu sehen, gehen Sie auf &quot;Customize Form&quot;."
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 &lt;script&gt;,Hinzufügen von Code als &lt;script&gt;
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 &quot;gerettet&quot;, 1 &quot;Eingereicht&quot; und 2 &quot;abgebrochen&quot; 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

+ 2
- 2
frappe/translations/fa.csv Wyświetl plik

@@ -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.",توجه: برای بهترین نتایج، تصاویر باید از همان اندازه است و عرض باید بیشتر از ارتفاع باشد.


+ 6
- 6
frappe/translations/fr.csv Wyświetl plik

@@ -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 &lt;script&gt;,Ajoutez le code en tant que &lt;script&gt;
@@ -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


+ 2
- 2
frappe/translations/hr.csv Wyświetl plik

@@ -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}


+ 1
- 1
frappe/translations/pt-BR.csv Wyświetl plik

@@ -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
- 1
frappe/utils/__init__.py Wyświetl plik

@@ -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


+ 2
- 2
frappe/utils/jinja.py Wyświetl plik

@@ -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": {


+ 2
- 2
frappe/website/doctype/blog_category/blog_category.json Wyświetl plik

@@ -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",


+ 2
- 2
frappe/website/doctype/blog_post/blog_post.json Wyświetl plik

@@ -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",


+ 2
- 2
frappe/website/doctype/web_form/web_form.json Wyświetl plik

@@ -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",


+ 2
- 2
frappe/website/doctype/web_page/web_page.json Wyświetl plik

@@ -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",


+ 2
- 2
frappe/website/doctype/website_theme/website_theme.json Wyświetl plik

@@ -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",


+ 2
- 2
frappe/workflow/doctype/workflow/workflow.json Wyświetl plik

@@ -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
- 1
setup.py Wyświetl plik

@@ -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()


+ 18
- 3
socketio.js Wyświetl plik

@@ -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) {


Ładowanie…
Anuluj
Zapisz