Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
fedf35c3
Commit
fedf35c3
authored
Oct 24, 2017
by
Alex Dusenbery
Committed by
Alex Dusenbery
Nov 07, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
EDUCATOR-1572 | Send an ACE message to thread author when new comment signal is received.
parent
9920a324
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
360 additions
and
5 deletions
+360
-5
lms/djangoapps/discussion/signals/handlers.py
+18
-5
lms/djangoapps/discussion/tasks.py
+92
-0
lms/djangoapps/discussion/templates/discussion/edx_ace/response_notification/email/body.html
+58
-0
lms/djangoapps/discussion/templates/discussion/edx_ace/response_notification/email/body.txt
+5
-0
lms/djangoapps/discussion/templates/discussion/edx_ace/response_notification/email/from_name.txt
+1
-0
lms/djangoapps/discussion/templates/discussion/edx_ace/response_notification/email/head.html
+30
-0
lms/djangoapps/discussion/templates/discussion/edx_ace/response_notification/email/subject.txt
+5
-0
lms/djangoapps/discussion/tests/test_tasks.py
+151
-0
No files found.
lms/djangoapps/discussion/signals/handlers.py
View file @
fedf35c3
...
...
@@ -7,6 +7,7 @@ from django.dispatch import receiver
from
django_comment_common
import
signals
from
lms.djangoapps.discussion.config.waffle
import
waffle
,
FORUM_RESPONSE_NOTIFICATIONS
from
lms.djangoapps.discussion
import
tasks
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -18,8 +19,20 @@ def send_discussion_email_notification(sender, user, post, **kwargs):
send_message
(
post
)
def
send_message
(
post
):
"""
TODO: https://openedx.atlassian.net/browse/EDUCATOR-1572
"""
log
.
info
(
'Sending message about thread
%
s'
,
post
.
thread_id
)
def
send_message
(
comment
):
thread
=
comment
.
thread
context
=
{
'course_id'
:
unicode
(
thread
.
course_id
),
'comment_id'
:
comment
.
id
,
'comment_body'
:
comment
.
body
,
'comment_author_id'
:
comment
.
user_id
,
'comment_username'
:
comment
.
username
,
'comment_created_at'
:
comment
.
created_at
,
'thread_id'
:
thread
.
id
,
'thread_title'
:
thread
.
title
,
'thread_username'
:
thread
.
username
,
'thread_author_id'
:
thread
.
user_id
,
'thread_created_at'
:
thread
.
created_at
,
'thread_commentable_id'
:
thread
.
commentable_id
,
}
tasks
.
send_ace_message
.
apply_async
(
args
=
[
context
])
lms/djangoapps/discussion/tasks.py
0 → 100644
View file @
fedf35c3
"""
Defines asynchronous celery task for sending email notification (through edx-ace)
pertaining to new discussion forum comments.
"""
import
logging
from
urlparse
import
urljoin
from
celery
import
task
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
from
django.contrib.sites.models
import
Site
from
celery_utils.logged_task
import
LoggedTask
from
edx_ace
import
ace
from
edx_ace.message
import
MessageType
from
edx_ace.recipient
import
Recipient
from
opaque_keys.edx.keys
import
CourseKey
from
lms.djangoapps.django_comment_client.utils
import
permalink
import
lms.lib.comment_client
as
cc
from
lms.lib.comment_client.utils
import
merge_dict
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.schedules.template_context
import
get_base_template_context
log
=
logging
.
getLogger
(
__name__
)
DEFAULT_LANGUAGE
=
'en'
ROUTING_KEY
=
getattr
(
settings
,
'ACE_ROUTING_KEY'
,
None
)
class
ResponseNotification
(
MessageType
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
ResponseNotification
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
name
=
'response_notification'
@task
(
base
=
LoggedTask
,
routing_key
=
ROUTING_KEY
)
def
send_ace_message
(
context
):
context
[
'course_id'
]
=
CourseKey
.
from_string
(
context
[
'course_id'
])
if
_should_send_message
(
context
):
thread_author
=
User
.
objects
.
get
(
id
=
context
[
'thread_author_id'
])
message_context
=
_build_message_context
(
context
)
message
=
ResponseNotification
()
.
personalize
(
Recipient
(
thread_author
.
username
,
thread_author
.
email
),
_get_course_language
(
context
[
'course_id'
]),
message_context
)
log
.
info
(
'Sending forum comment email notification with context
%
s'
,
message_context
)
ace
.
send
(
message
)
def
_should_send_message
(
context
):
cc_thread_author
=
cc
.
User
(
id
=
context
[
'thread_author_id'
],
course_id
=
context
[
'course_id'
])
return
_is_user_subscribed_to_thread
(
cc_thread_author
,
context
[
'thread_id'
])
def
_is_user_subscribed_to_thread
(
cc_user
,
thread_id
):
paginated_result
=
cc_user
.
subscribed_threads
()
thread_ids
=
{
thread
[
'id'
]
for
thread
in
paginated_result
.
collection
}
while
paginated_result
.
page
<
paginated_result
.
num_pages
:
next_page
=
paginated_result
.
page
+
1
paginated_result
=
cc_user
.
subscribed_threads
(
query_params
=
{
'page'
:
next_page
})
thread_ids
.
update
(
thread
[
'id'
]
for
thread
in
paginated_result
.
collection
)
return
thread_id
in
thread_ids
def
_get_course_language
(
course_id
):
course_overview
=
CourseOverview
.
objects
.
get
(
id
=
course_id
)
language
=
course_overview
.
language
or
DEFAULT_LANGUAGE
return
language
def
_build_message_context
(
context
):
message_context
=
get_base_template_context
(
Site
.
objects
.
get_current
())
message_context
.
update
(
context
)
message_context
[
'post_link'
]
=
_get_thread_url
(
context
)
return
message_context
def
_get_thread_url
(
context
):
thread_content
=
{
'type'
:
'thread'
,
'course_id'
:
context
[
'course_id'
],
'commentable_id'
:
context
[
'thread_commentable_id'
],
'id'
:
context
[
'thread_id'
],
}
return
urljoin
(
settings
.
LMS_ROOT_URL
,
permalink
(
thread_content
))
lms/djangoapps/discussion/templates/discussion/edx_ace/response_notification/email/body.html
0 → 100644
View file @
fedf35c3
{% load i18n %}
{% load static %}
{% block preview_text %}
{% blocktrans trimmed %}
Hi {{ thread_username }}
{% endblocktrans %}
{% endblock %}
{% block content %}
<table
width=
"100%"
align=
"left"
border=
"0"
cellpadding=
"0"
cellspacing=
"0"
role=
"presentation"
>
<tr>
<td>
{% blocktrans trimmed %}
<h1>
Hi {{ thread_username }},
</h1>
<p>
{{ comment_username }} made the following reply to {{ thread_title }} at {{ comment_created_at }}.
</p>
<p>
{{ comment_body }}
</p>
<p>
<a
href=
"{{ post_link }}"
>
View the discussion.
</a>
</p>
{% endblocktrans %}
<p>
{# email client support for style sheets is pretty spotty, so we have to inline all of these styles #}
<a
{%
if
course_ids
|
length =
=
1
%}
href=
"{{ upsell_link }}"
{%
else
%}
href=
"{{ dashboard_url }}"
{%
endif
%}
style=
"
color: #ffffff;
text-decoration: none;
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
background-color: #005686;
border-top: 10px solid #005686;
border-bottom: 10px solid #005686;
border-right: 16px solid #005686;
border-left: 16px solid #005686;
display: inline-block;
"
>
</a>
</p>
</td>
</tr>
</table>
{% endblock %}
lms/djangoapps/discussion/templates/discussion/edx_ace/response_notification/email/body.txt
0 → 100644
View file @
fedf35c3
{% load i18n %}
{% blocktrans trimmed %}
This is the reply to your thread:
{% endblocktrans %}
lms/djangoapps/discussion/templates/discussion/edx_ace/response_notification/email/from_name.txt
0 → 100644
View file @
fedf35c3
{{ platform_name }}
lms/djangoapps/discussion/templates/discussion/edx_ace/response_notification/email/head.html
0 → 100644
View file @
fedf35c3
<meta
http-equiv=
"Content-Type"
content=
"text/html; charset=UTF-8"
/>
<title>
{% block title %}Reply to {{ thread_title }} at {{ comment_created_at }} {% endblock %}
</title>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
/>
<style
type=
"text/css"
>
@media
only
screen
and
(
min-device-width
:
601px
)
{
.content
{
width
:
600px
!important
;
}
}
@-ms-viewport
{
width
:
device-width
;
}
/* Column Drop Layout Pattern CSS */
@media
only
screen
and
(
max-width
:
450px
)
{
td
[
class
=
"col"
]
{
display
:
block
;
width
:
100%
;
-moz-box-sizing
:
border-box
;
-webkit-box-sizing
:
border-box
;
box-sizing
:
border-box
;
float
:
left
;
text-align
:
left
!important
;
padding-bottom
:
20px
;
}
}
</style>
\ No newline at end of file
lms/djangoapps/discussion/templates/discussion/edx_ace/response_notification/email/subject.txt
0 → 100644
View file @
fedf35c3
{% load i18n %}
{% blocktrans %}
Someone replied to your thread on {{ platform_name }}
{% endblocktrans %}
lms/djangoapps/discussion/tests/test_tasks.py
0 → 100644
View file @
fedf35c3
"""
Tests the execution of forum notification tasks.
"""
from
contextlib
import
contextmanager
from
datetime
import
datetime
,
timedelta
import
json
import
math
from
urlparse
import
urljoin
import
ddt
from
django.conf
import
settings
from
django.contrib.sites.models
import
Site
import
mock
from
django_comment_common.models
import
ForumsConfig
from
django_comment_common.signals
import
comment_created
from
edx_ace.recipient
import
Recipient
from
lms.djangoapps.discussion.config.waffle
import
waffle
,
FORUM_RESPONSE_NOTIFICATIONS
from
openedx.core.djangoapps.content.course_overviews.tests.factories
import
CourseOverviewFactory
from
openedx.core.djangoapps.schedules.template_context
import
get_base_template_context
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
@contextmanager
def
mock_the_things
():
thread_permalink
=
'/courses/discussion/dummy_discussion_id'
with
mock
.
patch
(
'requests.request'
)
as
mock_request
,
mock
.
patch
(
'edx_ace.ace.send'
)
as
mock_ace_send
:
with
mock
.
patch
(
'lms.djangoapps.discussion.tasks.permalink'
,
return_value
=
thread_permalink
)
as
mock_permalink
:
with
mock
.
patch
(
'lms.djangoapps.discussion.tasks.cc.Thread'
):
yield
(
mock_request
,
mock_ace_send
,
mock_permalink
)
def
make_mock_responder
(
thread_ids
,
per_page
=
1
):
collection
=
[
{
'id'
:
thread_id
}
for
thread_id
in
thread_ids
]
def
mock_response
(
*
args
,
**
kwargs
):
page
=
kwargs
.
get
(
'params'
,
{})
.
get
(
'page'
,
1
)
start_index
=
per_page
*
(
page
-
1
)
end_index
=
per_page
*
page
data
=
{
'collection'
:
collection
[
start_index
:
end_index
],
'page'
:
page
,
'num_pages'
:
int
(
math
.
ceil
(
len
(
collection
)
/
float
(
per_page
))),
'thread_count'
:
len
(
collection
)
}
return
mock
.
Mock
(
status_code
=
200
,
text
=
json
.
dumps
(
data
),
json
=
mock
.
Mock
(
return_value
=
data
))
return
mock_response
@ddt.ddt
class
TaskTestCase
(
ModuleStoreTestCase
):
@mock.patch.dict
(
"django.conf.settings.FEATURES"
,
{
"ENABLE_DISCUSSION_SERVICE"
:
True
})
def
setUp
(
self
):
super
(
TaskTestCase
,
self
)
.
setUp
()
self
.
discussion_id
=
'dummy_discussion_id'
self
.
course
=
CourseOverviewFactory
.
create
(
language
=
'fr'
)
# Patch the comment client user save method so it does not try
# to create a new cc user when creating a django user
with
mock
.
patch
(
'student.models.cc.User.save'
):
self
.
thread_author
=
UserFactory
(
username
=
'thread_author'
,
password
=
'password'
,
email
=
'email'
)
self
.
comment_author
=
UserFactory
(
username
=
'comment_author'
,
password
=
'password'
,
email
=
'email'
)
CourseEnrollmentFactory
(
user
=
self
.
thread_author
,
course_id
=
self
.
course
.
id
)
CourseEnrollmentFactory
(
user
=
self
.
comment_author
,
course_id
=
self
.
course
.
id
)
config
=
ForumsConfig
.
current
()
config
.
enabled
=
True
config
.
save
()
@ddt.data
(
True
,
False
)
def
test_send_discussion_email_notification
(
self
,
user_subscribed
):
with
mock_the_things
()
as
mocked_items
:
mock_request
,
mock_ace_send
,
mock_permalink
=
mocked_items
if
user_subscribed
:
non_matching_id
=
'not-a-match'
# with per_page left with a default value of 1, this ensures
# that we test a multiple page result when calling
# comment_client.User.subscribed_threads()
mock_request
.
side_effect
=
make_mock_responder
([
non_matching_id
,
self
.
discussion_id
])
else
:
mock_request
.
side_effect
=
make_mock_responder
([])
now
=
datetime
.
utcnow
()
one_hour_ago
=
now
-
timedelta
(
hours
=
1
)
thread
=
mock
.
Mock
(
id
=
self
.
discussion_id
,
course_id
=
self
.
course
.
id
,
created_at
=
one_hour_ago
,
title
=
'thread-title'
,
user_id
=
self
.
thread_author
.
id
,
username
=
self
.
thread_author
.
username
,
commentable_id
=
'thread-commentable-id'
)
comment
=
mock
.
Mock
(
id
=
'comment-id'
,
body
=
'comment-body'
,
created_at
=
now
,
thread
=
thread
,
user_id
=
self
.
comment_author
.
id
,
username
=
self
.
comment_author
.
username
)
user
=
mock
.
Mock
()
with
waffle
()
.
override
(
FORUM_RESPONSE_NOTIFICATIONS
):
comment_created
.
send
(
sender
=
None
,
user
=
user
,
post
=
comment
)
if
user_subscribed
:
expected_message_context
=
get_base_template_context
(
Site
.
objects
.
get_current
())
expected_message_context
.
update
({
'comment_author_id'
:
self
.
comment_author
.
id
,
'comment_body'
:
'comment-body'
,
'comment_created_at'
:
now
,
'comment_id'
:
'comment-id'
,
'comment_username'
:
self
.
comment_author
.
username
,
'course_id'
:
self
.
course
.
id
,
'thread_author_id'
:
self
.
thread_author
.
id
,
'thread_created_at'
:
one_hour_ago
,
'thread_id'
:
self
.
discussion_id
,
'thread_title'
:
'thread-title'
,
'thread_username'
:
self
.
thread_author
.
username
,
'thread_commentable_id'
:
'thread-commentable-id'
,
'post_link'
:
urljoin
(
settings
.
LMS_ROOT_URL
,
mock_permalink
.
return_value
),
})
expected_recipient
=
Recipient
(
self
.
thread_author
.
username
,
self
.
thread_author
.
email
)
actual_message
=
mock_ace_send
.
call_args_list
[
0
][
0
][
0
]
self
.
assertEqual
(
expected_message_context
,
actual_message
.
context
)
self
.
assertEqual
(
expected_recipient
,
actual_message
.
recipient
)
self
.
assertEqual
(
self
.
course
.
language
,
actual_message
.
language
)
else
:
self
.
assertFalse
(
mock_ace_send
.
called
)
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