Commit 1616de50 by muzaffaryousaf

Bookmarks API.

TNL-2180
parent 283623ab
......@@ -11,13 +11,13 @@ from .views import (
EnrollmentCourseDetailView
)
USERNAME_PATTERN = '(?P<username>[\w.@+-]+)'
urlpatterns = patterns(
'enrollment.views',
url(
r'^enrollment/{username},{course_key}$'.format(username=USERNAME_PATTERN,
course_key=settings.COURSE_ID_PATTERN),
r'^enrollment/{username},{course_key}$'.format(
username=settings.USERNAME_PATTERN, course_key=settings.COURSE_ID_PATTERN
),
EnrollmentView.as_view(),
name='courseenrollment'
),
......
"""
Bookmarks module.
"""
DEFAULT_FIELDS = [
'id',
'course_id',
'usage_id',
'created',
]
OPTIONAL_FIELDS = [
'display_name',
'path',
]
"""
Bookmarks Python API.
"""
from . import DEFAULT_FIELDS, OPTIONAL_FIELDS
from .models import Bookmark
from .serializers import BookmarkSerializer
def get_bookmark(user, usage_key, fields=None):
"""
Return data for a bookmark.
Arguments:
user (User): The user of the bookmark.
usage_key (UsageKey): The usage_key of the bookmark.
fields (list): List of field names the data should contain (optional).
Returns:
Dict.
Raises:
ObjectDoesNotExist: If a bookmark with the parameters does not exist.
"""
bookmark = Bookmark.objects.get(user=user, usage_key=usage_key)
return BookmarkSerializer(bookmark, context={'fields': fields}).data
def get_bookmarks(user, course_key=None, fields=None, serialized=True):
"""
Return data for bookmarks of a user.
Arguments:
user (User): The user of the bookmarks.
course_key (CourseKey): The course_key of the bookmarks (optional).
fields (list): List of field names the data should contain (optional).
N/A if serialized is False.
serialized (bool): Whether to return a queryset or a serialized list of dicts.
Default is True.
Returns:
List of dicts if serialized is True else queryset.
"""
bookmarks_queryset = Bookmark.objects.filter(user=user)
if course_key:
bookmarks_queryset = bookmarks_queryset.filter(course_key=course_key)
bookmarks_queryset = bookmarks_queryset.order_by('-created')
if serialized:
return BookmarkSerializer(bookmarks_queryset, context={'fields': fields}, many=True).data
return bookmarks_queryset
def create_bookmark(user, usage_key):
"""
Create a bookmark.
Arguments:
user (User): The user of the bookmark.
usage_key (UsageKey): The usage_key of the bookmark.
Returns:
Dict.
Raises:
ItemNotFoundError: If no block exists for the usage_key.
"""
bookmark = Bookmark.create({
'user': user,
'usage_key': usage_key
})
return BookmarkSerializer(bookmark, context={'fields': DEFAULT_FIELDS + OPTIONAL_FIELDS}).data
def delete_bookmark(user, usage_key):
"""
Delete a bookmark.
Arguments:
user (User): The user of the bookmark.
usage_key (UsageKey): The usage_key of the bookmark.
Returns:
Dict.
Raises:
ObjectDoesNotExist: If a bookmark with the parameters does not exist.
"""
bookmark = Bookmark.objects.get(user=user, usage_key=usage_key)
bookmark.delete()
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Bookmark'
db.create_table('bookmarks_bookmark', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('created', self.gf('model_utils.fields.AutoCreatedField')(default=datetime.datetime.now)),
('modified', self.gf('model_utils.fields.AutoLastModifiedField')(default=datetime.datetime.now)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
('course_key', self.gf('xmodule_django.models.CourseKeyField')(max_length=255, db_index=True)),
('usage_key', self.gf('xmodule_django.models.LocationKeyField')(max_length=255, db_index=True)),
('display_name', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
('path', self.gf('jsonfield.fields.JSONField')()),
))
db.send_create_signal('bookmarks', ['Bookmark'])
def backwards(self, orm):
# Deleting model 'Bookmark'
db.delete_table('bookmarks_bookmark')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'bookmarks.bookmark': {
'Meta': {'object_name': 'Bookmark'},
'course_key': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'display_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'path': ('jsonfield.fields.JSONField', [], {}),
'usage_key': ('xmodule_django.models.LocationKeyField', [], {'max_length': '255', 'db_index': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['bookmarks']
"""
Models for Bookmarks.
"""
from django.contrib.auth.models import User
from django.db import models
from jsonfield.fields import JSONField
from model_utils.models import TimeStampedModel
from xmodule.modulestore.django import modulestore
from xmodule_django.models import CourseKeyField, LocationKeyField
class Bookmark(TimeStampedModel):
"""
Bookmarks model.
"""
user = models.ForeignKey(User, db_index=True)
course_key = CourseKeyField(max_length=255, db_index=True)
usage_key = LocationKeyField(max_length=255, db_index=True)
display_name = models.CharField(max_length=255, default='', help_text='Display name of block')
path = JSONField(help_text='Path in course tree to the block')
@classmethod
def create(cls, bookmark_data):
"""
Create a Bookmark object.
Arguments:
bookmark_data (dict): The data to create the object with.
Returns:
A Bookmark object.
Raises:
ItemNotFoundError: If no block exists for the usage_key.
"""
usage_key = bookmark_data.pop('usage_key')
usage_key = usage_key.replace(course_key=modulestore().fill_in_run(usage_key.course_key))
block = modulestore().get_item(usage_key)
bookmark_data['course_key'] = usage_key.course_key
bookmark_data['display_name'] = block.display_name
bookmark_data['path'] = cls.get_path(block)
user = bookmark_data.pop('user')
bookmark, __ = cls.objects.get_or_create(usage_key=usage_key, user=user, defaults=bookmark_data)
return bookmark
@staticmethod
def get_path(block):
"""
Returns data for the path to the block in the course tree.
Arguments:
block (XBlock): The block whose path is required.
Returns:
list of dicts of the form {'usage_id': <usage_id>, 'display_name': <display_name>}.
"""
parent = block.get_parent()
parents_data = []
while parent is not None and parent.location.block_type not in ['course']:
parents_data.append({"display_name": parent.display_name, "usage_id": unicode(parent.location)})
parent = parent.get_parent()
parents_data.reverse()
return parents_data
"""
Serializers for Bookmarks.
"""
from rest_framework import serializers
from . import DEFAULT_FIELDS
from .models import Bookmark
class BookmarkSerializer(serializers.ModelSerializer):
"""
Serializer for the Bookmark model.
"""
id = serializers.SerializerMethodField('resource_id') # pylint: disable=invalid-name
course_id = serializers.Field(source='course_key')
usage_id = serializers.Field(source='usage_key')
path = serializers.Field(source='path')
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
try:
fields = kwargs['context'].pop('fields', DEFAULT_FIELDS) or DEFAULT_FIELDS
except KeyError:
fields = DEFAULT_FIELDS
# Instantiate the superclass normally
super(BookmarkSerializer, self).__init__(*args, **kwargs)
# Drop any fields that are not specified in the `fields` argument.
required_fields = set(fields)
all_fields = set(self.fields.keys())
for field_name in all_fields - required_fields:
self.fields.pop(field_name)
class Meta(object):
""" Serializer metadata. """
model = Bookmark
fields = (
'id',
'course_id',
'usage_id',
'display_name',
'path',
'created',
)
def resource_id(self, bookmark):
"""
Return the REST resource id: {username,usage_id}.
"""
return "{0},{1}".format(bookmark.user.username, bookmark.usage_key)
"""
Bookmarks service.
"""
import logging
from django.core.exceptions import ObjectDoesNotExist
from xmodule.modulestore.exceptions import ItemNotFoundError
from . import DEFAULT_FIELDS, OPTIONAL_FIELDS, api
log = logging.getLogger(__name__)
class BookmarksService(object):
"""
A service that provides access to the bookmarks API.
"""
def __init__(self, user, **kwargs):
super(BookmarksService, self).__init__(**kwargs)
self._user = user
def bookmarks(self, course_key):
"""
Return a list of bookmarks for the course for the current user.
Arguments:
course_key: CourseKey of the course for which to retrieve the user's bookmarks for.
Returns:
list of dict:
"""
return api.get_bookmarks(self._user, course_key=course_key, fields=DEFAULT_FIELDS + OPTIONAL_FIELDS)
def is_bookmarked(self, usage_key):
"""
Return whether the block has been bookmarked by the user.
Arguments:
usage_key: UsageKey of the block.
Returns:
Bool
"""
try:
api.get_bookmark(user=self._user, usage_key=usage_key)
except ObjectDoesNotExist:
log.error(u'Bookmark with usage_id: %s does not exist.', usage_key)
return False
return True
def set_bookmarked(self, usage_key):
"""
Adds a bookmark for the block.
Arguments:
usage_key: UsageKey of the block.
Returns:
Bool indicating whether the bookmark was added.
"""
try:
api.create_bookmark(user=self._user, usage_key=usage_key)
except ItemNotFoundError:
log.error(u'Block with usage_id: %s not found.', usage_key)
return False
return True
def unset_bookmarked(self, usage_key):
"""
Removes the bookmark for the block.
Arguments:
usage_key: UsageKey of the block.
Returns:
Bool indicating whether the bookmark was removed.
"""
try:
api.delete_bookmark(self._user, usage_key=usage_key)
except ObjectDoesNotExist:
log.error(u'Bookmark with usage_id: %s does not exist.', usage_key)
return False
return True
"""
Factories for Bookmark models.
"""
from factory.django import DjangoModelFactory
from factory import SubFactory
from functools import partial
from student.tests.factories import UserFactory
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from ..models import Bookmark
COURSE_KEY = SlashSeparatedCourseKey(u'edX', u'test_course', u'test')
LOCATION = partial(COURSE_KEY.make_usage_key, u'problem')
class BookmarkFactory(DjangoModelFactory):
""" Simple factory class for generating Bookmark """
FACTORY_FOR = Bookmark
user = SubFactory(UserFactory)
course_key = COURSE_KEY
usage_key = LOCATION('usage_id')
display_name = ""
path = list()
"""
Tests for bookmarks api.
"""
from django.core.exceptions import ObjectDoesNotExist
from opaque_keys.edx.keys import UsageKey
from student.tests.factories import UserFactory
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from .factories import BookmarkFactory
from .. import api, DEFAULT_FIELDS, OPTIONAL_FIELDS
from ..models import Bookmark
class BookmarksAPITests(ModuleStoreTestCase):
"""
These tests cover the parts of the API methods.
"""
def setUp(self):
super(BookmarksAPITests, self).setUp()
self.user = UserFactory.create(password='test')
self.other_user = UserFactory.create(password='test')
self.course = CourseFactory.create(display_name='An Introduction to API Testing')
self.course_id = unicode(self.course.id)
self.chapter = ItemFactory.create(
parent_location=self.course.location, category='chapter', display_name='Week 1'
)
self.sequential = ItemFactory.create(
parent_location=self.chapter.location, category='sequential', display_name='Lesson 1'
)
self.vertical = ItemFactory.create(
parent_location=self.sequential.location, category='vertical', display_name='Subsection 1'
)
self.vertical_1 = ItemFactory.create(
parent_location=self.sequential.location, category='vertical', display_name='Subsection 1.1'
)
self.bookmark = BookmarkFactory.create(
user=self.user,
course_key=self.course_id,
usage_key=self.vertical.location,
display_name=self.vertical.display_name
)
self.course_2 = CourseFactory.create(display_name='An Introduction to API Testing 2')
self.chapter_2 = ItemFactory.create(
parent_location=self.course_2.location, category='chapter', display_name='Week 2'
)
self.sequential_2 = ItemFactory.create(
parent_location=self.chapter_2.location, category='sequential', display_name='Lesson 2'
)
self.vertical_2 = ItemFactory.create(
parent_location=self.sequential_2.location, category='vertical', display_name='Subsection 2'
)
self.bookmark_2 = BookmarkFactory.create(
user=self.user,
course_key=self.course_2.id,
usage_key=self.vertical_2.location,
display_name=self.vertical_2.display_name
)
self.all_fields = DEFAULT_FIELDS + OPTIONAL_FIELDS
def assert_bookmark_response(self, response_data, bookmark, optional_fields=False):
"""
Determines if the given response data (dict) matches the given bookmark.
"""
self.assertEqual(response_data['id'], '%s,%s' % (self.user.username, unicode(bookmark.usage_key)))
self.assertEqual(response_data['course_id'], unicode(bookmark.course_key))
self.assertEqual(response_data['usage_id'], unicode(bookmark.usage_key))
self.assertIsNotNone(response_data['created'])
if optional_fields:
self.assertEqual(response_data['display_name'], bookmark.display_name)
self.assertEqual(response_data['path'], bookmark.path)
def test_get_bookmark(self):
"""
Verifies that get_bookmark returns data as expected.
"""
bookmark_data = api.get_bookmark(user=self.user, usage_key=self.vertical.location)
self.assert_bookmark_response(bookmark_data, self.bookmark)
# With Optional fields.
bookmark_data = api.get_bookmark(
user=self.user,
usage_key=self.vertical.location,
fields=self.all_fields
)
self.assert_bookmark_response(bookmark_data, self.bookmark, optional_fields=True)
def test_get_bookmark_raises_error(self):
"""
Verifies that get_bookmark raises error as expected.
"""
with self.assertRaises(ObjectDoesNotExist):
api.get_bookmark(user=self.other_user, usage_key=self.vertical.location)
def test_get_bookmarks(self):
"""
Verifies that get_bookmarks returns data as expected.
"""
# Without course key.
bookmarks_data = api.get_bookmarks(user=self.user)
self.assertEqual(len(bookmarks_data), 2)
# Assert them in ordered manner.
self.assert_bookmark_response(bookmarks_data[0], self.bookmark_2)
self.assert_bookmark_response(bookmarks_data[1], self.bookmark)
# With course key.
bookmarks_data = api.get_bookmarks(user=self.user, course_key=self.course.id)
self.assertEqual(len(bookmarks_data), 1)
self.assert_bookmark_response(bookmarks_data[0], self.bookmark)
# With optional fields.
bookmarks_data = api.get_bookmarks(user=self.user, course_key=self.course.id, fields=self.all_fields)
self.assertEqual(len(bookmarks_data), 1)
self.assert_bookmark_response(bookmarks_data[0], self.bookmark, optional_fields=True)
# Without Serialized.
bookmarks = api.get_bookmarks(user=self.user, course_key=self.course.id, serialized=False)
self.assertEqual(len(bookmarks), 1)
self.assertTrue(bookmarks.model is Bookmark) # pylint: disable=no-member
self.assertEqual(bookmarks[0], self.bookmark)
def test_create_bookmark(self):
"""
Verifies that create_bookmark create & returns data as expected.
"""
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 1)
api.create_bookmark(user=self.user, usage_key=self.vertical_1.location)
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2)
def test_create_bookmark_do_not_create_duplicates(self):
"""
Verifies that create_bookmark do not create duplicate bookmarks.
"""
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 1)
bookmark_data = api.create_bookmark(user=self.user, usage_key=self.vertical_1.location)
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2)
bookmark_data_2 = api.create_bookmark(user=self.user, usage_key=self.vertical_1.location)
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2)
self.assertEqual(bookmark_data, bookmark_data_2)
def test_create_bookmark_raises_error(self):
"""
Verifies that create_bookmark raises error as expected.
"""
with self.assertRaises(ItemNotFoundError):
api.create_bookmark(user=self.user, usage_key=UsageKey.from_string('i4x://brb/100/html/340ef1771a0940'))
def test_delete_bookmark(self):
"""
Verifies that delete_bookmark removes bookmark as expected.
"""
self.assertEqual(len(api.get_bookmarks(user=self.user)), 2)
api.delete_bookmark(user=self.user, usage_key=self.vertical.location)
bookmarks_data = api.get_bookmarks(user=self.user)
self.assertEqual(len(bookmarks_data), 1)
self.assertNotEqual(unicode(self.vertical.location), bookmarks_data[0]['usage_id'])
def test_delete_bookmark_raises_error(self):
"""
Verifies that delete_bookmark raises error as expected.
"""
with self.assertRaises(ObjectDoesNotExist):
api.delete_bookmark(user=self.other_user, usage_key=self.vertical.location)
"""
Tests for Bookmarks models.
"""
from bookmarks.models import Bookmark
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
class BookmarkModelTest(ModuleStoreTestCase):
"""
Test the Bookmark model.
"""
def setUp(self):
super(BookmarkModelTest, self).setUp()
self.user = UserFactory.create(password='test')
self.course = CourseFactory.create(display_name='An Introduction to API Testing')
self.course_id = unicode(self.course.id)
self.chapter = ItemFactory.create(
parent_location=self.course.location, category='chapter', display_name='Week 1'
)
self.sequential = ItemFactory.create(
parent_location=self.chapter.location, category='sequential', display_name='Lesson 1'
)
self.vertical = ItemFactory.create(
parent_location=self.sequential.location, category='vertical', display_name='Subsection 1'
)
self.vertical_2 = ItemFactory.create(
parent_location=self.sequential.location, category='vertical', display_name='Subsection 2'
)
self.path = [
{'display_name': self.chapter.display_name, 'usage_id': unicode(self.chapter.location)},
{'display_name': self.sequential.display_name, 'usage_id': unicode(self.sequential.location)}
]
def get_bookmark_data(self, block):
"""
Returns bookmark data for testing.
"""
return {
'user': self.user,
'course_key': self.course.id,
'usage_key': block.location,
'display_name': block.display_name,
}
def assert_valid_bookmark(self, bookmark_object, bookmark_data):
"""
Check if the given data matches the specified bookmark.
"""
self.assertEqual(bookmark_object.user, self.user)
self.assertEqual(bookmark_object.course_key, bookmark_data['course_key'])
self.assertEqual(bookmark_object.usage_key, self.vertical.location)
self.assertEqual(bookmark_object.display_name, bookmark_data['display_name'])
self.assertEqual(bookmark_object.path, self.path)
self.assertIsNotNone(bookmark_object.created)
def test_create_bookmark_success(self):
"""
Tests creation of bookmark.
"""
bookmark_data = self.get_bookmark_data(self.vertical)
bookmark_object = Bookmark.create(bookmark_data)
self.assert_valid_bookmark(bookmark_object, bookmark_data)
def test_get_path(self):
"""
Tests creation of path with given block.
"""
path_object = Bookmark.get_path(block=self.vertical)
self.assertEqual(path_object, self.path)
def test_get_path_with_given_chapter_block(self):
"""
Tests path for chapter level block.
"""
path_object = Bookmark.get_path(block=self.chapter)
self.assertEqual(len(path_object), 0)
def test_get_path_with_given_sequential_block(self):
"""
Tests path for sequential level block.
"""
path_object = Bookmark.get_path(block=self.sequential)
self.assertEqual(len(path_object), 1)
self.assertEqual(path_object[0], self.path[0])
def test_get_path_returns_empty_list_for_unreachable_parent(self):
"""
Tests get_path returns empty list if block has no parent.
"""
path = Bookmark.get_path(block=self.course)
self.assertEqual(path, [])
"""
Tests for bookmark services.
"""
from opaque_keys.edx.keys import UsageKey
from .factories import BookmarkFactory
from ..services import BookmarksService
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
class BookmarksAPITests(ModuleStoreTestCase):
"""
Tests the Bookmarks service.
"""
def setUp(self):
super(BookmarksAPITests, self).setUp()
self.user = UserFactory.create(password='test')
self.other_user = UserFactory.create(password='test')
self.course = CourseFactory.create(display_name='An Introduction to API Testing')
self.course_id = unicode(self.course.id)
self.chapter = ItemFactory.create(
parent_location=self.course.location, category='chapter', display_name='Week 1'
)
self.sequential = ItemFactory.create(
parent_location=self.chapter.location, category='sequential', display_name='Lesson 1'
)
self.vertical = ItemFactory.create(
parent_location=self.sequential.location, category='vertical', display_name='Subsection 1'
)
self.vertical_1 = ItemFactory.create(
parent_location=self.sequential.location, category='vertical', display_name='Subsection 1.1'
)
self.bookmark = BookmarkFactory.create(
user=self.user,
course_key=self.course_id,
usage_key=self.vertical.location,
display_name=self.vertical.display_name
)
self.bookmark_service = BookmarksService(user=self.user)
def assert_bookmark_response(self, response_data, bookmark):
"""
Determines if the given response data (dict) matches the specified bookmark.
"""
self.assertEqual(response_data['id'], '%s,%s' % (self.user.username, unicode(bookmark.usage_key)))
self.assertEqual(response_data['course_id'], unicode(bookmark.course_key))
self.assertEqual(response_data['usage_id'], unicode(bookmark.usage_key))
self.assertIsNotNone(response_data['created'])
self.assertEqual(response_data['display_name'], bookmark.display_name)
self.assertEqual(response_data['path'], bookmark.path)
def test_get_bookmarks(self):
"""
Verifies get_bookmarks returns data as expected.
"""
bookmarks_data = self.bookmark_service.bookmarks(course_key=self.course.id)
self.assertEqual(len(bookmarks_data), 1)
self.assert_bookmark_response(bookmarks_data[0], self.bookmark)
def test_is_bookmarked(self):
"""
Verifies is_bookmarked returns Bool as expected.
"""
self.assertTrue(self.bookmark_service.is_bookmarked(usage_key=self.vertical.location))
self.assertFalse(self.bookmark_service.is_bookmarked(usage_key=self.vertical_1.location))
# Get bookmark that does not exist.
bookmark_service = BookmarksService(self.other_user)
self.assertFalse(bookmark_service.is_bookmarked(usage_key=self.vertical.location))
def test_set_bookmarked(self):
"""
Verifies set_bookmarked returns Bool as expected.
"""
# Assert False for item that does not exist.
self.assertFalse(
self.bookmark_service.set_bookmarked(usage_key=UsageKey.from_string("i4x://ed/ed/ed/interactive"))
)
self.assertTrue(self.bookmark_service.set_bookmarked(usage_key=self.vertical_1.location))
def test_unset_bookmarked(self):
"""
Verifies unset_bookmarked returns Bool as expected.
"""
self.assertFalse(
self.bookmark_service.unset_bookmarked(usage_key=UsageKey.from_string("i4x://ed/ed/ed/interactive"))
)
self.assertTrue(self.bookmark_service.unset_bookmarked(usage_key=self.vertical.location))
"""
URL routes for the bookmarks app.
"""
from django.conf import settings
from django.conf.urls import patterns, url
from .views import BookmarksListView, BookmarksDetailView
urlpatterns = patterns(
'bookmarks',
url(
r'^v1/bookmarks/$',
BookmarksListView.as_view(),
name='bookmarks'
),
url(
r'^v1/bookmarks/{username},{usage_key}/$'.format(
username=settings.USERNAME_PATTERN,
usage_key=settings.USAGE_ID_PATTERN
),
BookmarksDetailView.as_view(),
name='bookmarks_detail'
),
)
......@@ -6,17 +6,16 @@ from django.conf import settings
from .views import UserDetail, UserCourseEnrollmentsList, UserCourseStatus
USERNAME_PATTERN = r'(?P<username>[\w.+-]+)'
urlpatterns = patterns(
'mobile_api.users.views',
url('^' + USERNAME_PATTERN + '$', UserDetail.as_view(), name='user-detail'),
url('^' + settings.USERNAME_PATTERN + '$', UserDetail.as_view(), name='user-detail'),
url(
'^' + USERNAME_PATTERN + '/course_enrollments/$',
'^' + settings.USERNAME_PATTERN + '/course_enrollments/$',
UserCourseEnrollmentsList.as_view(),
name='courseenrollment-detail'
),
url('^{}/course_status_info/{}'.format(USERNAME_PATTERN, settings.COURSE_ID_PATTERN),
url('^{}/course_status_info/{}'.format(settings.USERNAME_PATTERN, settings.COURSE_ID_PATTERN),
UserCourseStatus.as_view(),
name='user-course-status')
)
......@@ -574,6 +574,7 @@ USAGE_KEY_PATTERN = r'(?P<usage_key_string>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@
ASSET_KEY_PATTERN = r'(?P<asset_key_string>(?:/?c4x(:/)?/[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))'
USAGE_ID_PATTERN = r'(?P<usage_id>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))'
USERNAME_PATTERN = r'(?P<username>[\w.@+-]+)'
############################## EVENT TRACKING #################################
LMS_SEGMENT_KEY = None
......@@ -1906,6 +1907,9 @@ INSTALLED_APPS = (
'xblock_django',
# Bookmarks
'bookmarks',
# programs support
'openedx.core.djangoapps.programs',
......
......@@ -89,6 +89,9 @@ urlpatterns = (
# User API endpoints
url(r'^api/user/', include('openedx.core.djangoapps.user_api.urls')),
# Bookmarks API endpoints
url(r'^api/bookmarks/', include('bookmarks.urls')),
# Profile Images API endpoints
url(r'^api/profile_images/', include('openedx.core.djangoapps.profile_images.urls')),
......
......@@ -9,18 +9,18 @@ NOTE: These views are deprecated. These routes are superseded by
from django.conf.urls import patterns, url
from .views import ProfileImageUploadView, ProfileImageRemoveView
from django.conf import settings
USERNAME_PATTERN = r'(?P<username>[\w.+-]+)'
urlpatterns = patterns(
'',
url(
r'^v1/' + USERNAME_PATTERN + '/upload$',
r'^v1/' + settings.USERNAME_PATTERN + '/upload$',
ProfileImageUploadView.as_view(),
name="profile_image_upload"
),
url(
r'^v1/' + USERNAME_PATTERN + '/remove$',
r'^v1/' + settings.USERNAME_PATTERN + '/remove$',
ProfileImageRemoveView.as_view(),
name="profile_image_remove"
),
......
......@@ -8,6 +8,8 @@ from ..profile_images.views import ProfileImageView
from .accounts.views import AccountView
from .preferences.views import PreferencesView, PreferencesDetailView
from django.conf.urls import patterns, url
USERNAME_PATTERN = r'(?P<username>[\w.+-]+)'
urlpatterns = patterns(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment