Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
仰若水
frappe
Commits
4e827056
Commit
4e827056
authored
9 years ago
by
Anand Doshi
Browse files
Options
Download
Plain Diff
Merge branch 'develop'
parents
e33a4f91
694729bb
develop
v11.1.5
v11.1.4
v11.1.3
v11.1.2
v11.1.1
v11.1.0
v11.0.3
v11.0.3-beta.51
v11.0.3-beta.50
v11.0.3-beta.49
v11.0.3-beta.48
v11.0.3-beta.47
v11.0.3-beta.46
v11.0.3-beta.45
v11.0.3-beta.44
v11.0.3-beta.43
v11.0.3-beta.42
v11.0.3-beta.41
v11.0.3-beta.40
v11.0.3-beta.39
v11.0.3-beta.38
v11.0.3-beta.37
v11.0.3-beta.36
v11.0.3-beta.35
v11.0.3-beta.34
v11.0.3-beta.33
v11.0.3-beta.32
v11.0.3-beta.31
v11.0.3-beta.30
v11.0.3-beta.29
v11.0.3-beta.28
v11.0.3-beta.27
v11.0.3-beta.26
v11.0.3-beta.25
v11.0.3-beta.24
v11.0.3-beta.23
v11.0.3-beta.22
v11.0.3-beta.21
v11.0.3-beta.20
v11.0.3-beta.19
v11.0.3-beta.18
v11.0.3-beta.17
v11.0.3-beta.16
v11.0.3-beta.15
v11.0.3-beta.14
v11.0.3-beta.13
v11.0.3-beta.12
v11.0.3-beta.11
v11.0.3-beta.10
v11.0.3-beta.9
v11.0.3-beta.8
v11.0.3-beta.7
v11.0.3-beta.6
v11.0.3-beta.5
v11.0.3-beta.4
v11.0.3-beta.3
v11.0.3-beta.2
v11.0.3-beta.1
v11.0.2
v11.0.1
v11.0.0-beta
v10.1.71
v10.1.70
v10.1.69
v10.1.68
v10.1.67
v10.1.66
v10.1.65
v10.1.64
v10.1.63
v10.1.62
v10.1.61
v10.1.60
v10.1.59
v10.1.58
v10.1.57
v10.1.56
v10.1.55
v10.1.54
v10.1.53
v10.1.52
v10.1.51
v10.1.50
v10.1.49
v10.1.49-beta.1
v10.1.48
v10.1.47
v10.1.46
v10.1.45
v10.1.44
v10.1.43
v10.1.42
v10.1.41
v10.1.40
v10.1.39
v10.1.38
v10.1.37
v10.1.36
v10.1.35
v10.1.34
v10.1.33
v10.1.32
v10.1.31
v10.1.30
v10.1.29
v10.1.28
v10.1.27
v10.1.26
v10.1.25
v10.1.24
v10.1.23
v10.1.22
v10.1.21
v10.1.20
v10.1.19
v10.1.18
v10.1.17
v10.1.16
v10.1.15
v10.1.14
v10.1.13
v10.1.12
v10.1.11
v10.1.10
v10.1.9
v10.1.8
v10.1.7
v10.1.6
v10.1.5
v10.1.4
v10.1.3
v10.1.2
v10.1.1
v10.1.0
v10.0.25
v10.0.24
v10.0.23
v10.0.22
v10.0.21
v10.0.20
v10.0.19
v10.0.18
v10.0.17
v10.0.16
v10.0.15
v10.0.14
v10.0.13
v10.0.12
v10.0.11
v10.0.10
v10.0.9
v10.0.8
v10.0.7
v10.0.6
v10.0.5
v10.0.4
v10.0.3
v10.0.2
v10.0.1
v10.0.0
v9.2.25
v9.2.24
v9.2.23
v9.2.22
v9.2.21
v9.2.20
v9.2.19
v9.2.18
v9.2.17
v9.2.16
v9.2.15
v9.2.14
v9.2.13
v9.2.12
v9.2.11
v9.2.10
v9.2.9
v9.2.8
v9.2.7
v9.2.6
v9.2.5
v9.2.4
v9.2.3
v9.2.2
v9.2.1
v9.2.0
v9.1.11
v9.1.10
v9.1.9
v9.1.8
v9.1.7
v9.1.6
v9.1.5
v9.1.4
v9.1.3
v9.1.2
v9.1.1
v9.1.0
v9.0.10
v9.0.9
v9.0.8
v9.0.7
v9.0.6
v9.0.5
v9.0.4
v9.0.3
v9.0.2
v9.0.1
v9.0.0
v8.10.9
v8.10.8
v8.10.7
v8.10.6
v8.10.5
v8.10.4
v8.10.3
v8.10.2
v8.10.1
v8.10.0
v8.9.4
v8.9.3
v8.9.2
v8.9.1
v8.9.0
v8.8.5
v8.8.4
v8.8.3
v8.8.2
v8.8.1
v8.8.0
v8.7.11
v8.7.10
v8.7.9
v8.7.8
v8.7.7
v8.7.6
v8.7.5
v8.7.4
v8.7.3
v8.7.2
v8.7.1
v8.7.0
v8.6.8
v8.6.7
v8.6.6
v8.6.5
v8.6.4
v8.6.3
v8.6.2
v8.6.1
v8.6.0
v8.5.8
v8.5.7
v8.5.6
v8.5.5
v8.5.4
v8.5.3
v8.5.2
v8.5.1
v8.5.0
v8.4.1
v8.4.0
v8.3.10
v8.3.9
v8.3.8
v8.3.7
v8.3.6
v8.3.5
v8.3.4
v8.3.3
v8.3.2
v8.3.1
v8.3.0
v8.2.7
v8.2.6
v8.2.5
v8.2.4
v8.2.3
v8.2.2
v8.2.1
v8.2.0
v8.1.4
v8.1.3
v8.1.2
v8.1.1
v8.1.0
v8.0.71
v8.0.70
v8.0.69
v8.0.68
v8.0.67
v8.0.66
v8.0.65
v8.0.64
v8.0.63
v8.0.62
v8.0.61
v8.0.60
v8.0.59
v8.0.58
v8.0.57
v8.0.56
v8.0.55
v8.0.54
v8.0.53
v8.0.52
v8.0.51
v8.0.50
v8.0.49
v8.0.48
v8.0.47
v8.0.46
v8.0.45
v8.0.44
v8.0.43
v8.0.42
v8.0.41
v8.0.40
v8.0.39
v8.0.38
v8.0.37
v8.0.36
v8.0.35
v8.0.34
v8.0.33
v8.0.32
v8.0.31
v8.0.30
v8.0.29
v8.0.28
v8.0.27
v8.0.26
v8.0.25
v8.0.24
v8.0.23
v8.0.22
v8.0.21
v8.0.20
v8.0.19
v8.0.18
v8.0.17
v8.0.16
v8.0.15
v8.0.14
v8.0.13
v8.0.12
v8.0.11
v8.0.10
v8.0.9
v8.0.8
v8.0.7
v8.0.6
v8.0.5
v8.0.4
v8.0.3
v8.0.2
v8.0.1
v8.0.0
v7.2.31
v7.2.30
v7.2.29
v7.2.28
v7.2.27
v7.2.26
v7.2.25
v7.2.24
v7.2.23
v7.2.22
v7.2.21
v7.2.20
v7.2.19
v7.2.18
v7.2.17
v7.2.16
v7.2.15
v7.2.14
v7.2.13
v7.2.12
v7.2.11
v7.2.10
v7.2.9
v7.2.8
v7.2.7
v7.2.6
v7.2.5
v7.2.4
v7.2.3
v7.2.2
v7.2.1
v7.2.0
v7.1.29
v7.1.28
v7.1.27
v7.1.26
v7.1.25
v7.1.24
v7.1.23
v7.1.22
v7.1.21
v7.1.20
v7.1.19
v7.1.18
v7.1.17
v7.1.16
v7.1.15
v7.1.14
v7.1.13
v7.1.12
v7.1.11
v7.1.10
v7.1.9
v7.1.8
v7.1.7
v7.1.6
v7.1.5
v7.1.4
v7.1.3
v7.1.2
v7.1.1
v7.1.0
v7.0.47
v7.0.46
v7.0.45
v7.0.44
v7.0.43
v7.0.42
v7.0.41
v7.0.40
v7.0.39
v7.0.38
v7.0.37
v7.0.36
v7.0.35
v7.0.34
v7.0.33
v7.0.32
v7.0.31
v7.0.30
v7.0.29
v7.0.28
v7.0.27
v7.0.26
v7.0.25
v7.0.24
v7.0.23
v7.0.22
v7.0.21
v7.0.20
v7.0.19
v7.0.18
v7.0.17
v7.0.16
v7.0.15
v7.0.14
v7.0.13
v7.0.12
v7.0.11
v7.0.10
v7.0.9
v7.0.8
v7.0.7
v7.0.6
v7.0.5
v7.0.4
v7.0.3
v7.0.2
v7.0.1
v7.0.0
v6.27.24
v6.27.23
v6.27.22
v6.27.21
v6.27.20
v6.27.19
v6.27.18
v6.27.17
v6.27.16
v6.27.15
v6.27.14
v6.27.13
v6.27.12
v6.27.11
v6.27.10
v6.27.9
v6.27.8
v6.27.7
v6.27.6
v6.27.5
v6.27.4
v6.27.3
v6.27.2
v6.27.1
v6.27.0
v6.26.6
v6.26.5
v6.26.4
v6.26.3
v6.26.2
v6.26.1
v6.26.0
v6.25.6
v6.25.5
v6.25.4
v6.25.3
v6.25.2
v6.25.1
v6.25.0
v6.24.10
v6.24.9
v6.24.8
v6.24.7
v6.24.6
v6.24.5
v6.24.4
v6.24.3
v6.24.2
v6.24.1
v6.24.0
v6.23.3
v6.23.2
v6.23.1
v6.23.0
v6.22.7
v6.22.6
v6.22.5
v6.22.4
v6.22.3
v6.22.2
v6.22.1
v6.22.0
v6.21.0
v6.20.2
v6.20.1
v6.20.0
v6.19.3
v6.19.2
v6.19.1
v6.19.0
v6.18.1
v6.18.0
v6.17.6
v6.17.5
v6.17.4
v6.17.3
v6.17.2
v6.17.1
v6.17.0
v6.16.4
v6.16.3
v6.16.2
v6.16.1
v6.16.0
v6.15.4
v6.15.3
v6.15.2
v6.15.1
v6.15.0
v6.14.1
v6.14.0
v6.13.5
v6.13.4
v6.13.3
v6.13.2
v6.13.1
v6.13.0
v6.12.4
v6.12.3
v6.12.2
v6.12.1
v6.12.0
v6.11.0
v6.10.4
v6.10.3
v6.10.2
v6.10.1
v6.10.0
v6.9.3
v6.9.2
v6.9.1
v6.9.0
v6.8.2
v6.8.1
v6.8.0
v6.7.11
v6.7.10
v6.7.9
v6.7.8
v6.7.7
v6.7.6
v6.7.5
v6.7.4
v6.7.3
v6.7.2
v6.7.1
v6.7.0
v6.6.5
v6.6.4
v6.6.3
v6.6.2
v6.6.1
v6.6.0
v6.5.4
v6.5.3
v6.5.2
v6.5.1
v6.5.0
v6.4.9
v6.4.8
v6.4.7
v6.4.6
v6.4.5
v6.4.4
v6.4.3
v6.4.2
v6.4.1
v6.4.0
v6.3.0
No related merge requests found
Changes
65
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
frappe/__init__.py
+4
-2
frappe/__init__.py
frappe/__version__.py
+1
-1
frappe/__version__.py
frappe/async.py
+10
-31
frappe/async.py
frappe/build.py
+5
-1
frappe/build.py
frappe/celery_app.py
+151
-23
frappe/celery_app.py
frappe/change_log/v6/v6_3_0.md
+2
-0
frappe/change_log/v6/v6_3_0.md
frappe/core/doctype/async_task/async_task.json
+64
-20
frappe/core/doctype/async_task/async_task.json
frappe/core/doctype/async_task/async_task_list.js
+10
-0
frappe/core/doctype/async_task/async_task_list.js
frappe/core/doctype/comment/comment.py
+2
-1
frappe/core/doctype/comment/comment.py
frappe/core/doctype/communication/communication.json
+112
-106
frappe/core/doctype/communication/communication.json
frappe/core/doctype/communication/communication.py
+163
-85
frappe/core/doctype/communication/communication.py
frappe/core/doctype/page/page.json
+2
-2
frappe/core/doctype/page/page.json
frappe/core/page/data_import_tool/data_import_tool.js
+27
-3
frappe/core/page/data_import_tool/data_import_tool.js
frappe/core/page/data_import_tool/importer.py
+6
-4
frappe/core/page/data_import_tool/importer.py
frappe/core/page/permission_manager/permission_manager.js
+0
-7
frappe/core/page/permission_manager/permission_manager.js
frappe/desk/doctype/note/note.json
+2
-2
frappe/desk/doctype/note/note.json
frappe/desk/form/meta.py
+11
-1
frappe/desk/form/meta.py
frappe/desk/page/messages/messages.js
+13
-6
frappe/desk/page/messages/messages.js
frappe/desk/page/messages/messages.py
+2
-0
frappe/desk/page/messages/messages.py
frappe/email/bulk.py
+59
-28
frappe/email/bulk.py
with
646 additions
and
323 deletions
+646
-323
frappe/__init__.py
View file @
4e827056
...
...
@@ -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
:
...
...
This diff is collapsed.
Click to expand it.
frappe/__version__.py
View file @
4e827056
from
__future__
import
unicode_literals
__version__
=
"6.
2
.0"
__version__
=
"6.
3
.0"
This diff is collapsed.
Click to expand it.
frappe/async.py
View file @
4e827056
...
...
@@ -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
):
...
...
This diff is collapsed.
Click to expand it.
frappe/build.py
View file @
4e827056
...
...
@@ -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
))
This diff is collapsed.
Click to expand it.
frappe/celery_app.py
View file @
4e827056
...
...
@@ -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
()
This diff is collapsed.
Click to expand it.
frappe/change_log/v6/v6_3_0.md
0 → 100644
View file @
4e827056
-
You can now add
**CC**
in Email
-
Show checkboxes in Print
This diff is collapsed.
Click to expand it.
frappe/core/doctype/async_task/async_task.json
View file @
4e827056
...
...
@@ -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"
:
"
\n
Queued
\n
Running
\n
Succeeded
\n
Failed
\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"
:
"
\n
Queued
\n
Running
\n
Finished
\n
Failed
\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"
:
"s
tderr
"
,
"fieldtype"
:
"
Long Text
"
,
"fieldname"
:
"s
ection_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-0
7-28 16:18:11.344349
"
,
"modified"
:
"2015-0
9-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"
}
\ No newline at end of file
This diff is collapsed.
Click to expand it.
frappe/core/doctype/async_task/async_task_list.js
0 → 100644
View file @
4e827056
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
"
];
}
}
};
This diff is collapsed.
Click to expand it.
frappe/core/doctype/comment/comment.py
View file @
4e827056
...
...
@@ -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
)
...
...
This diff is collapsed.
Click to expand it.
frappe/core/doctype/communication/communication.json
View file @
4e827056
...
...
@@ -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
\n
Received
"
,
"options"
:
"
\n
Chat
\n
Phone
\n
Email
\n
SMS
\n
Visit
\n
Other
"
,
"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"
:
"
statu
s"
,
"fieldtype"
:
"
Select
"
,
"fieldname"
:
"
recipient
s"
,
"fieldtype"
:
"
Data
"
,
"hidden"
:
0
,
"ignore_user_permissions"
:
0
,
"in_filter"
:
0
,
"in_list_view"
:
1
,
"label"
:
"
Statu
s"
,
"in_list_view"
:
0
,
"label"
:
"
Recipient
s"
,
"no_copy"
:
0
,
"options"
:
"Open
\n
Replied
\n
Closed
\n
Linked"
,
"permlevel"
:
0
,
"precision"
:
""
,
"print_hide"
:
0
,
"read_only"
:
0
,
"report_hide"
:
0
,
...
...
@@ -82,16 +81,15 @@
"allow_on_submit"
:
0
,
"bold"
:
0
,
"collapsible"
:
0
,
"de
scription"
:
"Integrations can use this field to set email delivery status
"
,
"fieldname"
:
"
delivery_status
"
,
"fieldtype"
:
"
Select
"
,
"hidden"
:
1
,
"de
pends_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"
:
"
\n
Sent
\n
Bounced
\n
Opened
\n
Marked As Spam
\n
Rejected
\n
Delayed
\n
Soft-Bounced
\n
Clicked
\n
Recipient 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
\n
Replied
\n
Closed
\n
Linked
"
,
"permlevel"
:
0
,
"precision"
:
""
,
"print_hide"
:
0
,
...
...
@@ -171,21 +170,20 @@
"allow_on_submit"
:
0
,
"bold"
:
0
,
"collapsible"
:
0
,
"fieldname"
:
"
refe
re
n
ce
_name
"
,
"fieldtype"
:
"
Dynamic Link
"
,
"fieldname"
:
"
sent_or_
rece
ived
"
,
"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
\n
Received
"
,
"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"
:
"
\n
Sent
\n
Bounced
\n
Opened
\n
Marked As Spam
\n
Rejected
\n
Delayed
\n
Soft-Bounced
\n
Clicked
\n
Recipient 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"
:
"co
mmunication_medium
"
,
"fieldtype"
:
"
Select
"
,
"fieldname"
:
"co
ntent
"
,
"fieldtype"
:
"
Text Editor
"
,
"hidden"
:
0
,
"ignore_user_permissions"
:
0
,
"in_filter"
:
0
,
"in_list_view"
:
1
,
"label"
:
"Co
mmunication Medium
"
,
"in_list_view"
:
0
,
"label"
:
"Co
ntent
"
,
"no_copy"
:
0
,
"options"
:
"
\n
Chat
\n
Phone
\n
Email
\n
SMS
\n
Visit
\n
Other"
,
"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"
:
"
Colum
n Break"
,
"collapsible"
:
1
,
"fieldname"
:
"
additional_info
"
,
"fieldtype"
:
"
Sectio
n 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
_1
4"
,
"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"
:
"
colum
n_
b
re
ak5
"
,
"fieldtype"
:
"
Column Brea
k"
,
"fieldname"
:
"
i
n_re
ply_to
"
,
"fieldtype"
:
"
Lin
k"
,
"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-0
8
-1
4 17:46:20.902296
"
,
"modified"
:
"2015-0
9
-1
5 05:51:16.112080
"
,
"modified_by"
:
"Administrator"
,
"module"
:
"Core"
,
"name"
:
"Communication"
,
...
...
This diff is collapsed.
Click to expand it.
frappe/core/doctype/communication/communication.py
View file @
4e827056
...
...
@@ -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_address
es
:
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
()
...
...
This diff is collapsed.
Click to expand it.
frappe/core/doctype/page/page.json
View file @
4e827056
...
...
@@ -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-0
7
-1
3 04:45:55.942795
"
,
"modified"
:
"2015-0
9
-1
1 12:19:55.121822
"
,
"modified_by"
:
"Administrator"
,
"module"
:
"Core"
,
"name"
:
"Page"
,
...
...
This diff is collapsed.
Click to expand it.
frappe/core/page/data_import_tool/data_import_tool.js
View file @
4e827056
...
...
@@ -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
"
);
...
...
This diff is collapsed.
Click to expand it.
frappe/core/page/data_import_tool/importer.py
View file @
4e827056
...
...
@@ -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
()
...
...
This diff is collapsed.
Click to expand it.
frappe/core/page/permission_manager/permission_manager.js
View file @
4e827056
...
...
@@ -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]
'
]]});
...
...
This diff is collapsed.
Click to expand it.
frappe/desk/doctype/note/note.json
View file @
4e827056
...
...
@@ -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"
,
...
...
This diff is collapsed.
Click to expand it.
frappe/desk/form/meta.py
View file @
4e827056
...
...
@@ -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.
...
...
This diff is collapsed.
Click to expand it.
frappe/desk/page/messages/messages.js
View file @
4e827056
...
...
@@ -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
});
...
...
This diff is collapsed.
Click to expand it.
frappe/desk/page/messages/messages.py
View file @
4e827056
...
...
@@ -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
()
...
...
This diff is collapsed.
Click to expand it.
frappe/email/bulk.py
View file @
4e827056
...
...
@@ -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"
),
...
...
This diff is collapsed.
Click to expand it.
Prev
1
2
3
4
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment
Menu
Projects
Groups
Snippets
Help