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
69f900dd
Commit
69f900dd
authored
Jul 30, 2014
by
Diana Huang
Committed by
Julia Hansbrough
Aug 01, 2014
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Basic notifications handling.
LMS-11163
parent
a0b4f8b6
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
160 additions
and
6 deletions
+160
-6
cms/djangoapps/contentstore/utils.py
+1
-2
cms/djangoapps/contentstore/views/course.py
+94
-1
cms/djangoapps/contentstore/views/tests/test_course_index.py
+63
-2
cms/urls.py
+1
-0
common/djangoapps/course_action_state/managers.py
+1
-1
No files found.
cms/djangoapps/contentstore/utils.py
View file @
69f900dd
...
@@ -57,8 +57,7 @@ def initialize_permissions(course_key, user_who_created_course):
...
@@ -57,8 +57,7 @@ def initialize_permissions(course_key, user_who_created_course):
def
remove_all_instructors
(
course_key
):
def
remove_all_instructors
(
course_key
):
"""
"""
Removes given user as instructor and staff to the given course,
Removes all instructor and staff users from the given course.
after verifying that the requesting_user has permission to do so.
"""
"""
staff_role
=
CourseStaffRole
(
course_key
)
staff_role
=
CourseStaffRole
(
course_key
)
staff_role
.
remove_users
(
*
staff_role
.
users_with_role
())
staff_role
.
remove_users
(
*
staff_role
.
users_with_role
())
...
...
cms/djangoapps/contentstore/views/course.py
View file @
69f900dd
...
@@ -38,6 +38,7 @@ from contentstore.utils import (
...
@@ -38,6 +38,7 @@ from contentstore.utils import (
reverse_course_url
,
reverse_course_url
,
reverse_usage_url
,
reverse_usage_url
,
reverse_url
,
reverse_url
,
remove_all_instructors
,
)
)
from
models.settings.course_details
import
CourseDetails
,
CourseSettingsEncoder
from
models.settings.course_details
import
CourseDetails
,
CourseSettingsEncoder
from
models.settings.course_grading
import
CourseGradingModel
from
models.settings.course_grading
import
CourseGradingModel
...
@@ -62,6 +63,7 @@ from student.roles import (
...
@@ -62,6 +63,7 @@ from student.roles import (
)
)
from
student
import
auth
from
student
import
auth
from
course_action_state.models
import
CourseRerunState
,
CourseRerunUIStateManager
from
course_action_state.models
import
CourseRerunState
,
CourseRerunUIStateManager
from
course_action_state.managers
import
CourseActionStateItemNotFoundError
from
microsite_configuration
import
microsite
from
microsite_configuration
import
microsite
...
@@ -70,6 +72,7 @@ __all__ = ['course_info_handler', 'course_handler', 'course_info_update_handler'
...
@@ -70,6 +72,7 @@ __all__ = ['course_info_handler', 'course_handler', 'course_info_update_handler'
'settings_handler'
,
'settings_handler'
,
'grading_handler'
,
'grading_handler'
,
'advanced_settings_handler'
,
'advanced_settings_handler'
,
'course_notifications_handler'
,
'textbooks_list_handler'
,
'textbooks_detail_handler'
,
'textbooks_list_handler'
,
'textbooks_detail_handler'
,
'group_configurations_list_handler'
,
'group_configurations_detail_handler'
]
'group_configurations_list_handler'
,
'group_configurations_detail_handler'
]
...
@@ -95,6 +98,90 @@ def _get_course_module(course_key, user, depth=0):
...
@@ -95,6 +98,90 @@ def _get_course_module(course_key, user, depth=0):
return
course_module
return
course_module
@login_required
def
course_notifications_handler
(
request
,
course_key_string
=
None
,
action_state_id
=
None
):
"""
Handle incoming requests for notifications in a RESTful way.
course_key_string and action_state_id must both be set; else a HttpBadResponseRequest is returned.
For each of these operations, the requesting user must have access to the course;
else a PermissionDenied error is returned.
GET
json: return json representing information about the notification (action, state, etc)
DELETE
json: return json repressing success or failure of dismissal/deletion of the notification
PUT
Raises a NotImplementedError.
POST
Raises a NotImplementedError.
"""
# ensure that we have a course and an action state
if
not
course_key_string
or
not
action_state_id
:
return
HttpResponseBadRequest
()
response_format
=
request
.
REQUEST
.
get
(
'format'
,
'html'
)
course_key
=
CourseKey
.
from_string
(
course_key_string
)
if
response_format
==
'json'
or
'application/json'
in
request
.
META
.
get
(
'HTTP_ACCEPT'
,
'application/json'
):
if
not
has_course_access
(
request
.
user
,
course_key
):
raise
PermissionDenied
()
if
request
.
method
==
'GET'
:
return
_course_notifications_json_get
(
action_state_id
)
elif
request
.
method
==
'DELETE'
:
# we assume any delete requests dismiss actions from the UI
return
_dismiss_notification
(
request
,
action_state_id
)
elif
request
.
method
==
'PUT'
:
raise
NotImplementedError
()
elif
request
.
method
==
'POST'
:
raise
NotImplementedError
()
else
:
return
HttpResponseBadRequest
()
else
:
return
HttpResponseNotFound
()
def
_course_notifications_json_get
(
course_action_state_id
):
"""
Return the action and the action state for the given id
"""
try
:
action_state
=
CourseRerunState
.
objects
.
find_first
(
id
=
course_action_state_id
)
except
CourseActionStateItemNotFoundError
:
return
HttpResponseBadRequest
()
action_state_info
=
{
'action'
:
action_state
.
action
,
'state'
:
action_state
.
state
,
'should_display'
:
action_state
.
should_display
}
return
JsonResponse
(
action_state_info
)
def
_dismiss_notification
(
request
,
course_action_state_id
):
# pylint: disable=unused-argument
"""
Update the display of the course notification
"""
try
:
action_state
=
CourseRerunState
.
objects
.
find_first
(
id
=
course_action_state_id
)
except
CourseActionStateItemNotFoundError
:
# Can't dismiss a notification that doesn't exist in the first place
return
HttpResponseBadRequest
()
if
action_state
.
state
==
CourseRerunUIStateManager
.
State
.
FAILED
:
# We remove all permissions for this course key at this time, since
# no further access is required to a course that failed to be created.
remove_all_instructors
(
action_state
.
course_key
)
# The CourseRerunState is no longer needed by the UI; delete
action_state
.
delete
()
return
JsonResponse
({
'success'
:
True
})
# pylint: disable=unused-argument
# pylint: disable=unused-argument
@login_required
@login_required
def
course_handler
(
request
,
course_key_string
=
None
):
def
course_handler
(
request
,
course_key_string
=
None
):
...
@@ -297,6 +384,11 @@ def course_index(request, course_key):
...
@@ -297,6 +384,11 @@ def course_index(request, course_key):
lms_link
=
get_lms_link_for_item
(
course_module
.
location
)
lms_link
=
get_lms_link_for_item
(
course_module
.
location
)
sections
=
course_module
.
get_children
()
sections
=
course_module
.
get_children
()
try
:
current_action
=
CourseRerunState
.
objects
.
find_first
(
course_key
=
course_key
,
should_display
=
True
)
except
(
ItemNotFoundError
,
CourseActionStateItemNotFoundError
):
current_action
=
None
return
render_to_response
(
'overview.html'
,
{
return
render_to_response
(
'overview.html'
,
{
'context_course'
:
course_module
,
'context_course'
:
course_module
,
'lms_link'
:
lms_link
,
'lms_link'
:
lms_link
,
...
@@ -307,7 +399,8 @@ def course_index(request, course_key):
...
@@ -307,7 +399,8 @@ def course_index(request, course_key):
'new_section_category'
:
'chapter'
,
'new_section_category'
:
'chapter'
,
'new_subsection_category'
:
'sequential'
,
'new_subsection_category'
:
'sequential'
,
'new_unit_category'
:
'vertical'
,
'new_unit_category'
:
'vertical'
,
'category'
:
'vertical'
'category'
:
'vertical'
,
'rerun_notification_id'
:
current_action
.
id
if
current_action
else
None
,
})
})
...
...
cms/djangoapps/contentstore/views/tests/test_course_index.py
View file @
69f900dd
...
@@ -5,9 +5,13 @@ import json
...
@@ -5,9 +5,13 @@ import json
import
lxml
import
lxml
from
contentstore.tests.utils
import
CourseTestCase
from
contentstore.tests.utils
import
CourseTestCase
from
contentstore.utils
import
reverse_course_url
from
contentstore.utils
import
reverse_course_url
,
add_instructor
from
contentstore.views.access
import
has_course_access
from
course_action_state.models
import
CourseRerunState
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
opaque_keys.edx.locator
import
Locator
from
opaque_keys.edx.locator
import
CourseLocator
from
student.tests.factories
import
UserFactory
from
course_action_state.managers
import
CourseRerunUIStateManager
from
django.conf
import
settings
from
django.conf
import
settings
...
@@ -115,6 +119,63 @@ class TestCourseIndex(CourseTestCase):
...
@@ -115,6 +119,63 @@ class TestCourseIndex(CourseTestCase):
# Finally, validate the entire response for consistency
# Finally, validate the entire response for consistency
self
.
assert_correct_json_response
(
json_response
)
self
.
assert_correct_json_response
(
json_response
)
def
test_notifications_handler_get
(
self
):
state
=
CourseRerunUIStateManager
.
State
.
FAILED
action
=
CourseRerunUIStateManager
.
ACTION
should_display
=
True
# try when no notification exists
notification_url
=
reverse_course_url
(
'course_notifications_handler'
,
self
.
course
.
id
,
kwargs
=
{
'action_state_id'
:
1
,
})
resp
=
self
.
client
.
get
(
notification_url
,
HTTP_ACCEPT
=
'application/json'
)
# verify that we get an empty dict out
self
.
assertEquals
(
resp
.
status_code
,
400
)
# create a test notification
rerun_state
=
CourseRerunState
.
objects
.
update_state
(
course_key
=
self
.
course
.
id
,
new_state
=
state
,
allow_not_found
=
True
)
CourseRerunState
.
objects
.
update_should_display
(
entry_id
=
rerun_state
.
id
,
user
=
UserFactory
(),
should_display
=
should_display
)
# try to get information on this notification
notification_url
=
reverse_course_url
(
'course_notifications_handler'
,
self
.
course
.
id
,
kwargs
=
{
'action_state_id'
:
rerun_state
.
id
,
})
resp
=
self
.
client
.
get
(
notification_url
,
HTTP_ACCEPT
=
'application/json'
)
json_response
=
json
.
loads
(
resp
.
content
)
self
.
assertEquals
(
json_response
[
'state'
],
state
)
self
.
assertEquals
(
json_response
[
'action'
],
action
)
self
.
assertEquals
(
json_response
[
'should_display'
],
should_display
)
def
test_notifications_handler_dismiss
(
self
):
state
=
CourseRerunUIStateManager
.
State
.
FAILED
should_display
=
True
rerun_course_key
=
CourseLocator
(
org
=
'testx'
,
course
=
'test_course'
,
run
=
'test_run'
)
# add an instructor to this course
user2
=
UserFactory
()
add_instructor
(
rerun_course_key
,
self
.
user
,
user2
)
# create a test notification
rerun_state
=
CourseRerunState
.
objects
.
update_state
(
course_key
=
rerun_course_key
,
new_state
=
state
,
allow_not_found
=
True
)
CourseRerunState
.
objects
.
update_should_display
(
entry_id
=
rerun_state
.
id
,
user
=
user2
,
should_display
=
should_display
)
# try to get information on this notification
notification_dismiss_url
=
reverse_course_url
(
'course_notifications_handler'
,
self
.
course
.
id
,
kwargs
=
{
'action_state_id'
:
rerun_state
.
id
,
})
resp
=
self
.
client
.
delete
(
notification_dismiss_url
)
self
.
assertEquals
(
resp
.
status_code
,
200
)
with
self
.
assertRaises
(
CourseRerunState
.
DoesNotExist
):
# delete nofications that are dismissed
CourseRerunState
.
objects
.
get
(
id
=
rerun_state
.
id
)
self
.
assertFalse
(
has_course_access
(
user2
,
rerun_course_key
))
def
assert_correct_json_response
(
self
,
json_response
):
def
assert_correct_json_response
(
self
,
json_response
):
"""
"""
Asserts that the JSON response is syntactically consistent
Asserts that the JSON response is syntactically consistent
...
...
cms/urls.py
View file @
69f900dd
...
@@ -73,6 +73,7 @@ urlpatterns += patterns(
...
@@ -73,6 +73,7 @@ urlpatterns += patterns(
'course_info_update_handler'
'course_info_update_handler'
),
),
url
(
r'^course/{}?$'
.
format
(
settings
.
COURSE_KEY_PATTERN
),
'course_handler'
,
name
=
'course_handler'
),
url
(
r'^course/{}?$'
.
format
(
settings
.
COURSE_KEY_PATTERN
),
'course_handler'
,
name
=
'course_handler'
),
url
(
r'^course_notifications/{}/(?P<action_state_id>\d+)?$'
.
format
(
settings
.
COURSE_KEY_PATTERN
),
'course_notifications_handler'
),
url
(
r'^subsection/{}$'
.
format
(
settings
.
USAGE_KEY_PATTERN
),
'subsection_handler'
),
url
(
r'^subsection/{}$'
.
format
(
settings
.
USAGE_KEY_PATTERN
),
'subsection_handler'
),
url
(
r'^unit/{}$'
.
format
(
settings
.
USAGE_KEY_PATTERN
),
'unit_handler'
),
url
(
r'^unit/{}$'
.
format
(
settings
.
USAGE_KEY_PATTERN
),
'unit_handler'
),
url
(
r'^container/{}$'
.
format
(
settings
.
USAGE_KEY_PATTERN
),
'container_handler'
),
url
(
r'^container/{}$'
.
format
(
settings
.
USAGE_KEY_PATTERN
),
'container_handler'
),
...
...
common/djangoapps/course_action_state/managers.py
View file @
69f900dd
...
@@ -96,7 +96,7 @@ class CourseActionUIStateManager(CourseActionStateManager):
...
@@ -96,7 +96,7 @@ class CourseActionUIStateManager(CourseActionStateManager):
"""
"""
Updates the should_display field with the given value for the entry for the given id.
Updates the should_display field with the given value for the entry for the given id.
"""
"""
self
.
update
(
id
=
entry_id
,
updated_user
=
user
,
should_display
=
should_display
)
return
self
.
update
(
id
=
entry_id
,
updated_user
=
user
,
should_display
=
should_display
)
class
CourseRerunUIStateManager
(
CourseActionUIStateManager
):
class
CourseRerunUIStateManager
(
CourseActionUIStateManager
):
...
...
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