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
3385d1e1
Commit
3385d1e1
authored
Dec 15, 2015
by
ehtesham
Committed by
Ehtesham
Dec 21, 2015
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
TNL-2810 backend implementation to limit the maximum bookmarks per course
parent
43b5fcc0
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
176 additions
and
35 deletions
+176
-35
lms/envs/aws.py
+3
-0
lms/envs/common.py
+3
-0
lms/static/js/bookmarks/views/bookmark_button.js
+17
-7
lms/static/js/bookmarks/views/bookmarks_list.js
+1
-4
lms/static/js/spec/bookmarks/bookmark_button_view_spec.js
+17
-0
lms/static/js/spec/bookmarks/bookmarks_list_view_spec.js
+0
-2
openedx/core/djangoapps/bookmarks/api.py
+45
-2
openedx/core/djangoapps/bookmarks/models.py
+0
-2
openedx/core/djangoapps/bookmarks/tests/test_api.py
+30
-3
openedx/core/djangoapps/bookmarks/tests/test_services.py
+1
-1
openedx/core/djangoapps/bookmarks/tests/test_views.py
+31
-5
openedx/core/djangoapps/bookmarks/views.py
+28
-9
No files found.
lms/envs/aws.py
View file @
3385d1e1
...
@@ -731,3 +731,6 @@ JWT_EXPIRATION = ENV_TOKENS.get('JWT_EXPIRATION', JWT_EXPIRATION)
...
@@ -731,3 +731,6 @@ JWT_EXPIRATION = ENV_TOKENS.get('JWT_EXPIRATION', JWT_EXPIRATION)
PROCTORING_BACKEND_PROVIDER
=
AUTH_TOKENS
.
get
(
"PROCTORING_BACKEND_PROVIDER"
,
PROCTORING_BACKEND_PROVIDER
)
PROCTORING_BACKEND_PROVIDER
=
AUTH_TOKENS
.
get
(
"PROCTORING_BACKEND_PROVIDER"
,
PROCTORING_BACKEND_PROVIDER
)
PROCTORING_SETTINGS
=
ENV_TOKENS
.
get
(
"PROCTORING_SETTINGS"
,
PROCTORING_SETTINGS
)
PROCTORING_SETTINGS
=
ENV_TOKENS
.
get
(
"PROCTORING_SETTINGS"
,
PROCTORING_SETTINGS
)
# Course Content Bookmarks Settings
MAX_BOOKMARKS_PER_COURSE
=
ENV_TOKENS
.
get
(
'MAX_BOOKMARKS_PER_COURSE'
,
MAX_BOOKMARKS_PER_COURSE
)
lms/envs/common.py
View file @
3385d1e1
...
@@ -2655,3 +2655,6 @@ CCX_MAX_STUDENTS_ALLOWED = 200
...
@@ -2655,3 +2655,6 @@ CCX_MAX_STUDENTS_ALLOWED = 200
# financial assistance form
# financial assistance form
FINANCIAL_ASSISTANCE_MIN_LENGTH
=
800
FINANCIAL_ASSISTANCE_MIN_LENGTH
=
800
FINANCIAL_ASSISTANCE_MAX_LENGTH
=
2500
FINANCIAL_ASSISTANCE_MAX_LENGTH
=
2500
# Course Content Bookmarks Settings
MAX_BOOKMARKS_PER_COURSE
=
100
lms/static/js/bookmarks/views/bookmark_button.js
View file @
3385d1e1
...
@@ -4,8 +4,6 @@
...
@@ -4,8 +4,6 @@
function
(
gettext
,
$
,
_
,
Backbone
,
MessageBannerView
)
{
function
(
gettext
,
$
,
_
,
Backbone
,
MessageBannerView
)
{
return
Backbone
.
View
.
extend
({
return
Backbone
.
View
.
extend
({
errorIcon
:
'<i class="fa fa-fw fa-exclamation-triangle message-error" aria-hidden="true"></i>'
,
errorMessage
:
gettext
(
'An error has occurred. Please try again.'
),
errorMessage
:
gettext
(
'An error has occurred. Please try again.'
),
srAddBookmarkText
:
gettext
(
'Click to add'
),
srAddBookmarkText
:
gettext
(
'Click to add'
),
...
@@ -15,6 +13,8 @@
...
@@ -15,6 +13,8 @@
'click'
:
'toggleBookmark'
'click'
:
'toggleBookmark'
},
},
showBannerInterval
:
5000
,
// time in ms
initialize
:
function
(
options
)
{
initialize
:
function
(
options
)
{
this
.
apiUrl
=
options
.
apiUrl
;
this
.
apiUrl
=
options
.
apiUrl
;
this
.
bookmarkId
=
options
.
bookmarkId
;
this
.
bookmarkId
=
options
.
bookmarkId
;
...
@@ -44,8 +44,10 @@
...
@@ -44,8 +44,10 @@
view
.
$el
.
trigger
(
'bookmark:add'
);
view
.
$el
.
trigger
(
'bookmark:add'
);
view
.
setBookmarkState
(
true
);
view
.
setBookmarkState
(
true
);
},
},
error
:
function
()
{
error
:
function
(
jqXHR
)
{
view
.
showError
();
var
response
=
jqXHR
.
responseText
?
JSON
.
parse
(
jqXHR
.
responseText
)
:
''
;
var
userMessage
=
response
?
response
.
user_message
:
''
;
view
.
showError
(
userMessage
);
}
}
});
});
},
},
...
@@ -79,13 +81,21 @@
...
@@ -79,13 +81,21 @@
}
}
},
},
showError
:
function
()
{
showError
:
function
(
errorText
)
{
var
errorMsg
=
errorText
||
this
.
errorMessage
;
if
(
!
this
.
messageView
)
{
if
(
!
this
.
messageView
)
{
this
.
messageView
=
new
MessageBannerView
({
this
.
messageView
=
new
MessageBannerView
({
el
:
$
(
'.message-banner'
)
el
:
$
(
'.message-banner'
),
type
:
'error'
});
});
}
}
this
.
messageView
.
showMessage
(
this
.
errorMessage
,
this
.
errorIcon
);
this
.
messageView
.
showMessage
(
errorMsg
);
// Hide message automatically after some interval
setTimeout
(
_
.
bind
(
function
()
{
this
.
messageView
.
hideMessage
();
},
this
),
this
.
showBannerInterval
);
}
}
});
});
});
});
...
...
lms/static/js/bookmarks/views/bookmarks_list.js
View file @
3385d1e1
...
@@ -55,14 +55,11 @@
...
@@ -55,14 +55,11 @@
this
.
hideErrorMessage
();
this
.
hideErrorMessage
();
this
.
showBookmarksContainer
();
this
.
showBookmarksContainer
();
this
.
showLoadingMessage
();
this
.
collection
.
goTo
(
this
.
defaultPage
).
done
(
function
()
{
this
.
collection
.
goTo
(
this
.
defaultPage
).
done
(
function
()
{
view
.
hideLoadingMessage
();
view
.
render
();
view
.
render
();
view
.
focusBookmarksElement
();
view
.
focusBookmarksElement
();
}).
fail
(
function
()
{
}).
fail
(
function
()
{
view
.
hideLoadingMessage
();
view
.
showErrorMessage
();
view
.
showErrorMessage
();
});
});
},
},
...
@@ -100,7 +97,7 @@
...
@@ -100,7 +97,7 @@
hideBookmarks
:
function
()
{
hideBookmarks
:
function
()
{
this
.
$el
.
hide
();
this
.
$el
.
hide
();
$
(
this
.
coursewareResultsWrapperEl
).
hide
();
$
(
this
.
coursewareResultsWrapperEl
).
hide
();
$
(
this
.
coursewareContentEl
).
css
(
'display'
,
'table-cell'
);
$
(
this
.
coursewareContentEl
).
css
(
'display'
,
'table-cell'
);
},
},
showBookmarksContainer
:
function
()
{
showBookmarksContainer
:
function
()
{
...
...
lms/static/js/spec/bookmarks/bookmark_button_view_spec.js
View file @
3385d1e1
...
@@ -5,6 +5,7 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
...
@@ -5,6 +5,7 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
'use strict'
;
'use strict'
;
describe
(
"bookmarks.button"
,
function
()
{
describe
(
"bookmarks.button"
,
function
()
{
var
timerCallback
;
var
API_URL
=
'bookmarks/api/v1/bookmarks/'
;
var
API_URL
=
'bookmarks/api/v1/bookmarks/'
;
...
@@ -15,6 +16,9 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
...
@@ -15,6 +16,9 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
'templates/fields/message_banner'
'templates/fields/message_banner'
]
]
);
);
timerCallback
=
jasmine
.
createSpy
(
'timerCallback'
);
jasmine
.
Clock
.
useMock
();
});
});
var
createBookmarkButtonView
=
function
(
isBookmarked
)
{
var
createBookmarkButtonView
=
function
(
isBookmarked
)
{
...
@@ -135,5 +139,18 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
...
@@ -135,5 +139,18 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
expect
(
$messageBanner
.
text
().
trim
()).
toBe
(
bookmarkButtonView
.
errorMessage
);
expect
(
$messageBanner
.
text
().
trim
()).
toBe
(
bookmarkButtonView
.
errorMessage
);
});
});
it
(
'removes error message after 5 seconds'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
),
$messageBanner
=
$
(
'.message-banner'
),
bookmarkButtonView
=
createBookmarkButtonView
(
false
);
bookmarkButtonView
.
$el
.
click
();
AjaxHelpers
.
respondWithError
(
requests
);
expect
(
$messageBanner
.
text
().
trim
()).
toBe
(
bookmarkButtonView
.
errorMessage
);
jasmine
.
Clock
.
tick
(
5001
);
expect
(
$messageBanner
.
text
().
trim
()).
toBe
(
''
);
});
});
});
});
});
lms/static/js/spec/bookmarks/bookmarks_list_view_spec.js
View file @
3385d1e1
...
@@ -162,8 +162,6 @@ define(['backbone',
...
@@ -162,8 +162,6 @@ define(['backbone',
var
expectedData
=
createBookmarksData
({
numBookmarksToCreate
:
3
});
var
expectedData
=
createBookmarksData
({
numBookmarksToCreate
:
3
});
bookmarksButtonView
.
$
(
'.bookmarks-list-button'
).
click
();
bookmarksButtonView
.
$
(
'.bookmarks-list-button'
).
click
();
expect
(
$
(
'#loading-message'
).
text
().
trim
()).
toBe
(
bookmarksButtonView
.
bookmarksListView
.
loadingMessage
);
verifyRequestParams
(
verifyRequestParams
(
requests
,
requests
,
...
...
openedx/core/djangoapps/bookmarks/api.py
View file @
3385d1e1
...
@@ -3,10 +3,20 @@ Bookmarks Python API.
...
@@ -3,10 +3,20 @@ Bookmarks Python API.
"""
"""
from
eventtracking
import
tracker
from
eventtracking
import
tracker
from
.
import
DEFAULT_FIELDS
,
OPTIONAL_FIELDS
from
.
import
DEFAULT_FIELDS
,
OPTIONAL_FIELDS
from
xmodule.modulestore.django
import
modulestore
from
django.conf
import
settings
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
.models
import
Bookmark
from
.models
import
Bookmark
from
.serializers
import
BookmarkSerializer
from
.serializers
import
BookmarkSerializer
class
BookmarksLimitReachedError
(
Exception
):
"""
if try to create new bookmark when max limit of bookmarks already reached
"""
pass
def
get_bookmark
(
user
,
usage_key
,
fields
=
None
):
def
get_bookmark
(
user
,
usage_key
,
fields
=
None
):
"""
"""
Return data for a bookmark.
Return data for a bookmark.
...
@@ -66,6 +76,28 @@ def get_bookmarks(user, course_key=None, fields=None, serialized=True):
...
@@ -66,6 +76,28 @@ def get_bookmarks(user, course_key=None, fields=None, serialized=True):
return
bookmarks_queryset
return
bookmarks_queryset
def
can_create_more
(
data
):
"""
Determine if a new Bookmark can be created for the course
based on limit defined in django.conf.settings.MAX_BOOKMARKS_PER_COURSE
Arguments:
data (dict): The data to create the object with.
Returns:
Boolean
"""
data
=
dict
(
data
)
user
=
data
[
'user'
]
course_key
=
data
[
'usage_key'
]
.
course_key
# User can create up to max_bookmarks_per_course bookmarks
if
Bookmark
.
objects
.
filter
(
user
=
user
,
course_key
=
course_key
)
.
count
()
>=
settings
.
MAX_BOOKMARKS_PER_COURSE
:
return
False
return
True
def
create_bookmark
(
user
,
usage_key
):
def
create_bookmark
(
user
,
usage_key
):
"""
"""
Create a bookmark.
Create a bookmark.
...
@@ -79,11 +111,22 @@ def create_bookmark(user, usage_key):
...
@@ -79,11 +111,22 @@ def create_bookmark(user, usage_key):
Raises:
Raises:
ItemNotFoundError: If no block exists for the usage_key.
ItemNotFoundError: If no block exists for the usage_key.
BookmarksLimitReachedError: if try to create new bookmark when max limit of bookmarks already reached
"""
"""
bookmark
,
created
=
Bookmark
.
create
({
usage_key
=
usage_key
.
replace
(
course_key
=
modulestore
()
.
fill_in_run
(
usage_key
.
course_key
))
data
=
{
'user'
:
user
,
'user'
:
user
,
'usage_key'
:
usage_key
'usage_key'
:
usage_key
})
}
if
usage_key
.
course_key
.
run
is
None
:
raise
ItemNotFoundError
if
not
can_create_more
(
data
):
raise
BookmarksLimitReachedError
bookmark
,
created
=
Bookmark
.
create
(
data
)
if
created
:
if
created
:
_track_event
(
'edx.bookmark.added'
,
bookmark
)
_track_event
(
'edx.bookmark.added'
,
bookmark
)
return
BookmarkSerializer
(
bookmark
,
context
=
{
'fields'
:
DEFAULT_FIELDS
+
OPTIONAL_FIELDS
})
.
data
return
BookmarkSerializer
(
bookmark
,
context
=
{
'fields'
:
DEFAULT_FIELDS
+
OPTIONAL_FIELDS
})
.
data
...
...
openedx/core/djangoapps/bookmarks/models.py
View file @
3385d1e1
...
@@ -74,9 +74,7 @@ class Bookmark(TimeStampedModel):
...
@@ -74,9 +74,7 @@ class Bookmark(TimeStampedModel):
ItemNotFoundError: If no block exists for the usage_key.
ItemNotFoundError: If no block exists for the usage_key.
"""
"""
data
=
dict
(
data
)
data
=
dict
(
data
)
usage_key
=
data
.
pop
(
'usage_key'
)
usage_key
=
data
.
pop
(
'usage_key'
)
usage_key
=
usage_key
.
replace
(
course_key
=
modulestore
()
.
fill_in_run
(
usage_key
.
course_key
))
with
modulestore
()
.
bulk_operations
(
usage_key
.
course_key
):
with
modulestore
()
.
bulk_operations
(
usage_key
.
course_key
):
block
=
modulestore
()
.
get_item
(
usage_key
)
block
=
modulestore
()
.
get_item
(
usage_key
)
...
...
openedx/core/djangoapps/bookmarks/tests/test_api.py
View file @
3385d1e1
...
@@ -14,6 +14,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
...
@@ -14,6 +14,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
from
..
import
api
from
..
import
api
from
..models
import
Bookmark
from
..models
import
Bookmark
from
openedx.core.djangoapps.bookmarks.api
import
BookmarksLimitReachedError
from
.test_models
import
BookmarksTestsBase
from
.test_models
import
BookmarksTestsBase
...
@@ -120,7 +121,7 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
...
@@ -120,7 +121,7 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
"""
"""
self
.
assertEqual
(
len
(
api
.
get_bookmarks
(
user
=
self
.
user
,
course_key
=
self
.
course
.
id
)),
2
)
self
.
assertEqual
(
len
(
api
.
get_bookmarks
(
user
=
self
.
user
,
course_key
=
self
.
course
.
id
)),
2
)
with
self
.
assertNumQueries
(
8
):
with
self
.
assertNumQueries
(
9
):
bookmark_data
=
api
.
create_bookmark
(
user
=
self
.
user
,
usage_key
=
self
.
vertical_2
.
location
)
bookmark_data
=
api
.
create_bookmark
(
user
=
self
.
user
,
usage_key
=
self
.
vertical_2
.
location
)
self
.
assert_bookmark_event_emitted
(
self
.
assert_bookmark_event_emitted
(
...
@@ -141,7 +142,7 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
...
@@ -141,7 +142,7 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
"""
"""
self
.
assertEqual
(
len
(
api
.
get_bookmarks
(
user
=
self
.
user
,
course_key
=
self
.
course
.
id
)),
2
)
self
.
assertEqual
(
len
(
api
.
get_bookmarks
(
user
=
self
.
user
,
course_key
=
self
.
course
.
id
)),
2
)
with
self
.
assertNumQueries
(
8
):
with
self
.
assertNumQueries
(
9
):
bookmark_data
=
api
.
create_bookmark
(
user
=
self
.
user
,
usage_key
=
self
.
vertical_2
.
location
)
bookmark_data
=
api
.
create_bookmark
(
user
=
self
.
user
,
usage_key
=
self
.
vertical_2
.
location
)
self
.
assert_bookmark_event_emitted
(
self
.
assert_bookmark_event_emitted
(
...
@@ -157,7 +158,7 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
...
@@ -157,7 +158,7 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
mock_tracker
.
reset_mock
()
mock_tracker
.
reset_mock
()
with
self
.
assertNumQueries
(
4
):
with
self
.
assertNumQueries
(
5
):
bookmark_data_2
=
api
.
create_bookmark
(
user
=
self
.
user
,
usage_key
=
self
.
vertical_2
.
location
)
bookmark_data_2
=
api
.
create_bookmark
(
user
=
self
.
user
,
usage_key
=
self
.
vertical_2
.
location
)
self
.
assertEqual
(
len
(
api
.
get_bookmarks
(
user
=
self
.
user
,
course_key
=
self
.
course
.
id
)),
3
)
self
.
assertEqual
(
len
(
api
.
get_bookmarks
(
user
=
self
.
user
,
course_key
=
self
.
course
.
id
)),
3
)
...
@@ -177,6 +178,32 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
...
@@ -177,6 +178,32 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
self
.
assert_no_events_were_emitted
(
mock_tracker
)
self
.
assert_no_events_were_emitted
(
mock_tracker
)
@patch
(
'openedx.core.djangoapps.bookmarks.api.tracker.emit'
)
@patch
(
'openedx.core.djangoapps.bookmarks.api.tracker.emit'
)
@patch
(
'django.conf.settings.MAX_BOOKMARKS_PER_COURSE'
,
5
)
def
bookmark_more_than_limit_raise_error
(
self
,
mock_tracker
):
"""
Verifies that create_bookmark raises error when maximum number of units
allowed to bookmark per course are already bookmarked.
"""
max_bookmarks
=
settings
.
MAX_BOOKMARKS_PER_COURSE
__
,
blocks
,
__
=
self
.
create_course_with_bookmarks_count
(
max_bookmarks
)
with
self
.
assertNumQueries
(
1
):
with
self
.
assertRaises
(
BookmarksLimitReachedError
):
api
.
create_bookmark
(
user
=
self
.
user
,
usage_key
=
blocks
[
-
1
]
.
location
)
self
.
assert_no_events_were_emitted
(
mock_tracker
)
# if user tries to create bookmark in another course it should succeed
self
.
assertEqual
(
len
(
api
.
get_bookmarks
(
user
=
self
.
user
,
course_key
=
self
.
other_course
.
id
)),
1
)
api
.
create_bookmark
(
user
=
self
.
user
,
usage_key
=
self
.
other_chapter_1
.
location
)
self
.
assertEqual
(
len
(
api
.
get_bookmarks
(
user
=
self
.
user
,
course_key
=
self
.
other_course
.
id
)),
2
)
# if another user tries to create bookmark it should succeed
self
.
assertEqual
(
len
(
api
.
get_bookmarks
(
user
=
self
.
other_user
,
course_key
=
blocks
[
-
1
]
.
location
.
course_key
)),
0
)
api
.
create_bookmark
(
user
=
self
.
other_user
,
usage_key
=
blocks
[
-
1
]
.
location
)
self
.
assertEqual
(
len
(
api
.
get_bookmarks
(
user
=
self
.
other_user
,
course_key
=
blocks
[
-
1
]
.
location
.
course_key
)),
1
)
@patch
(
'openedx.core.djangoapps.bookmarks.api.tracker.emit'
)
def
test_delete_bookmark
(
self
,
mock_tracker
):
def
test_delete_bookmark
(
self
,
mock_tracker
):
"""
"""
Verifies that delete_bookmark removes bookmark as expected.
Verifies that delete_bookmark removes bookmark as expected.
...
...
openedx/core/djangoapps/bookmarks/tests/test_services.py
View file @
3385d1e1
...
@@ -68,7 +68,7 @@ class BookmarksServiceTests(BookmarksTestsBase):
...
@@ -68,7 +68,7 @@ class BookmarksServiceTests(BookmarksTestsBase):
self
.
bookmark_service
.
set_bookmarked
(
usage_key
=
UsageKey
.
from_string
(
"i4x://ed/ed/ed/interactive"
))
self
.
bookmark_service
.
set_bookmarked
(
usage_key
=
UsageKey
.
from_string
(
"i4x://ed/ed/ed/interactive"
))
)
)
with
self
.
assertNumQueries
(
8
):
with
self
.
assertNumQueries
(
9
):
self
.
assertTrue
(
self
.
bookmark_service
.
set_bookmarked
(
usage_key
=
self
.
vertical_2
.
location
))
self
.
assertTrue
(
self
.
bookmark_service
.
set_bookmarked
(
usage_key
=
self
.
vertical_2
.
location
))
def
test_unset_bookmarked
(
self
):
def
test_unset_bookmarked
(
self
):
...
...
openedx/core/djangoapps/bookmarks/tests/test_views.py
View file @
3385d1e1
...
@@ -234,7 +234,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
...
@@ -234,7 +234,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
"""
"""
Test that posting a bookmark successfully returns newly created data with 201 code.
Test that posting a bookmark successfully returns newly created data with 201 code.
"""
"""
with
self
.
assertNumQueries
(
1
5
):
with
self
.
assertNumQueries
(
1
6
):
response
=
self
.
send_post
(
response
=
self
.
send_post
(
client
=
self
.
client
,
client
=
self
.
client
,
url
=
reverse
(
'bookmarks'
),
url
=
reverse
(
'bookmarks'
),
...
@@ -265,7 +265,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
...
@@ -265,7 +265,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
data
=
{
'usage_id'
:
'invalid'
},
data
=
{
'usage_id'
:
'invalid'
},
expected_status
=
400
expected_status
=
400
)
)
self
.
assertEqual
(
response
.
data
[
'user_message'
],
u'
Invalid usage_id: invalid
.'
)
self
.
assertEqual
(
response
.
data
[
'user_message'
],
u'
An error has occurred. Please try again
.'
)
# Send data without usage_id.
# Send data without usage_id.
with
self
.
assertNumQueries
(
7
):
# No queries for bookmark table.
with
self
.
assertNumQueries
(
7
):
# No queries for bookmark table.
...
@@ -275,7 +275,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
...
@@ -275,7 +275,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
data
=
{
'course_id'
:
'invalid'
},
data
=
{
'course_id'
:
'invalid'
},
expected_status
=
400
expected_status
=
400
)
)
self
.
assertEqual
(
response
.
data
[
'user_message'
],
u'
Parameter usage_id not provided
.'
)
self
.
assertEqual
(
response
.
data
[
'user_message'
],
u'
An error has occurred. Please try again
.'
)
self
.
assertEqual
(
response
.
data
[
'developer_message'
],
u'Parameter usage_id not provided.'
)
self
.
assertEqual
(
response
.
data
[
'developer_message'
],
u'Parameter usage_id not provided.'
)
# Send empty data dictionary.
# Send empty data dictionary.
...
@@ -286,7 +286,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
...
@@ -286,7 +286,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
data
=
{},
data
=
{},
expected_status
=
400
expected_status
=
400
)
)
self
.
assertEqual
(
response
.
data
[
'user_message'
],
u'
No data provided
.'
)
self
.
assertEqual
(
response
.
data
[
'user_message'
],
u'
An error has occurred. Please try again
.'
)
self
.
assertEqual
(
response
.
data
[
'developer_message'
],
u'No data provided.'
)
self
.
assertEqual
(
response
.
data
[
'developer_message'
],
u'No data provided.'
)
def
test_post_bookmark_for_non_existing_block
(
self
):
def
test_post_bookmark_for_non_existing_block
(
self
):
...
@@ -302,13 +302,39 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
...
@@ -302,13 +302,39 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
)
)
self
.
assertEqual
(
self
.
assertEqual
(
response
.
data
[
'user_message'
],
response
.
data
[
'user_message'
],
u'
Block with usage_id: i4x://arbi/100/html/340ef1771a094090ad260ec940d04a21 not found
.'
u'
An error has occurred. Please try again
.'
)
)
self
.
assertEqual
(
self
.
assertEqual
(
response
.
data
[
'developer_message'
],
response
.
data
[
'developer_message'
],
u'Block with usage_id: i4x://arbi/100/html/340ef1771a094090ad260ec940d04a21 not found.'
u'Block with usage_id: i4x://arbi/100/html/340ef1771a094090ad260ec940d04a21 not found.'
)
)
@patch
(
'django.conf.settings.MAX_BOOKMARKS_PER_COURSE'
,
5
)
def
test_post_bookmark_when_max_bookmarks_already_exist
(
self
):
"""
Test that posting a bookmark for a block that does not exist returns a 400.
"""
max_bookmarks
=
settings
.
MAX_BOOKMARKS_PER_COURSE
__
,
blocks
,
__
=
self
.
create_course_with_bookmarks_count
(
max_bookmarks
)
with
self
.
assertNumQueries
(
8
):
# No queries for bookmark table.
response
=
self
.
send_post
(
client
=
self
.
client
,
url
=
reverse
(
'bookmarks'
),
data
=
{
'usage_id'
:
unicode
(
blocks
[
-
1
]
.
location
)},
expected_status
=
400
)
self
.
assertEqual
(
response
.
data
[
'user_message'
],
u'You can create up to {0} bookmarks.'
u' You must remove some bookmarks before you can add new ones.'
.
format
(
max_bookmarks
)
)
self
.
assertEqual
(
response
.
data
[
'developer_message'
],
u'You can create up to {0} bookmarks.'
u' You must remove some bookmarks before you can add new ones.'
.
format
(
max_bookmarks
)
)
def
test_unsupported_methods
(
self
):
def
test_unsupported_methods
(
self
):
"""
"""
Test that DELETE and PUT are not supported.
Test that DELETE and PUT are not supported.
...
...
openedx/core/djangoapps/bookmarks/views.py
View file @
3385d1e1
...
@@ -20,6 +20,8 @@ from rest_framework_oauth.authentication import OAuth2Authentication
...
@@ -20,6 +20,8 @@ from rest_framework_oauth.authentication import OAuth2Authentication
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
django.conf
import
settings
from
openedx.core.djangoapps.bookmarks.api
import
BookmarksLimitReachedError
from
openedx.core.lib.api.permissions
import
IsUserInUrl
from
openedx.core.lib.api.permissions
import
IsUserInUrl
...
@@ -33,6 +35,9 @@ from .serializers import BookmarkSerializer
...
@@ -33,6 +35,9 @@ from .serializers import BookmarkSerializer
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
# Default error message for user
DEFAULT_USER_MESSAGE
=
ugettext_noop
(
u'An error has occurred. Please try again.'
)
class
BookmarksPagination
(
DefaultPagination
):
class
BookmarksPagination
(
DefaultPagination
):
"""
"""
...
@@ -71,7 +76,7 @@ class BookmarksViewMixin(object):
...
@@ -71,7 +76,7 @@ class BookmarksViewMixin(object):
optional_fields
=
params
.
get
(
'fields'
,
''
)
.
split
(
','
)
optional_fields
=
params
.
get
(
'fields'
,
''
)
.
split
(
','
)
return
DEFAULT_FIELDS
+
[
field
for
field
in
optional_fields
if
field
in
OPTIONAL_FIELDS
]
return
DEFAULT_FIELDS
+
[
field
for
field
in
optional_fields
if
field
in
OPTIONAL_FIELDS
]
def
error_response
(
self
,
messag
e
,
error_status
=
status
.
HTTP_400_BAD_REQUEST
):
def
error_response
(
self
,
developer_message
,
user_message
=
Non
e
,
error_status
=
status
.
HTTP_400_BAD_REQUEST
):
"""
"""
Create and return a Response.
Create and return a Response.
...
@@ -80,10 +85,13 @@ class BookmarksViewMixin(object):
...
@@ -80,10 +85,13 @@ class BookmarksViewMixin(object):
and user_message fields.
and user_message fields.
status: The status of the response. Default is HTTP_400_BAD_REQUEST.
status: The status of the response. Default is HTTP_400_BAD_REQUEST.
"""
"""
if
not
user_message
:
user_message
=
developer_message
return
Response
(
return
Response
(
{
{
"developer_message"
:
message
,
"developer_message"
:
developer_
message
,
"user_message"
:
_
(
message
)
# pylint: disable=translation-of-non-string
"user_message"
:
_
(
user_
message
)
# pylint: disable=translation-of-non-string
},
},
status
=
error_status
status
=
error_status
)
)
...
@@ -219,25 +227,36 @@ class BookmarksListView(ListCreateAPIView, BookmarksViewMixin):
...
@@ -219,25 +227,36 @@ class BookmarksListView(ListCreateAPIView, BookmarksViewMixin):
POST /api/bookmarks/v1/bookmarks/
POST /api/bookmarks/v1/bookmarks/
Request data: {"usage_id": "<usage-id>"}
Request data: {"usage_id": "<usage-id>"}
"""
"""
if
not
request
.
data
:
if
not
request
.
data
:
return
self
.
error_response
(
ugettext_noop
(
u'No data provided.'
))
return
self
.
error_response
(
ugettext_noop
(
u'No data provided.'
)
,
DEFAULT_USER_MESSAGE
)
usage_id
=
request
.
data
.
get
(
'usage_id'
,
None
)
usage_id
=
request
.
data
.
get
(
'usage_id'
,
None
)
if
not
usage_id
:
if
not
usage_id
:
return
self
.
error_response
(
ugettext_noop
(
u'Parameter usage_id not provided.'
))
return
self
.
error_response
(
ugettext_noop
(
u'Parameter usage_id not provided.'
)
,
DEFAULT_USER_MESSAGE
)
try
:
try
:
usage_key
=
UsageKey
.
from_string
(
unquote_slashes
(
usage_id
))
usage_key
=
UsageKey
.
from_string
(
unquote_slashes
(
usage_id
))
except
InvalidKeyError
:
except
InvalidKeyError
:
error_message
=
ugettext_noop
(
u'Invalid usage_id: {usage_id}.'
)
.
format
(
usage_id
=
usage_id
)
error_message
=
ugettext_noop
(
u'Invalid usage_id: {usage_id}.'
)
.
format
(
usage_id
=
usage_id
)
log
.
error
(
error_message
)
log
.
error
(
error_message
)
return
self
.
error_response
(
error_message
)
return
self
.
error_response
(
error_message
,
DEFAULT_USER_MESSAGE
)
try
:
try
:
bookmark
=
api
.
create_bookmark
(
user
=
self
.
request
.
user
,
usage_key
=
usage_key
)
bookmark
=
api
.
create_bookmark
(
user
=
self
.
request
.
user
,
usage_key
=
usage_key
)
except
ItemNotFoundError
:
except
ItemNotFoundError
:
error_message
=
ugettext_noop
(
u'Block with usage_id: {usage_id} not found.'
)
.
format
(
usage_id
=
usage_id
)
error_message
=
ugettext_noop
(
u'Block with usage_id: {usage_id} not found.'
)
.
format
(
usage_id
=
usage_id
)
log
.
error
(
error_message
)
log
.
error
(
error_message
)
return
self
.
error_response
(
error_message
,
DEFAULT_USER_MESSAGE
)
except
BookmarksLimitReachedError
:
error_message
=
ugettext_noop
(
u'You can create up to {max_num_bookmarks_per_course} bookmarks.'
u' You must remove some bookmarks before you can add new ones.'
)
.
format
(
max_num_bookmarks_per_course
=
settings
.
MAX_BOOKMARKS_PER_COURSE
)
log
.
info
(
u'Attempted to create more than
%
s bookmarks'
,
settings
.
MAX_BOOKMARKS_PER_COURSE
)
return
self
.
error_response
(
error_message
)
return
self
.
error_response
(
error_message
)
return
Response
(
bookmark
,
status
=
status
.
HTTP_201_CREATED
)
return
Response
(
bookmark
,
status
=
status
.
HTTP_201_CREATED
)
...
@@ -301,7 +320,7 @@ class BookmarksDetailView(APIView, BookmarksViewMixin):
...
@@ -301,7 +320,7 @@ class BookmarksDetailView(APIView, BookmarksViewMixin):
except
InvalidKeyError
:
except
InvalidKeyError
:
error_message
=
ugettext_noop
(
u'Invalid usage_id: {usage_id}.'
)
.
format
(
usage_id
=
usage_id
)
error_message
=
ugettext_noop
(
u'Invalid usage_id: {usage_id}.'
)
.
format
(
usage_id
=
usage_id
)
log
.
error
(
error_message
)
log
.
error
(
error_message
)
return
self
.
error_response
(
error_message
,
status
.
HTTP_404_NOT_FOUND
)
return
self
.
error_response
(
error_message
,
error_status
=
status
.
HTTP_404_NOT_FOUND
)
def
get
(
self
,
request
,
username
=
None
,
usage_id
=
None
):
# pylint: disable=unused-argument
def
get
(
self
,
request
,
username
=
None
,
usage_id
=
None
):
# pylint: disable=unused-argument
"""
"""
...
@@ -323,7 +342,7 @@ class BookmarksDetailView(APIView, BookmarksViewMixin):
...
@@ -323,7 +342,7 @@ class BookmarksDetailView(APIView, BookmarksViewMixin):
u'Bookmark with usage_id: {usage_id} does not exist.'
u'Bookmark with usage_id: {usage_id} does not exist.'
)
.
format
(
usage_id
=
usage_id
)
)
.
format
(
usage_id
=
usage_id
)
log
.
error
(
error_message
)
log
.
error
(
error_message
)
return
self
.
error_response
(
error_message
,
status
.
HTTP_404_NOT_FOUND
)
return
self
.
error_response
(
error_message
,
error_status
=
status
.
HTTP_404_NOT_FOUND
)
return
Response
(
bookmark_data
)
return
Response
(
bookmark_data
)
...
@@ -343,6 +362,6 @@ class BookmarksDetailView(APIView, BookmarksViewMixin):
...
@@ -343,6 +362,6 @@ class BookmarksDetailView(APIView, BookmarksViewMixin):
u'Bookmark with usage_id: {usage_id} does not exist.'
u'Bookmark with usage_id: {usage_id} does not exist.'
)
.
format
(
usage_id
=
usage_id
)
)
.
format
(
usage_id
=
usage_id
)
log
.
error
(
error_message
)
log
.
error
(
error_message
)
return
self
.
error_response
(
error_message
,
status
.
HTTP_404_NOT_FOUND
)
return
self
.
error_response
(
error_message
,
error_status
=
status
.
HTTP_404_NOT_FOUND
)
return
Response
(
status
=
status
.
HTTP_204_NO_CONTENT
)
return
Response
(
status
=
status
.
HTTP_204_NO_CONTENT
)
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