Commit 1616de50 by muzaffaryousaf

Bookmarks API.

TNL-2180
parent 283623ab
...@@ -11,13 +11,13 @@ from .views import ( ...@@ -11,13 +11,13 @@ from .views import (
EnrollmentCourseDetailView EnrollmentCourseDetailView
) )
USERNAME_PATTERN = '(?P<username>[\w.@+-]+)'
urlpatterns = patterns( urlpatterns = patterns(
'enrollment.views', 'enrollment.views',
url( url(
r'^enrollment/{username},{course_key}$'.format(username=USERNAME_PATTERN, r'^enrollment/{username},{course_key}$'.format(
course_key=settings.COURSE_ID_PATTERN), username=settings.USERNAME_PATTERN, course_key=settings.COURSE_ID_PATTERN
),
EnrollmentView.as_view(), EnrollmentView.as_view(),
name='courseenrollment' 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 ...@@ -6,17 +6,16 @@ from django.conf import settings
from .views import UserDetail, UserCourseEnrollmentsList, UserCourseStatus from .views import UserDetail, UserCourseEnrollmentsList, UserCourseStatus
USERNAME_PATTERN = r'(?P<username>[\w.+-]+)'
urlpatterns = patterns( urlpatterns = patterns(
'mobile_api.users.views', 'mobile_api.users.views',
url('^' + USERNAME_PATTERN + '$', UserDetail.as_view(), name='user-detail'), url('^' + settings.USERNAME_PATTERN + '$', UserDetail.as_view(), name='user-detail'),
url( url(
'^' + USERNAME_PATTERN + '/course_enrollments/$', '^' + settings.USERNAME_PATTERN + '/course_enrollments/$',
UserCourseEnrollmentsList.as_view(), UserCourseEnrollmentsList.as_view(),
name='courseenrollment-detail' 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(), UserCourseStatus.as_view(),
name='user-course-status') name='user-course-status')
) )
...@@ -574,6 +574,7 @@ USAGE_KEY_PATTERN = r'(?P<usage_key_string>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@ ...@@ -574,6 +574,7 @@ USAGE_KEY_PATTERN = r'(?P<usage_key_string>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@
ASSET_KEY_PATTERN = r'(?P<asset_key_string>(?:/?c4x(:/)?/[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))' ASSET_KEY_PATTERN = r'(?P<asset_key_string>(?:/?c4x(:/)?/[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))'
USAGE_ID_PATTERN = r'(?P<usage_id>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))' USAGE_ID_PATTERN = r'(?P<usage_id>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))'
USERNAME_PATTERN = r'(?P<username>[\w.@+-]+)'
############################## EVENT TRACKING ################################# ############################## EVENT TRACKING #################################
LMS_SEGMENT_KEY = None LMS_SEGMENT_KEY = None
...@@ -1906,6 +1907,9 @@ INSTALLED_APPS = ( ...@@ -1906,6 +1907,9 @@ INSTALLED_APPS = (
'xblock_django', 'xblock_django',
# Bookmarks
'bookmarks',
# programs support # programs support
'openedx.core.djangoapps.programs', 'openedx.core.djangoapps.programs',
......
...@@ -89,6 +89,9 @@ urlpatterns = ( ...@@ -89,6 +89,9 @@ urlpatterns = (
# User API endpoints # User API endpoints
url(r'^api/user/', include('openedx.core.djangoapps.user_api.urls')), 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 # Profile Images API endpoints
url(r'^api/profile_images/', include('openedx.core.djangoapps.profile_images.urls')), 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 ...@@ -9,18 +9,18 @@ NOTE: These views are deprecated. These routes are superseded by
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from .views import ProfileImageUploadView, ProfileImageRemoveView from .views import ProfileImageUploadView, ProfileImageRemoveView
from django.conf import settings
USERNAME_PATTERN = r'(?P<username>[\w.+-]+)'
urlpatterns = patterns( urlpatterns = patterns(
'', '',
url( url(
r'^v1/' + USERNAME_PATTERN + '/upload$', r'^v1/' + settings.USERNAME_PATTERN + '/upload$',
ProfileImageUploadView.as_view(), ProfileImageUploadView.as_view(),
name="profile_image_upload" name="profile_image_upload"
), ),
url( url(
r'^v1/' + USERNAME_PATTERN + '/remove$', r'^v1/' + settings.USERNAME_PATTERN + '/remove$',
ProfileImageRemoveView.as_view(), ProfileImageRemoveView.as_view(),
name="profile_image_remove" name="profile_image_remove"
), ),
......
...@@ -8,6 +8,8 @@ from ..profile_images.views import ProfileImageView ...@@ -8,6 +8,8 @@ from ..profile_images.views import ProfileImageView
from .accounts.views import AccountView from .accounts.views import AccountView
from .preferences.views import PreferencesView, PreferencesDetailView from .preferences.views import PreferencesView, PreferencesDetailView
from django.conf.urls import patterns, url
USERNAME_PATTERN = r'(?P<username>[\w.+-]+)' USERNAME_PATTERN = r'(?P<username>[\w.+-]+)'
urlpatterns = patterns( 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