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
5ffc6ac2
Commit
5ffc6ac2
authored
Oct 24, 2014
by
John Eskew
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #4854 from edx/jeskew/assetstore_modulestore_work
Phase 1 of adding asset metadata saving to old Mongo
parents
3d1c54fe
b857a0ed
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
1215 additions
and
16 deletions
+1215
-16
common/lib/xmodule/xmodule/assetstore/__init__.py
+168
-0
common/lib/xmodule/xmodule/modulestore/__init__.py
+276
-3
common/lib/xmodule/xmodule/modulestore/mixed.py
+210
-1
common/lib/xmodule/xmodule/modulestore/mongo/base.py
+155
-3
common/lib/xmodule/xmodule/modulestore/split_mongo/mongo_connection.py
+5
-1
common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
+2
-0
common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
+398
-8
lms/envs/common.py
+1
-0
No files found.
common/lib/xmodule/xmodule/assetstore/__init__.py
0 → 100644
View file @
5ffc6ac2
"""
Classes representing asset & asset thumbnail metadata.
"""
from
datetime
import
datetime
from
contracts
import
contract
,
new_contract
from
opaque_keys.edx.keys
import
CourseKey
,
AssetKey
new_contract
(
'AssetKey'
,
AssetKey
)
new_contract
(
'datetime'
,
datetime
)
class
IncorrectAssetIdType
(
Exception
):
"""
Raised when the asset ID passed-in to create an AssetMetadata or
AssetThumbnailMetadata is of the wrong type.
"""
pass
class
AssetMetadata
(
object
):
"""
Stores the metadata associated with a particular course asset. The asset metadata gets stored
in the modulestore.
"""
TOP_LEVEL_ATTRS
=
[
'basename'
,
'internal_name'
,
'locked'
,
'contenttype'
,
'md5'
]
EDIT_INFO_ATTRS
=
[
'curr_version'
,
'prev_version'
,
'edited_by'
,
'edited_on'
]
ALLOWED_ATTRS
=
TOP_LEVEL_ATTRS
+
EDIT_INFO_ATTRS
# All AssetMetadata objects should have AssetLocators with this type.
ASSET_TYPE
=
'asset'
@contract
(
asset_id
=
'AssetKey'
,
basename
=
'str | unicode | None'
,
internal_name
=
'str | None'
,
locked
=
'bool | None'
,
contenttype
=
'str | unicode | None'
,
md5
=
'str | None'
,
curr_version
=
'str | None'
,
prev_version
=
'str | None'
)
def
__init__
(
self
,
asset_id
,
basename
=
None
,
internal_name
=
None
,
locked
=
None
,
contenttype
=
None
,
md5
=
None
,
curr_version
=
None
,
prev_version
=
None
):
"""
Construct a AssetMetadata object.
Arguments:
asset_id (AssetKey): Key identifying this particular asset.
basename (str): Original path to file at asset upload time.
internal_name (str): Name under which the file is stored internally.
locked (bool): If True, only course participants can access the asset.
contenttype (str): MIME type of the asset.
curr_version (str): Current version of the asset.
prev_version (str): Previous version of the asset.
"""
if
asset_id
.
asset_type
!=
self
.
ASSET_TYPE
:
raise
IncorrectAssetIdType
()
self
.
asset_id
=
asset_id
self
.
basename
=
basename
# Path w/o filename.
self
.
internal_name
=
internal_name
self
.
locked
=
locked
self
.
contenttype
=
contenttype
self
.
md5
=
md5
self
.
curr_version
=
curr_version
self
.
prev_version
=
prev_version
self
.
edited_by
=
None
self
.
edited_on
=
None
def
__repr__
(
self
):
return
"""AssetMetadata{!r}"""
.
format
((
self
.
asset_id
,
self
.
basename
,
self
.
internal_name
,
self
.
locked
,
self
.
contenttype
,
self
.
md5
,
self
.
curr_version
,
self
.
prev_version
,
self
.
edited_by
,
self
.
edited_on
))
def
update
(
self
,
attr_dict
):
"""
Set the attributes on the metadata. Ignore all those outside the known fields.
Arguments:
attr_dict: Prop, val dictionary of all attributes to set.
"""
for
attr
,
val
in
attr_dict
.
iteritems
():
if
attr
in
self
.
ALLOWED_ATTRS
:
setattr
(
self
,
attr
,
val
)
def
to_mongo
(
self
):
"""
Converts metadata properties into a MongoDB-storable dict.
"""
return
{
'filename'
:
self
.
asset_id
.
path
,
'basename'
:
self
.
basename
,
'internal_name'
:
self
.
internal_name
,
'locked'
:
self
.
locked
,
'contenttype'
:
self
.
contenttype
,
'md5'
:
self
.
md5
,
'edit_info'
:
{
'curr_version'
:
self
.
curr_version
,
'prev_version'
:
self
.
prev_version
,
'edited_by'
:
self
.
edited_by
,
'edited_on'
:
self
.
edited_on
}
}
@contract
(
asset_doc
=
'dict | None'
)
def
from_mongo
(
self
,
asset_doc
):
"""
Fill in all metadata fields from a MongoDB document.
The asset_id prop is initialized upon construction only.
"""
if
asset_doc
is
None
:
return
self
.
basename
=
asset_doc
[
'basename'
]
self
.
internal_name
=
asset_doc
[
'internal_name'
]
self
.
locked
=
asset_doc
[
'locked'
]
self
.
contenttype
=
asset_doc
[
'contenttype'
]
self
.
md5
=
asset_doc
[
'md5'
]
edit_info
=
asset_doc
[
'edit_info'
]
self
.
curr_version
=
edit_info
[
'curr_version'
]
self
.
prev_version
=
edit_info
[
'prev_version'
]
self
.
edited_by
=
edit_info
[
'edited_by'
]
self
.
edited_on
=
edit_info
[
'edited_on'
]
class
AssetThumbnailMetadata
(
object
):
"""
Stores the metadata associated with the thumbnail of a course asset.
"""
# All AssetThumbnailMetadata objects should have AssetLocators with this type.
ASSET_TYPE
=
'thumbnail'
@contract
(
asset_id
=
'AssetKey'
,
internal_name
=
'str | unicode | None'
)
def
__init__
(
self
,
asset_id
,
internal_name
=
None
):
"""
Construct a AssetThumbnailMetadata object.
Arguments:
asset_id (AssetKey): Key identifying this particular asset.
internal_name (str): Name under which the file is stored internally.
"""
if
asset_id
.
asset_type
!=
self
.
ASSET_TYPE
:
raise
IncorrectAssetIdType
()
self
.
asset_id
=
asset_id
self
.
internal_name
=
internal_name
def
__repr__
(
self
):
return
"""AssetMetadata{!r}"""
.
format
((
self
.
asset_id
,
self
.
internal_name
))
def
to_mongo
(
self
):
"""
Converts metadata properties into a MongoDB-storable dict.
"""
return
{
'filename'
:
self
.
asset_id
.
path
,
'internal_name'
:
self
.
internal_name
}
@contract
(
thumbnail_doc
=
'dict | None'
)
def
from_mongo
(
self
,
thumbnail_doc
):
"""
Fill in all metadata fields from a MongoDB document.
The asset_id prop is initialized upon construction only.
"""
if
thumbnail_doc
is
None
:
return
self
.
internal_name
=
thumbnail_doc
[
'internal_name'
]
common/lib/xmodule/xmodule/modulestore/__init__.py
View file @
5ffc6ac2
...
@@ -12,23 +12,30 @@ from uuid import uuid4
...
@@ -12,23 +12,30 @@ from uuid import uuid4
from
collections
import
namedtuple
,
defaultdict
from
collections
import
namedtuple
,
defaultdict
import
collections
import
collections
from
contextlib
import
contextmanager
from
contextlib
import
contextmanager
import
functools
import
threading
from
abc
import
ABCMeta
,
abstractmethod
from
abc
import
ABCMeta
,
abstractmethod
from
contracts
import
contract
,
new_contract
from
xblock.plugin
import
default_select
from
xblock.plugin
import
default_select
from
.exceptions
import
InvalidLocationError
,
InsufficientSpecificationError
from
.exceptions
import
InvalidLocationError
,
InsufficientSpecificationError
from
xmodule.errortracker
import
make_error_tracker
from
xmodule.errortracker
import
make_error_tracker
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
xmodule.assetstore
import
AssetMetadata
,
AssetThumbnailMetadata
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
,
AssetKey
from
opaque_keys.edx.locations
import
Location
# For import backwards compatibility
from
opaque_keys.edx.locations
import
Location
# For import backwards compatibility
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
xblock.runtime
import
Mixologist
from
xblock.runtime
import
Mixologist
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
import
functools
import
threading
log
=
logging
.
getLogger
(
'edx.modulestore'
)
log
=
logging
.
getLogger
(
'edx.modulestore'
)
new_contract
(
'CourseKey'
,
CourseKey
)
new_contract
(
'AssetKey'
,
AssetKey
)
new_contract
(
'AssetMetadata'
,
AssetMetadata
)
new_contract
(
'AssetThumbnailMetadata'
,
AssetThumbnailMetadata
)
class
ModuleStoreEnum
(
object
):
class
ModuleStoreEnum
(
object
):
"""
"""
...
@@ -740,6 +747,9 @@ class ModuleStoreReadBase(BulkOperationsMixin, ModuleStoreRead):
...
@@ -740,6 +747,9 @@ class ModuleStoreReadBase(BulkOperationsMixin, ModuleStoreRead):
"""
"""
@functools.wraps
(
func
)
@functools.wraps
(
func
)
def
wrapper
(
self
,
*
args
,
**
kwargs
):
def
wrapper
(
self
,
*
args
,
**
kwargs
):
"""
Wraps a method to memoize results.
"""
if
self
.
request_cache
:
if
self
.
request_cache
:
cache_key
=
'&'
.
join
([
hashvalue
(
arg
)
for
arg
in
args
])
cache_key
=
'&'
.
join
([
hashvalue
(
arg
)
for
arg
in
args
])
if
cache_key
in
self
.
request_cache
.
data
.
setdefault
(
func
.
__name__
,
{}):
if
cache_key
in
self
.
request_cache
.
data
.
setdefault
(
func
.
__name__
,
{}):
...
@@ -863,6 +873,269 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
...
@@ -863,6 +873,269 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
parent
.
children
.
append
(
item
.
location
)
parent
.
children
.
append
(
item
.
location
)
self
.
update_item
(
parent
,
user_id
)
self
.
update_item
(
parent
,
user_id
)
def
_find_course_assets
(
self
,
course_key
):
"""
Base method to override.
"""
raise
NotImplementedError
()
def
_find_course_asset
(
self
,
course_key
,
filename
,
get_thumbnail
=
False
):
"""
Internal; finds or creates course asset info -and- finds existing asset (or thumbnail) metadata.
Arguments:
course_key (CourseKey): course identifier
filename (str): filename of the asset or thumbnail
get_thumbnail (bool): True gets thumbnail data, False gets asset data
Returns:
Asset info for the course, index of asset/thumbnail in list (None if asset/thumbnail does not exist)
"""
course_assets
=
self
.
_find_course_assets
(
course_key
)
if
get_thumbnail
:
all_assets
=
course_assets
[
'thumbnails'
]
else
:
all_assets
=
course_assets
[
'assets'
]
# See if this asset already exists by checking the external_filename.
# Studio doesn't currently support using multiple course assets with the same filename.
# So use the filename as the unique identifier.
for
idx
,
asset
in
enumerate
(
all_assets
):
if
asset
[
'filename'
]
==
filename
:
return
course_assets
,
idx
return
course_assets
,
None
def
_save_asset_info
(
self
,
course_key
,
asset_metadata
,
user_id
,
thumbnail
=
False
):
"""
Base method to over-ride in modulestore.
"""
raise
NotImplementedError
()
@contract
(
course_key
=
'CourseKey'
,
asset_metadata
=
'AssetMetadata'
,
user_id
=
'str | unicode'
)
def
save_asset_metadata
(
self
,
course_key
,
asset_metadata
,
user_id
):
"""
Saves the asset metadata for a particular course's asset.
Arguments:
course_key (CourseKey): course identifier
asset_metadata (AssetMetadata): data about the course asset data
Returns:
True if metadata save was successful, else False
"""
return
self
.
_save_asset_info
(
course_key
,
asset_metadata
,
user_id
,
thumbnail
=
False
)
@contract
(
course_key
=
'CourseKey'
,
asset_thumbnail_metadata
=
'AssetThumbnailMetadata'
)
def
save_asset_thumbnail_metadata
(
self
,
course_key
,
asset_thumbnail_metadata
):
"""
Saves the asset thumbnail metadata for a particular course asset's thumbnail.
Arguments:
course_key (CourseKey): course identifier
asset_thumbnail_metadata (AssetThumbnailMetadata): data about the course asset thumbnail
Returns:
True if thumbnail metadata save was successful, else False
"""
return
self
.
_save_asset_info
(
course_key
,
asset_thumbnail_metadata
,
''
,
thumbnail
=
True
)
@contract
(
asset_key
=
'AssetKey'
)
def
_find_asset_info
(
self
,
asset_key
,
thumbnail
=
False
):
"""
Find the info for a particular course asset/thumbnail.
Arguments:
asset_key (AssetKey): key containing original asset filename
thumbnail (bool): True if finding thumbnail, False if finding asset metadata
Returns:
asset/thumbnail metadata (AssetMetadata/AssetThumbnailMetadata) -or- None if not found
"""
course_assets
,
asset_idx
=
self
.
_find_course_asset
(
asset_key
.
course_key
,
asset_key
.
path
,
thumbnail
)
if
asset_idx
is
None
:
return
None
if
thumbnail
:
info
=
'thumbnails'
mdata
=
AssetThumbnailMetadata
(
asset_key
,
asset_key
.
path
)
else
:
info
=
'assets'
mdata
=
AssetMetadata
(
asset_key
,
asset_key
.
path
)
all_assets
=
course_assets
[
info
]
mdata
.
from_mongo
(
all_assets
[
asset_idx
])
return
mdata
@contract
(
asset_key
=
'AssetKey'
)
def
find_asset_metadata
(
self
,
asset_key
):
"""
Find the metadata for a particular course asset.
Arguments:
asset_key (AssetKey): key containing original asset filename
Returns:
asset metadata (AssetMetadata) -or- None if not found
"""
return
self
.
_find_asset_info
(
asset_key
,
thumbnail
=
False
)
@contract
(
asset_key
=
'AssetKey'
)
def
find_asset_thumbnail_metadata
(
self
,
asset_key
):
"""
Find the metadata for a particular course asset.
Arguments:
asset_key (AssetKey): key containing original asset filename
Returns:
asset metadata (AssetMetadata) -or- None if not found
"""
return
self
.
_find_asset_info
(
asset_key
,
thumbnail
=
True
)
@contract
(
course_key
=
'CourseKey'
,
start
=
'int | None'
,
maxresults
=
'int | None'
,
sort
=
'list | None'
,
get_thumbnails
=
'bool'
)
def
_get_all_asset_metadata
(
self
,
course_key
,
start
=
0
,
maxresults
=-
1
,
sort
=
None
,
get_thumbnails
=
False
):
"""
Returns a list of static asset (or thumbnail) metadata for a course.
Args:
course_key (CourseKey): course identifier
start (int): optional - start at this asset number
maxresults (int): optional - return at most this many, -1 means no limit
sort (array): optional - None means no sort
(sort_by (str), sort_order (str))
sort_by - one of 'uploadDate' or 'displayname'
sort_order - one of 'ascending' or 'descending'
get_thumbnails (bool): True if getting thumbnail metadata, else getting asset metadata
Returns:
List of AssetMetadata or AssetThumbnailMetadata objects.
"""
course_assets
=
self
.
_find_course_assets
(
course_key
)
if
course_assets
is
None
:
# If no course assets are found, return None instead of empty list
# to distinguish zero assets from "not able to retrieve assets".
return
None
if
get_thumbnails
:
all_assets
=
course_assets
[
'thumbnails'
]
else
:
all_assets
=
course_assets
[
'assets'
]
# DO_NEXT: Add start/maxresults/sort functionality as part of https://openedx.atlassian.net/browse/PLAT-74
if
start
and
maxresults
and
sort
:
pass
ret_assets
=
[]
for
asset
in
all_assets
:
if
get_thumbnails
:
thumb
=
AssetThumbnailMetadata
(
course_key
.
make_asset_key
(
'thumbnail'
,
asset
[
'filename'
]),
internal_name
=
asset
[
'filename'
])
ret_assets
.
append
(
thumb
)
else
:
one_asset
=
AssetMetadata
(
course_key
.
make_asset_key
(
'asset'
,
asset
[
'filename'
]))
one_asset
.
from_mongo
(
asset
)
ret_assets
.
append
(
one_asset
)
return
ret_assets
@contract
(
course_key
=
'CourseKey'
,
start
=
'int | None'
,
maxresults
=
'int | None'
,
sort
=
'list | None'
)
def
get_all_asset_metadata
(
self
,
course_key
,
start
=
0
,
maxresults
=-
1
,
sort
=
None
):
"""
Returns a list of static assets for a course.
By default all assets are returned, but start and maxresults can be provided to limit the query.
Args:
course_key (CourseKey): course identifier
start (int): optional - start at this asset number
maxresults (int): optional - return at most this many, -1 means no limit
sort (array): optional - None means no sort
(sort_by (str), sort_order (str))
sort_by - one of 'uploadDate' or 'displayname'
sort_order - one of 'ascending' or 'descending'
Returns:
List of AssetMetadata objects.
"""
return
self
.
_get_all_asset_metadata
(
course_key
,
start
,
maxresults
,
sort
,
get_thumbnails
=
False
)
@contract
(
course_key
=
'CourseKey'
)
def
get_all_asset_thumbnail_metadata
(
self
,
course_key
):
"""
Returns a list of thumbnails for all course assets.
Args:
course_key (CourseKey): course identifier
Returns:
List of AssetThumbnailMetadata objects.
"""
return
self
.
_get_all_asset_metadata
(
course_key
,
get_thumbnails
=
True
)
def
set_asset_metadata_attrs
(
self
,
asset_key
,
attrs
,
user_id
):
"""
Base method to over-ride in modulestore.
"""
raise
NotImplementedError
()
def
_delete_asset_data
(
self
,
asset_key
,
thumbnail
=
False
):
"""
Base method to over-ride in modulestore.
"""
raise
NotImplementedError
()
@contract
(
asset_key
=
'AssetKey'
,
attr
=
str
)
def
set_asset_metadata_attr
(
self
,
asset_key
,
attr
,
value
,
user_id
):
"""
Add/set the given attr on the asset at the given location. Value can be any type which pymongo accepts.
Arguments:
asset_key (AssetKey): asset identifier
attr (str): which attribute to set
value: the value to set it to (any type pymongo accepts such as datetime, number, string)
Raises:
ItemNotFoundError if no such item exists
AttributeError is attr is one of the build in attrs.
"""
return
self
.
set_asset_metadata_attrs
(
asset_key
,
{
attr
:
value
},
user_id
)
@contract
(
asset_key
=
'AssetKey'
)
def
delete_asset_metadata
(
self
,
asset_key
):
"""
Deletes a single asset's metadata.
Arguments:
asset_key (AssetKey): locator containing original asset filename
Returns:
Number of asset metadata entries deleted (0 or 1)
"""
return
self
.
_delete_asset_data
(
asset_key
,
thumbnail
=
False
)
@contract
(
asset_key
=
'AssetKey'
)
def
delete_asset_thumbnail_metadata
(
self
,
asset_key
):
"""
Deletes a single asset's metadata.
Arguments:
asset_key (AssetKey): locator containing original asset filename
Returns:
Number of asset metadata entries deleted (0 or 1)
"""
return
self
.
_delete_asset_data
(
asset_key
,
thumbnail
=
True
)
@contract
(
source_course_key
=
'CourseKey'
,
dest_course_key
=
'CourseKey'
)
def
copy_all_asset_metadata
(
self
,
source_course_key
,
dest_course_key
):
"""
Copy all the course assets from source_course_key to dest_course_key.
Arguments:
source_course_key (CourseKey): identifier of course to copy from
dest_course_key (CourseKey): identifier of course to copy to
"""
pass
def
only_xmodules
(
identifier
,
entry_points
):
def
only_xmodules
(
identifier
,
entry_points
):
"""Only use entry_points that are supplied by the xmodule package"""
"""Only use entry_points that are supplied by the xmodule package"""
...
...
common/lib/xmodule/xmodule/modulestore/mixed.py
View file @
5ffc6ac2
...
@@ -9,10 +9,12 @@ import logging
...
@@ -9,10 +9,12 @@ import logging
from
contextlib
import
contextmanager
from
contextlib
import
contextmanager
import
itertools
import
itertools
import
functools
import
functools
from
contracts
import
contract
,
new_contract
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
,
AssetKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
xmodule.assetstore
import
AssetMetadata
,
AssetThumbnailMetadata
from
.
import
ModuleStoreWriteBase
from
.
import
ModuleStoreWriteBase
from
.
import
ModuleStoreEnum
from
.
import
ModuleStoreEnum
...
@@ -20,6 +22,10 @@ from .exceptions import ItemNotFoundError, DuplicateCourseError
...
@@ -20,6 +22,10 @@ from .exceptions import ItemNotFoundError, DuplicateCourseError
from
.draft_and_published
import
ModuleStoreDraftAndPublished
from
.draft_and_published
import
ModuleStoreDraftAndPublished
from
.split_migrator
import
SplitMigrator
from
.split_migrator
import
SplitMigrator
new_contract
(
'CourseKey'
,
CourseKey
)
new_contract
(
'AssetKey'
,
AssetKey
)
new_contract
(
'AssetMetadata'
,
AssetMetadata
)
new_contract
(
'AssetThumbnailMetadata'
,
AssetThumbnailMetadata
)
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -309,6 +315,209 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -309,6 +315,209 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
return
store
.
delete_course
(
course_key
,
user_id
)
return
store
.
delete_course
(
course_key
,
user_id
)
def
_save_asset_info
(
self
,
course_key
,
asset_metadata
,
user_id
,
thumbnail
=
False
):
"""
Base method to over-ride in modulestore.
"""
raise
NotImplementedError
()
def
_delete_asset_data
(
self
,
asset_key
,
thumbnail
=
False
):
"""
Base method to over-ride in modulestore.
"""
raise
NotImplementedError
()
def
_find_course_assets
(
self
,
course_key
):
"""
Base method to override.
"""
raise
NotImplementedError
()
@contract
(
course_key
=
'CourseKey'
,
asset_metadata
=
'AssetMetadata'
)
def
save_asset_metadata
(
self
,
course_key
,
asset_metadata
,
user_id
):
"""
Saves the asset metadata for a particular course's asset.
Args:
course_key (CourseKey): course identifier
asset_metadata (AssetMetadata): data about the course asset data
Returns:
bool: True if metadata save was successful, else False
"""
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
return
store
.
save_asset_metadata
(
course_key
,
asset_metadata
,
user_id
)
@contract
(
course_key
=
'CourseKey'
,
asset_thumbnail_metadata
=
'AssetThumbnailMetadata'
)
def
save_asset_thumbnail_metadata
(
self
,
course_key
,
asset_thumbnail_metadata
):
"""
Saves the asset thumbnail metadata for a particular course asset's thumbnail.
Arguments:
course_key (CourseKey): course identifier
asset_thumbnail_metadata (AssetThumbnailMetadata): data about the course asset thumbnail
Returns:
True if thumbnail metadata save was successful, else False
"""
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
return
store
.
save_asset_metadata
(
course_key
,
asset_thumbnail_metadata
)
@contract
(
asset_key
=
'AssetKey'
)
def
find_asset_metadata
(
self
,
asset_key
):
"""
Find the metadata for a particular course asset.
Args:
asset_key (AssetKey): locator containing original asset filename
Returns:
asset metadata (AssetMetadata) -or- None if not found
"""
store
=
self
.
_get_modulestore_for_courseid
(
asset_key
.
course_key
)
return
store
.
find_asset_metadata
(
asset_key
)
@contract
(
asset_key
=
'AssetKey'
)
def
find_asset_thumbnail_metadata
(
self
,
asset_key
):
"""
Find the metadata for a particular course asset.
Arguments:
asset_key (AssetKey): key containing original asset filename
Returns:
asset metadata (AssetMetadata) -or- None if not found
"""
store
=
self
.
_get_modulestore_for_courseid
(
asset_key
.
course_key
)
return
store
.
find_asset_thumbnail_metadata
(
asset_key
)
@contract
(
course_key
=
'CourseKey'
,
start
=
int
,
maxresults
=
int
,
sort
=
'list | None'
)
def
get_all_asset_metadata
(
self
,
course_key
,
start
=
0
,
maxresults
=-
1
,
sort
=
None
):
"""
Returns a list of static assets for a course.
By default all assets are returned, but start and maxresults can be provided to limit the query.
Args:
course_key (CourseKey): course identifier
start (int): optional - start at this asset number
maxresults (int): optional - return at most this many, -1 means no limit
sort (array): optional - None means no sort
(sort_by (str), sort_order (str))
sort_by - one of 'uploadDate' or 'displayname'
sort_order - one of 'ascending' or 'descending'
Returns:
List of asset data dictionaries, which have the following keys:
asset_key (AssetKey): asset identifier
displayname: The human-readable name of the asset
uploadDate (datetime.datetime): The date and time that the file was uploaded
contentType: The mimetype string of the asset
md5: An md5 hash of the asset content
"""
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
return
store
.
get_all_asset_metadata
(
course_key
,
start
,
maxresults
,
sort
)
@contract
(
course_key
=
'CourseKey'
)
def
get_all_asset_thumbnail_metadata
(
self
,
course_key
):
"""
Returns a list of thumbnails for all course assets.
Args:
course_key (CourseKey): course identifier
Returns:
List of AssetThumbnailMetadata objects.
"""
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
return
store
.
get_all_asset_thumbnail_metadata
(
course_key
)
@contract
(
asset_key
=
'AssetKey'
)
def
delete_asset_metadata
(
self
,
asset_key
):
"""
Deletes a single asset's metadata.
Arguments:
asset_id (AssetKey): locator containing original asset filename
Returns:
Number of asset metadata entries deleted (0 or 1)
"""
store
=
self
.
_get_modulestore_for_courseid
(
asset_key
.
course_key
)
return
store
.
delete_asset_metadata
(
asset_key
)
@contract
(
asset_key
=
'AssetKey'
)
def
delete_asset_thumbnail_metadata
(
self
,
asset_key
):
"""
Deletes a single asset's metadata.
Arguments:
asset_key (AssetKey): locator containing original asset filename
Returns:
Number of asset metadata entries deleted (0 or 1)
"""
store
=
self
.
_get_modulestore_for_courseid
(
asset_key
.
course_key
)
return
store
.
delete_asset_metadata
(
asset_key
)
@contract
(
course_key
=
'CourseKey'
)
def
delete_all_asset_metadata
(
self
,
course_key
):
"""
Delete all of the assets which use this course_key as an identifier.
Arguments:
course_key (CourseKey): course_identifier
"""
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
return
store
.
delete_all_asset_metadata
(
course_key
)
@contract
(
source_course_key
=
'CourseKey'
,
dest_course_key
=
'CourseKey'
)
def
copy_all_asset_metadata
(
self
,
source_course_key
,
dest_course_key
):
"""
Copy all the course assets from source_course_key to dest_course_key.
Arguments:
source_course_key (CourseKey): identifier of course to copy from
dest_course_key (CourseKey): identifier of course to copy to
"""
# When implementing this in https://openedx.atlassian.net/browse/PLAT-78 , consider this:
# Check the modulestores of both the source and dest course_keys. If in different modulestores,
# export all asset data from one modulestore and import it into the dest one.
store
=
self
.
_get_modulestore_for_courseid
(
source_course_key
)
return
store
.
copy_all_asset_metadata
(
source_course_key
,
dest_course_key
)
@contract
(
asset_key
=
'AssetKey'
,
attr
=
str
)
def
set_asset_metadata_attr
(
self
,
asset_key
,
attr
,
value
,
user_id
):
"""
Add/set the given attr on the asset at the given location. Value can be any type which pymongo accepts.
Arguments:
asset_key (AssetKey): asset identifier
attr (str): which attribute to set
value: the value to set it to (any type pymongo accepts such as datetime, number, string)
Raises:
NotFoundError if no such item exists
AttributeError is attr is one of the build in attrs.
"""
store
=
self
.
_get_modulestore_for_courseid
(
asset_key
.
course_key
)
return
store
.
set_asset_metadata_attrs
(
asset_key
,
attr
,
value
,
user_id
)
@contract
(
asset_key
=
'AssetKey'
,
attr_dict
=
dict
)
def
set_asset_metadata_attrs
(
self
,
asset_key
,
attr_dict
,
user_id
):
"""
Add/set the given dict of attrs on the asset at the given location. Value can be any type which pymongo accepts.
Arguments:
asset_key (AssetKey): asset identifier
attr_dict (dict): attribute/value pairs to set
Raises:
NotFoundError if no such item exists
AttributeError is attr is one of the build in attrs.
"""
store
=
self
.
_get_modulestore_for_courseid
(
asset_key
.
course_key
)
return
store
.
set_asset_metadata_attrs
(
asset_key
,
attr_dict
,
user_id
)
@strip_key
@strip_key
def
get_parent_location
(
self
,
location
,
**
kwargs
):
def
get_parent_location
(
self
,
location
,
**
kwargs
):
"""
"""
...
...
common/lib/xmodule/xmodule/modulestore/mongo/base.py
View file @
5ffc6ac2
...
@@ -24,6 +24,7 @@ from fs.osfs import OSFS
...
@@ -24,6 +24,7 @@ from fs.osfs import OSFS
from
path
import
path
from
path
import
path
from
datetime
import
datetime
from
datetime
import
datetime
from
pytz
import
UTC
from
pytz
import
UTC
from
contracts
import
contract
,
new_contract
from
importlib
import
import_module
from
importlib
import
import_module
from
xmodule.errortracker
import
null_error_tracker
,
exc_info_to_str
from
xmodule.errortracker
import
null_error_tracker
,
exc_info_to_str
...
@@ -41,12 +42,18 @@ from xmodule.modulestore.inheritance import InheritanceMixin, inherit_metadata,
...
@@ -41,12 +42,18 @@ from xmodule.modulestore.inheritance import InheritanceMixin, inherit_metadata,
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locator
import
CourseLocator
from
opaque_keys.edx.locator
import
CourseLocator
from
opaque_keys.edx.keys
import
UsageKey
,
CourseKey
from
opaque_keys.edx.keys
import
UsageKey
,
CourseKey
,
AssetKey
from
xmodule.exceptions
import
HeartbeatFailure
from
xmodule.exceptions
import
HeartbeatFailure
from
xmodule.modulestore.edit_info
import
EditInfoRuntimeMixin
from
xmodule.modulestore.edit_info
import
EditInfoRuntimeMixin
from
xmodule.assetstore
import
AssetMetadata
,
AssetThumbnailMetadata
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
new_contract
(
'CourseKey'
,
CourseKey
)
new_contract
(
'AssetKey'
,
AssetKey
)
new_contract
(
'AssetMetadata'
,
AssetMetadata
)
new_contract
(
'AssetThumbnailMetadata'
,
AssetThumbnailMetadata
)
# sort order that returns DRAFT items first
# sort order that returns DRAFT items first
SORT_REVISION_FAVOR_DRAFT
=
(
'_id.revision'
,
pymongo
.
DESCENDING
)
SORT_REVISION_FAVOR_DRAFT
=
(
'_id.revision'
,
pymongo
.
DESCENDING
)
...
@@ -195,7 +202,6 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
...
@@ -195,7 +202,6 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
category
=
json_data
[
'location'
][
'category'
]
category
=
json_data
[
'location'
][
'category'
]
class_
=
self
.
load_block_type
(
category
)
class_
=
self
.
load_block_type
(
category
)
definition
=
json_data
.
get
(
'definition'
,
{})
definition
=
json_data
.
get
(
'definition'
,
{})
metadata
=
json_data
.
get
(
'metadata'
,
{})
metadata
=
json_data
.
get
(
'metadata'
,
{})
for
old_name
,
new_name
in
getattr
(
class_
,
'metadata_translations'
,
{})
.
items
():
for
old_name
,
new_name
in
getattr
(
class_
,
'metadata_translations'
,
{})
.
items
():
...
@@ -443,7 +449,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
...
@@ -443,7 +449,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
super
(
MongoModuleStore
,
self
)
.
__init__
(
contentstore
=
contentstore
,
**
kwargs
)
super
(
MongoModuleStore
,
self
)
.
__init__
(
contentstore
=
contentstore
,
**
kwargs
)
def
do_connection
(
def
do_connection
(
db
,
collection
,
host
,
port
=
27017
,
tz_aware
=
True
,
user
=
None
,
password
=
None
,
**
kwargs
db
,
collection
,
host
,
port
=
27017
,
tz_aware
=
True
,
user
=
None
,
password
=
None
,
asset_collection
=
None
,
**
kwargs
):
):
"""
"""
Create & open the connection, authenticate, and provide pointers to the collection
Create & open the connection, authenticate, and provide pointers to the collection
...
@@ -460,6 +466,11 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
...
@@ -460,6 +466,11 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
)
)
self
.
collection
=
self
.
database
[
collection
]
self
.
collection
=
self
.
database
[
collection
]
# Collection which stores asset metadata.
self
.
asset_collection
=
None
if
asset_collection
is
not
None
:
self
.
asset_collection
=
self
.
database
[
asset_collection
]
if
user
is
not
None
and
password
is
not
None
:
if
user
is
not
None
and
password
is
not
None
:
self
.
database
.
authenticate
(
user
,
password
)
self
.
database
.
authenticate
(
user
,
password
)
...
@@ -1436,6 +1447,147 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
...
@@ -1436,6 +1447,147 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
field_data
=
KvsFieldData
(
kvs
)
field_data
=
KvsFieldData
(
kvs
)
return
field_data
return
field_data
def
_find_course_assets
(
self
,
course_key
):
"""
Internal; finds (or creates) course asset info about all assets for a particular course
Arguments:
course_key (CourseKey): course identifier
Returns:
Asset info for the course
"""
if
self
.
asset_collection
is
None
:
return
None
# Using the course_key, find or insert the course asset metadata document.
# A single document exists per course to store the course asset metadata.
course_assets
=
self
.
asset_collection
.
find_one
(
{
'course_id'
:
unicode
(
course_key
)},
fields
=
(
'course_id'
,
'storage'
,
'assets'
,
'thumbnails'
)
)
if
course_assets
is
None
:
# Not found, so create.
course_assets
=
{
'course_id'
:
unicode
(
course_key
),
'storage'
:
'FILLMEIN-TMP'
,
'assets'
:
[],
'thumbnails'
:
[]}
course_assets
[
'_id'
]
=
self
.
asset_collection
.
insert
(
course_assets
)
return
course_assets
@contract
(
course_key
=
'CourseKey'
,
asset_metadata
=
'AssetMetadata | AssetThumbnailMetadata'
,
user_id
=
'str | unicode'
)
def
_save_asset_info
(
self
,
course_key
,
asset_metadata
,
user_id
,
thumbnail
=
False
):
"""
Saves the info for a particular course's asset/thumbnail.
Arguments:
course_key (CourseKey): course identifier
asset_metadata (AssetMetadata/AssetThumbnailMetadata): data about the course asset/thumbnail
thumbnail (bool): True if saving thumbnail metadata, False if saving asset metadata
Returns:
True if info save was successful, else False
"""
if
self
.
asset_collection
is
None
:
return
False
course_assets
,
asset_idx
=
self
.
_find_course_asset
(
course_key
,
asset_metadata
.
asset_id
.
path
,
thumbnail
)
info
=
'thumbnails'
if
thumbnail
else
'assets'
all_assets
=
course_assets
[
info
]
# Set the edited information for assets only - not thumbnails.
if
not
thumbnail
:
asset_metadata
.
update
({
'edited_by'
:
user_id
,
'edited_on'
:
datetime
.
now
(
UTC
)})
# Translate metadata to Mongo format.
metadata_to_insert
=
asset_metadata
.
to_mongo
()
if
asset_idx
is
None
:
# Append new metadata.
# Future optimization: Insert in order & binary search to retrieve.
all_assets
.
append
(
metadata_to_insert
)
else
:
# Replace existing metadata.
all_assets
[
asset_idx
]
=
metadata_to_insert
# Update the document.
self
.
asset_collection
.
update
({
'_id'
:
course_assets
[
'_id'
]},
{
'$set'
:
{
info
:
all_assets
}})
return
True
@contract
(
asset_key
=
'AssetKey'
,
attr_dict
=
dict
)
def
set_asset_metadata_attrs
(
self
,
asset_key
,
attr_dict
,
user_id
):
"""
Add/set the given dict of attrs on the asset at the given location. Value can be any type which pymongo accepts.
Arguments:
asset_key (AssetKey): asset identifier
attr_dict (dict): attribute: value pairs to set
Raises:
ItemNotFoundError if no such item exists
AttributeError is attr is one of the build in attrs.
"""
if
self
.
asset_collection
is
None
:
return
course_assets
,
asset_idx
=
self
.
_find_course_asset
(
asset_key
.
course_key
,
asset_key
.
path
)
if
asset_idx
is
None
:
raise
ItemNotFoundError
(
asset_key
)
# Form an AssetMetadata.
all_assets
=
course_assets
[
'assets'
]
md
=
AssetMetadata
(
asset_key
,
asset_key
.
path
)
md
.
from_mongo
(
all_assets
[
asset_idx
])
md
.
update
(
attr_dict
)
md
.
update
({
'edited_by'
:
user_id
,
'edited_on'
:
datetime
.
now
(
UTC
)})
# Generate a Mongo doc from the metadata and update the course asset info.
all_assets
[
asset_idx
]
=
md
.
to_mongo
()
self
.
asset_collection
.
update
({
'_id'
:
course_assets
[
'_id'
]},
{
"$set"
:
{
'assets'
:
all_assets
}})
@contract
(
asset_key
=
'AssetKey'
)
def
_delete_asset_data
(
self
,
asset_key
,
thumbnail
=
False
):
"""
Internal; deletes a single asset's metadata -or- thumbnail.
Arguments:
asset_key (AssetKey): key containing original asset/thumbnail filename
thumbnail: True if thumbnail deletion, False if asset metadata deletion
Returns:
Number of asset metadata/thumbnail entries deleted (0 or 1)
"""
if
self
.
asset_collection
is
None
:
return
0
course_assets
,
asset_idx
=
self
.
_find_course_asset
(
asset_key
.
course_key
,
asset_key
.
path
,
get_thumbnail
=
thumbnail
)
if
asset_idx
is
None
:
return
0
info
=
'thumbnails'
if
thumbnail
else
'assets'
all_asset_info
=
course_assets
[
info
]
all_asset_info
.
pop
(
asset_idx
)
# Update the document.
self
.
asset_collection
.
update
({
'_id'
:
course_assets
[
'_id'
]},
{
'$set'
:
{
info
:
all_asset_info
}})
return
1
@contract
(
course_key
=
'CourseKey'
)
def
delete_all_asset_metadata
(
self
,
course_key
):
"""
Delete all of the assets which use this course_key as an identifier.
Arguments:
course_key (CourseKey): course_identifier
"""
if
self
.
asset_collection
is
None
:
return
# Using the course_id, find the course asset metadata document.
# A single document exists per course to store the course asset metadata.
course_assets
=
self
.
_find_course_assets
(
course_key
)
self
.
asset_collection
.
remove
(
course_assets
[
'_id'
])
def
heartbeat
(
self
):
def
heartbeat
(
self
):
"""
"""
Check that the db is reachable.
Check that the db is reachable.
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/mongo_connection.py
View file @
5ffc6ac2
...
@@ -99,7 +99,7 @@ class MongoConnection(object):
...
@@ -99,7 +99,7 @@ class MongoConnection(object):
Segregation of pymongo functions from the data modeling mechanisms for split modulestore.
Segregation of pymongo functions from the data modeling mechanisms for split modulestore.
"""
"""
def
__init__
(
def
__init__
(
self
,
db
,
collection
,
host
,
port
=
27017
,
tz_aware
=
True
,
user
=
None
,
password
=
None
,
**
kwargs
self
,
db
,
collection
,
host
,
port
=
27017
,
tz_aware
=
True
,
user
=
None
,
password
=
None
,
asset_collection
=
None
,
**
kwargs
):
):
"""
"""
Create & open the connection, authenticate, and provide pointers to the collections
Create & open the connection, authenticate, and provide pointers to the collections
...
@@ -114,6 +114,10 @@ class MongoConnection(object):
...
@@ -114,6 +114,10 @@ class MongoConnection(object):
db
db
)
)
# Remove when adding official Split support for asset metadata storage.
if
asset_collection
:
pass
if
user
is
not
None
and
password
is
not
None
:
if
user
is
not
None
and
password
is
not
None
:
self
.
database
.
authenticate
(
user
,
password
)
self
.
database
.
authenticate
(
user
,
password
)
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
View file @
5ffc6ac2
...
@@ -47,6 +47,7 @@ class TestMixedModuleStore(CourseComparisonTest):
...
@@ -47,6 +47,7 @@ class TestMixedModuleStore(CourseComparisonTest):
PORT
=
MONGO_PORT_NUM
PORT
=
MONGO_PORT_NUM
DB
=
'test_mongo_
%
s'
%
uuid4
()
.
hex
[:
5
]
DB
=
'test_mongo_
%
s'
%
uuid4
()
.
hex
[:
5
]
COLLECTION
=
'modulestore'
COLLECTION
=
'modulestore'
ASSET_COLLECTION
=
'assetstore'
FS_ROOT
=
DATA_DIR
FS_ROOT
=
DATA_DIR
DEFAULT_CLASS
=
'xmodule.raw_module.RawDescriptor'
DEFAULT_CLASS
=
'xmodule.raw_module.RawDescriptor'
RENDER_TEMPLATE
=
lambda
t_n
,
d
,
ctx
=
None
,
nsp
=
'main'
:
''
RENDER_TEMPLATE
=
lambda
t_n
,
d
,
ctx
=
None
,
nsp
=
'main'
:
''
...
@@ -67,6 +68,7 @@ class TestMixedModuleStore(CourseComparisonTest):
...
@@ -67,6 +68,7 @@ class TestMixedModuleStore(CourseComparisonTest):
'port'
:
PORT
,
'port'
:
PORT
,
'db'
:
DB
,
'db'
:
DB
,
'collection'
:
COLLECTION
,
'collection'
:
COLLECTION
,
'asset_collection'
:
ASSET_COLLECTION
,
}
}
OPTIONS
=
{
OPTIONS
=
{
'mappings'
:
{
'mappings'
:
{
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
View file @
5ffc6ac2
...
@@ -14,6 +14,7 @@ from datetime import datetime
...
@@ -14,6 +14,7 @@ from datetime import datetime
from
pytz
import
UTC
from
pytz
import
UTC
import
unittest
import
unittest
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
ddt
import
ddt
,
data
from
xblock.fields
import
Scope
,
Reference
,
ReferenceList
,
ReferenceValueDict
from
xblock.fields
import
Scope
,
Reference
,
ReferenceList
,
ReferenceValueDict
from
xblock.runtime
import
KeyValueStore
from
xblock.runtime
import
KeyValueStore
...
@@ -30,6 +31,7 @@ from opaque_keys.edx.keys import UsageKey
...
@@ -30,6 +31,7 @@ from opaque_keys.edx.keys import UsageKey
from
xmodule.modulestore.xml_exporter
import
export_to_xml
from
xmodule.modulestore.xml_exporter
import
export_to_xml
from
xmodule.modulestore.xml_importer
import
import_from_xml
,
perform_xlint
from
xmodule.modulestore.xml_importer
import
import_from_xml
,
perform_xlint
from
xmodule.contentstore.mongo
import
MongoContentStore
from
xmodule.contentstore.mongo
import
MongoContentStore
from
xmodule.assetstore
import
AssetMetadata
,
AssetThumbnailMetadata
from
nose.tools
import
assert_in
from
nose.tools
import
assert_in
from
xmodule.exceptions
import
NotFoundError
from
xmodule.exceptions
import
NotFoundError
...
@@ -45,6 +47,7 @@ HOST = MONGO_HOST
...
@@ -45,6 +47,7 @@ HOST = MONGO_HOST
PORT
=
MONGO_PORT_NUM
PORT
=
MONGO_PORT_NUM
DB
=
'test_mongo_
%
s'
%
uuid4
()
.
hex
[:
5
]
DB
=
'test_mongo_
%
s'
%
uuid4
()
.
hex
[:
5
]
COLLECTION
=
'modulestore'
COLLECTION
=
'modulestore'
ASSET_COLLECTION
=
'assetstore'
FS_ROOT
=
DATA_DIR
# TODO (vshnayder): will need a real fs_root for testing load_item
FS_ROOT
=
DATA_DIR
# TODO (vshnayder): will need a real fs_root for testing load_item
DEFAULT_CLASS
=
'xmodule.raw_module.RawDescriptor'
DEFAULT_CLASS
=
'xmodule.raw_module.RawDescriptor'
RENDER_TEMPLATE
=
lambda
t_n
,
d
,
ctx
=
None
,
nsp
=
'main'
:
''
RENDER_TEMPLATE
=
lambda
t_n
,
d
,
ctx
=
None
,
nsp
=
'main'
:
''
...
@@ -60,8 +63,10 @@ class ReferenceTestXBlock(XBlock, XModuleMixin):
...
@@ -60,8 +63,10 @@ class ReferenceTestXBlock(XBlock, XModuleMixin):
reference_dict
=
ReferenceValueDict
(
scope
=
Scope
.
settings
)
reference_dict
=
ReferenceValueDict
(
scope
=
Scope
.
settings
)
class
TestMongoModuleStore
(
unittest
.
TestCase
):
class
TestMongoModuleStoreBase
(
unittest
.
TestCase
):
'''Tests!'''
'''
Basic setup for all tests
'''
# Explicitly list the courses to load (don't want the big one)
# Explicitly list the courses to load (don't want the big one)
courses
=
[
'toy'
,
'simple'
,
'simple_with_draft'
,
'test_unicode'
]
courses
=
[
'toy'
,
'simple'
,
'simple_with_draft'
,
'test_unicode'
]
...
@@ -88,6 +93,13 @@ class TestMongoModuleStore(unittest.TestCase):
...
@@ -88,6 +93,13 @@ class TestMongoModuleStore(unittest.TestCase):
cls
.
connection
.
close
()
cls
.
connection
.
close
()
@classmethod
@classmethod
def
add_asset_collection
(
cls
,
doc_store_config
):
"""
No asset collection.
"""
pass
@classmethod
def
initdb
(
cls
):
def
initdb
(
cls
):
# connect to the db
# connect to the db
doc_store_config
=
{
doc_store_config
=
{
...
@@ -95,7 +107,10 @@ class TestMongoModuleStore(unittest.TestCase):
...
@@ -95,7 +107,10 @@ class TestMongoModuleStore(unittest.TestCase):
'port'
:
PORT
,
'port'
:
PORT
,
'db'
:
DB
,
'db'
:
DB
,
'collection'
:
COLLECTION
,
'collection'
:
COLLECTION
,
#'asset_collection': ASSET_COLLECTION,
}
}
cls
.
add_asset_collection
(
doc_store_config
)
# since MongoModuleStore and MongoContentStore are basically assumed to be together, create this class
# since MongoModuleStore and MongoContentStore are basically assumed to be together, create this class
# as well
# as well
content_store
=
MongoContentStore
(
HOST
,
DB
,
port
=
PORT
)
content_store
=
MongoContentStore
(
HOST
,
DB
,
port
=
PORT
)
...
@@ -136,14 +151,33 @@ class TestMongoModuleStore(unittest.TestCase):
...
@@ -136,14 +151,33 @@ class TestMongoModuleStore(unittest.TestCase):
# Destroy the test db.
# Destroy the test db.
connection
.
drop_database
(
DB
)
connection
.
drop_database
(
DB
)
def
setUp
(
self
):
@classmethod
# make a copy for convenience
def
setUp
(
cls
):
self
.
connection
=
TestMongoModuleStore
.
connection
cls
.
dummy_user
=
ModuleStoreEnum
.
UserID
.
test
self
.
dummy_user
=
ModuleStoreEnum
.
UserID
.
test
def
tearDown
(
self
):
@classmethod
def
tearDown
(
cls
):
pass
pass
class
TestMongoModuleStore
(
TestMongoModuleStoreBase
):
'''Module store tests'''
@classmethod
def
add_asset_collection
(
cls
,
doc_store_config
):
"""
No asset collection - it's not used in the tests below.
"""
pass
@classmethod
def
setupClass
(
cls
):
super
(
TestMongoModuleStore
,
cls
)
.
setupClass
()
@classmethod
def
teardownClass
(
cls
):
super
(
TestMongoModuleStore
,
cls
)
.
teardownClass
()
def
test_init
(
self
):
def
test_init
(
self
):
'''Make sure the db loads'''
'''Make sure the db loads'''
ids
=
list
(
self
.
connection
[
DB
][
COLLECTION
]
.
find
({},
{
'_id'
:
True
}))
ids
=
list
(
self
.
connection
[
DB
][
COLLECTION
]
.
find
({},
{
'_id'
:
True
}))
...
@@ -233,7 +267,6 @@ class TestMongoModuleStore(unittest.TestCase):
...
@@ -233,7 +267,6 @@ class TestMongoModuleStore(unittest.TestCase):
self
.
draft_store
.
get_item
(
Location
(
'edX'
,
'test_unicode'
,
'2012_Fall'
,
'chapter'
,
'Overview'
)),
self
.
draft_store
.
get_item
(
Location
(
'edX'
,
'test_unicode'
,
'2012_Fall'
,
'chapter'
,
'Overview'
)),
)
)
def
test_find_one
(
self
):
def
test_find_one
(
self
):
assert_not_none
(
assert_not_none
(
self
.
draft_store
.
_find_one
(
Location
(
'edX'
,
'toy'
,
'2012_Fall'
,
'course'
,
'2012_Fall'
)),
self
.
draft_store
.
_find_one
(
Location
(
'edX'
,
'toy'
,
'2012_Fall'
,
'course'
,
'2012_Fall'
)),
...
@@ -632,6 +665,363 @@ class TestMongoModuleStore(unittest.TestCase):
...
@@ -632,6 +665,363 @@ class TestMongoModuleStore(unittest.TestCase):
shutil
.
rmtree
(
root_dir
)
shutil
.
rmtree
(
root_dir
)
@ddt
class
TestMongoAssetMetadataStorage
(
TestMongoModuleStore
):
"""
Tests for storing/querying course asset metadata from Mongo storage.
"""
def
_make_asset_metadata
(
self
,
asset_loc
):
"""
Make a single test asset metadata.
"""
return
AssetMetadata
(
asset_loc
,
internal_name
=
'EKMND332DDBK'
,
basename
=
'pictures/historical'
,
contenttype
=
'image/jpeg'
,
locked
=
False
,
md5
=
'77631ca4f0e08419b70726a447333ab6'
,
curr_version
=
'v1.0'
,
prev_version
=
'v0.95'
)
def
_make_asset_thumbnail_metadata
(
self
,
asset_key
):
"""
Make a single test asset thumbnail metadata.
"""
return
AssetThumbnailMetadata
(
asset_key
,
internal_name
=
'ABC39XJUDN2'
)
@classmethod
def
add_asset_collection
(
cls
,
doc_store_config
):
"""
Valid asset collection.
"""
doc_store_config
[
'asset_collection'
]
=
ASSET_COLLECTION
@classmethod
def
setupClass
(
cls
):
super
(
TestMongoAssetMetadataStorage
,
cls
)
.
setupClass
()
@classmethod
def
teardownClass
(
cls
):
super
(
TestMongoAssetMetadataStorage
,
cls
)
.
teardownClass
()
def
setup_assets
(
self
):
"""
Setup assets.
"""
asset_fields
=
(
'filename'
,
'internal_name'
,
'basename'
,
'locked'
,
'curr_version'
,
'prev_version'
)
asset1_vals
=
(
'pic1.jpg'
,
'EKMND332DDBK'
,
'pix/archive'
,
False
,
'14'
,
'13'
)
asset2_vals
=
(
'shout.ogg'
,
'KFMDONSKF39K'
,
'sounds'
,
True
,
'1'
,
None
)
asset3_vals
=
(
'code.tgz'
,
'ZZB2333YBDMW'
,
'exercises/14'
,
False
,
'AB'
,
'AA'
)
asset4_vals
=
(
'dog.png'
,
'PUPY4242X'
,
'pictures/animals'
,
True
,
'5'
,
'4'
)
asset5_vals
=
(
'not_here.txt'
,
'JJJCCC747'
,
'/dev/null'
,
False
,
'50'
,
'49'
)
asset1
=
dict
(
zip
(
asset_fields
[
1
:],
asset1_vals
[
1
:]))
asset2
=
dict
(
zip
(
asset_fields
[
1
:],
asset2_vals
[
1
:]))
asset3
=
dict
(
zip
(
asset_fields
[
1
:],
asset3_vals
[
1
:]))
asset4
=
dict
(
zip
(
asset_fields
[
1
:],
asset4_vals
[
1
:]))
non_existent_asset
=
dict
(
zip
(
asset_fields
[
1
:],
asset5_vals
[
1
:]))
# Asset6 and thumbnail6 have equivalent information on purpose.
asset6_vals
=
(
'asset.txt'
,
'JJJCCC747858'
,
'/dev/null'
,
False
,
'50'
,
'49'
)
asset6
=
dict
(
zip
(
asset_fields
[
1
:],
asset6_vals
[
1
:]))
asset1_key
=
self
.
course1
.
id
.
make_asset_key
(
'asset'
,
asset1_vals
[
0
])
asset2_key
=
self
.
course1
.
id
.
make_asset_key
(
'asset'
,
asset2_vals
[
0
])
asset3_key
=
self
.
course2
.
id
.
make_asset_key
(
'asset'
,
asset3_vals
[
0
])
asset4_key
=
self
.
course2
.
id
.
make_asset_key
(
'asset'
,
asset4_vals
[
0
])
asset5_key
=
self
.
course2
.
id
.
make_asset_key
(
'asset'
,
asset5_vals
[
0
])
asset6_key
=
self
.
course2
.
id
.
make_asset_key
(
'asset'
,
asset6_vals
[
0
])
asset1_md
=
AssetMetadata
(
asset1_key
,
**
asset1
)
asset2_md
=
AssetMetadata
(
asset2_key
,
**
asset2
)
asset3_md
=
AssetMetadata
(
asset3_key
,
**
asset3
)
asset4_md
=
AssetMetadata
(
asset4_key
,
**
asset4
)
asset5_md
=
AssetMetadata
(
asset5_key
,
**
non_existent_asset
)
asset6_md
=
AssetMetadata
(
asset6_key
,
**
asset6
)
editing_user
=
'Oliver Twist'
self
.
assertTrue
(
self
.
draft_store
.
save_asset_metadata
(
self
.
course1
.
id
,
asset1_md
,
editing_user
))
self
.
assertTrue
(
self
.
draft_store
.
save_asset_metadata
(
self
.
course1
.
id
,
asset2_md
,
editing_user
))
self
.
assertTrue
(
self
.
draft_store
.
save_asset_metadata
(
self
.
course2
.
id
,
asset3_md
,
editing_user
))
self
.
assertTrue
(
self
.
draft_store
.
save_asset_metadata
(
self
.
course2
.
id
,
asset4_md
,
editing_user
))
# asset5 and asset6 are not saved on purpose!
return
(
asset1_md
,
asset2_md
,
asset3_md
,
asset4_md
,
asset5_md
,
asset6_md
)
def
setup_thumbnails
(
self
):
"""
Setup thumbs.
"""
thumbnail_fields
=
(
'filename'
,
'internal_name'
)
thumbnail1_vals
=
(
'cat_thumb.jpg'
,
'XYXYXYXYXYXY'
)
thumbnail2_vals
=
(
'kitten_thumb.jpg'
,
'123ABC123ABC'
)
thumbnail3_vals
=
(
'puppy_thumb.jpg'
,
'ADAM12ADAM12'
)
thumbnail4_vals
=
(
'meerkat_thumb.jpg'
,
'CHIPSPONCH14'
)
thumbnail5_vals
=
(
'corgi_thumb.jpg'
,
'RON8LDXFFFF10'
)
thumbnail1
=
dict
(
zip
(
thumbnail_fields
[
1
:],
thumbnail1_vals
[
1
:]))
thumbnail2
=
dict
(
zip
(
thumbnail_fields
[
1
:],
thumbnail2_vals
[
1
:]))
thumbnail3
=
dict
(
zip
(
thumbnail_fields
[
1
:],
thumbnail3_vals
[
1
:]))
thumbnail4
=
dict
(
zip
(
thumbnail_fields
[
1
:],
thumbnail4_vals
[
1
:]))
non_existent_thumbnail
=
dict
(
zip
(
thumbnail_fields
[
1
:],
thumbnail5_vals
[
1
:]))
# Asset6 and thumbnail6 have equivalent information on purpose.
thumbnail6_vals
=
(
'asset.txt'
,
'JJJCCC747858'
)
thumbnail6
=
dict
(
zip
(
thumbnail_fields
[
1
:],
thumbnail6_vals
[
1
:]))
thumb1_key
=
self
.
course1
.
id
.
make_asset_key
(
'thumbnail'
,
thumbnail1_vals
[
0
])
thumb2_key
=
self
.
course1
.
id
.
make_asset_key
(
'thumbnail'
,
thumbnail2_vals
[
0
])
thumb3_key
=
self
.
course2
.
id
.
make_asset_key
(
'thumbnail'
,
thumbnail3_vals
[
0
])
thumb4_key
=
self
.
course2
.
id
.
make_asset_key
(
'thumbnail'
,
thumbnail4_vals
[
0
])
thumb5_key
=
self
.
course2
.
id
.
make_asset_key
(
'thumbnail'
,
thumbnail5_vals
[
0
])
thumb6_key
=
self
.
course2
.
id
.
make_asset_key
(
'thumbnail'
,
thumbnail6_vals
[
0
])
thumb1_md
=
AssetThumbnailMetadata
(
thumb1_key
,
**
thumbnail1
)
thumb2_md
=
AssetThumbnailMetadata
(
thumb2_key
,
**
thumbnail2
)
thumb3_md
=
AssetThumbnailMetadata
(
thumb3_key
,
**
thumbnail3
)
thumb4_md
=
AssetThumbnailMetadata
(
thumb4_key
,
**
thumbnail4
)
thumb5_md
=
AssetThumbnailMetadata
(
thumb5_key
,
**
non_existent_thumbnail
)
thumb6_md
=
AssetThumbnailMetadata
(
thumb6_key
,
**
thumbnail6
)
self
.
assertTrue
(
self
.
draft_store
.
save_asset_thumbnail_metadata
(
self
.
course1
.
id
,
thumb1_md
))
self
.
assertTrue
(
self
.
draft_store
.
save_asset_thumbnail_metadata
(
self
.
course1
.
id
,
thumb2_md
))
self
.
assertTrue
(
self
.
draft_store
.
save_asset_thumbnail_metadata
(
self
.
course2
.
id
,
thumb3_md
))
self
.
assertTrue
(
self
.
draft_store
.
save_asset_thumbnail_metadata
(
self
.
course2
.
id
,
thumb4_md
))
# thumb5 and thumb6 are not saved on purpose!
return
(
thumb1_md
,
thumb2_md
,
thumb3_md
,
thumb4_md
,
thumb5_md
,
thumb6_md
)
def
setUp
(
self
):
"""
Set up a quantity of test asset metadata for testing purposes.
"""
super
(
TestMongoAssetMetadataStorage
,
self
)
.
setUp
()
courses
=
self
.
draft_store
.
get_courses
()
self
.
course1
=
courses
[
0
]
self
.
course2
=
courses
[
1
]
(
self
.
asset1_md
,
self
.
asset2_md
,
self
.
asset3_md
,
self
.
asset4_md
,
self
.
asset5_md
,
self
.
asset6_md
)
=
self
.
setup_assets
()
(
self
.
thumb1_md
,
self
.
thumb2_md
,
self
.
thumb3_md
,
self
.
thumb4_md
,
self
.
thumb5_md
,
self
.
thumb6_md
)
=
self
.
setup_thumbnails
()
def
tearDown
(
self
):
self
.
draft_store
.
delete_all_asset_metadata
(
self
.
course1
.
id
)
self
.
draft_store
.
delete_all_asset_metadata
(
self
.
course2
.
id
)
def
test_save_one_and_confirm
(
self
):
courses
=
self
.
draft_store
.
get_courses
()
course
=
courses
[
0
]
asset_filename
=
'burnside.jpg'
new_asset_loc
=
course
.
id
.
make_asset_key
(
'asset'
,
asset_filename
)
# Confirm that the asset's metadata is not present.
self
.
assertIsNone
(
self
.
draft_store
.
find_asset_metadata
(
new_asset_loc
))
# Save the asset's metadata.
new_asset_md
=
self
.
_make_asset_metadata
(
new_asset_loc
)
self
.
assertTrue
(
self
.
draft_store
.
save_asset_metadata
(
course
.
id
,
new_asset_md
,
'John Doe'
))
# Find the asset's metadata and confirm it's the same.
found_asset_md
=
self
.
draft_store
.
find_asset_metadata
(
new_asset_loc
)
self
.
assertIsNotNone
(
found_asset_md
)
self
.
assertEquals
(
new_asset_md
.
asset_id
,
found_asset_md
.
asset_id
)
# Confirm that only two setup plus one asset's metadata exists.
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_metadata
(
course
.
id
)),
3
)
# Delete all metadata and confirm it's gone.
self
.
draft_store
.
delete_all_asset_metadata
(
course
.
id
)
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_metadata
(
course
.
id
)),
0
)
def
test_delete_all_without_creation
(
self
):
courses
=
self
.
draft_store
.
get_courses
()
course
=
courses
[
0
]
# Confirm that only setup asset metadata exists.
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_metadata
(
course
.
id
)),
2
)
# Now delete the metadata.
self
.
draft_store
.
delete_all_asset_metadata
(
course
.
id
)
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_metadata
(
course
.
id
)),
0
)
# Now delete the non-existent metadata.
self
.
draft_store
.
delete_all_asset_metadata
(
course
.
id
)
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_metadata
(
course
.
id
)),
0
)
def
test_save_many_and_delete_one
(
self
):
# Make sure there's two assets.
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_metadata
(
self
.
course1
.
id
)),
2
)
# Delete one of the assets.
self
.
assertEquals
(
self
.
draft_store
.
delete_asset_metadata
(
self
.
asset1_md
.
asset_id
),
1
)
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_metadata
(
self
.
course1
.
id
)),
1
)
# Attempt to delete an asset that doesn't exist.
self
.
assertEquals
(
self
.
draft_store
.
delete_asset_metadata
(
self
.
asset5_md
.
asset_id
),
0
)
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_metadata
(
self
.
course1
.
id
)),
1
)
def
test_find_existing_and_non_existing_assets
(
self
):
# Find existing asset metadata.
asset_md
=
self
.
draft_store
.
find_asset_metadata
(
self
.
asset1_md
.
asset_id
)
self
.
assertIsNotNone
(
asset_md
)
# Find non-existent asset metadata.
asset_md
=
self
.
draft_store
.
find_asset_metadata
(
self
.
asset5_md
.
asset_id
)
self
.
assertIsNone
(
asset_md
)
def
test_add_same_asset_twice
(
self
):
courses
=
self
.
draft_store
.
get_courses
()
course
=
courses
[
0
]
asset_filename
=
'burnside.jpg'
new_asset_loc
=
course
.
id
.
make_asset_key
(
'asset'
,
asset_filename
)
new_asset_md
=
self
.
_make_asset_metadata
(
new_asset_loc
)
# Only the setup stuff here?
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_metadata
(
course
.
id
)),
2
)
# Add asset metadata.
self
.
assertTrue
(
self
.
draft_store
.
save_asset_metadata
(
course
.
id
,
new_asset_md
,
"John Do"
))
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_metadata
(
course
.
id
)),
3
)
# Add *the same* asset metadata.
self
.
assertTrue
(
self
.
draft_store
.
save_asset_metadata
(
course
.
id
,
new_asset_md
,
"John Dont"
))
# Still one here?
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_metadata
(
course
.
id
)),
3
)
self
.
draft_store
.
delete_all_asset_metadata
(
course
.
id
)
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_metadata
(
course
.
id
)),
0
)
def
test_lock_unlock_assets
(
self
):
# Find a course asset and check its locked status.
asset_md
=
self
.
draft_store
.
find_asset_metadata
(
self
.
asset1_md
.
asset_id
)
self
.
assertIsNotNone
(
asset_md
)
locked_state
=
asset_md
.
locked
# Flip the course asset's locked status.
self
.
draft_store
.
set_asset_metadata_attr
(
self
.
asset1_md
.
asset_id
,
"locked"
,
not
locked_state
,
'John Doe'
)
# Find the same course and check its locked status.
updated_asset_md
=
self
.
draft_store
.
find_asset_metadata
(
self
.
asset1_md
.
asset_id
)
self
.
assertIsNotNone
(
updated_asset_md
)
self
.
assertEquals
(
updated_asset_md
.
locked
,
not
locked_state
)
# Now flip it back.
self
.
draft_store
.
set_asset_metadata_attr
(
self
.
asset1_md
.
asset_id
,
"locked"
,
locked_state
,
'John Doe'
)
reupdated_asset_md
=
self
.
draft_store
.
find_asset_metadata
(
self
.
asset1_md
.
asset_id
)
self
.
assertIsNotNone
(
reupdated_asset_md
)
self
.
assertEquals
(
reupdated_asset_md
.
locked
,
locked_state
)
ALLOWED_ATTRS
=
(
(
'basename'
,
'/new/path'
),
(
'internal_name'
,
'new_filename.txt'
),
(
'locked'
,
True
),
(
'contenttype'
,
'image/png'
),
(
'md5'
,
'5346682d948cc3f683635b6918f9b3d0'
),
(
'curr_version'
,
'v1.01'
),
(
'prev_version'
,
'v1.0'
),
(
'edited_by'
,
'Mork'
),
(
'edited_on'
,
datetime
(
1969
,
1
,
1
,
tzinfo
=
UTC
)),
)
DISALLOWED_ATTRS
=
(
(
'asset_id'
,
'IAmBogus'
),
)
UNKNOWN_ATTRS
=
(
(
'lunch_order'
,
'burger_and_fries'
),
(
'villain'
,
'Khan'
)
)
@data
(
*
ALLOWED_ATTRS
)
def
test_set_all_attrs
(
self
,
attr_pair
):
# Find a course asset.
asset_md
=
self
.
draft_store
.
find_asset_metadata
(
self
.
asset1_md
.
asset_id
)
self
.
assertIsNotNone
(
asset_md
)
# Set the course asset's attr.
editing_user
=
'user_who_edited'
self
.
draft_store
.
set_asset_metadata_attr
(
self
.
asset1_md
.
asset_id
,
*
attr_pair
,
user_id
=
editing_user
)
# Find the same course asset and check its changed attr.
updated_asset_md
=
self
.
draft_store
.
find_asset_metadata
(
self
.
asset1_md
.
asset_id
)
self
.
assertIsNotNone
(
updated_asset_md
)
self
.
assertIsNotNone
(
getattr
(
updated_asset_md
,
attr_pair
[
0
],
None
))
if
attr_pair
[
0
]
==
'edited_by'
:
# No matter what the edited_by attr_pair is, it gets over-ridden by the passed-in user_id.
self
.
assertEquals
(
getattr
(
updated_asset_md
,
attr_pair
[
0
],
None
),
editing_user
)
elif
attr_pair
[
0
]
==
'edited_on'
:
# edited_on is also over-ridden to be the time of update.
pass
else
:
self
.
assertEquals
(
getattr
(
updated_asset_md
,
attr_pair
[
0
],
None
),
attr_pair
[
1
])
@data
(
*
DISALLOWED_ATTRS
)
def
test_set_disallowed_attrs
(
self
,
attr_pair
):
# Find a course asset.
asset_md
=
self
.
draft_store
.
find_asset_metadata
(
self
.
asset1_md
.
asset_id
)
self
.
assertIsNotNone
(
asset_md
)
original_attr_val
=
getattr
(
asset_md
,
attr_pair
[
0
])
# Set the course asset's attr.
self
.
draft_store
.
set_asset_metadata_attr
(
self
.
asset1_md
.
asset_id
,
*
attr_pair
,
user_id
=
'John Doe'
)
# Find the same course and check its changed attr.
updated_asset_md
=
self
.
draft_store
.
find_asset_metadata
(
self
.
asset1_md
.
asset_id
)
self
.
assertIsNotNone
(
updated_asset_md
)
self
.
assertIsNotNone
(
getattr
(
updated_asset_md
,
attr_pair
[
0
],
None
))
# Make sure that the attr is unchanged from its original value.
self
.
assertEquals
(
getattr
(
updated_asset_md
,
attr_pair
[
0
],
None
),
original_attr_val
)
@data
(
*
UNKNOWN_ATTRS
)
def
test_set_unknown_attrs
(
self
,
attr_pair
):
# Find a course asset.
asset_md
=
self
.
draft_store
.
find_asset_metadata
(
self
.
asset1_md
.
asset_id
)
self
.
assertIsNotNone
(
asset_md
)
# Set the course asset's attr.
self
.
draft_store
.
set_asset_metadata_attr
(
self
.
asset1_md
.
asset_id
,
*
attr_pair
,
user_id
=
'John Smith'
)
# Find the same course and check its changed attr.
updated_asset_md
=
self
.
draft_store
.
find_asset_metadata
(
self
.
asset1_md
.
asset_id
)
self
.
assertIsNotNone
(
updated_asset_md
)
# Make sure the unknown field was *not* added.
with
self
.
assertRaises
(
AttributeError
):
self
.
assertEquals
(
getattr
(
updated_asset_md
,
attr_pair
[
0
]),
attr_pair
[
1
])
def
test_save_one_thumbnail_and_delete_one_thumbnail
(
self
):
thumbnail_filename
=
'burn_thumb.jpg'
asset_key
=
self
.
course1
.
id
.
make_asset_key
(
'thumbnail'
,
thumbnail_filename
)
new_asset_thumbnail
=
self
.
_make_asset_thumbnail_metadata
(
asset_key
)
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_thumbnail_metadata
(
self
.
course1
.
id
)),
2
)
self
.
assertTrue
(
self
.
draft_store
.
save_asset_thumbnail_metadata
(
self
.
course1
.
id
,
new_asset_thumbnail
))
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_thumbnail_metadata
(
self
.
course1
.
id
)),
3
)
self
.
assertEquals
(
self
.
draft_store
.
delete_asset_thumbnail_metadata
(
asset_key
),
1
)
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_thumbnail_metadata
(
self
.
course1
.
id
)),
2
)
def
test_find_thumbnail
(
self
):
self
.
assertIsNotNone
(
self
.
draft_store
.
find_asset_thumbnail_metadata
(
self
.
thumb1_md
.
asset_id
))
self
.
assertIsNone
(
self
.
draft_store
.
find_asset_thumbnail_metadata
(
self
.
thumb5_md
.
asset_id
))
def
test_delete_all_thumbnails
(
self
):
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_thumbnail_metadata
(
self
.
course1
.
id
)),
2
)
self
.
draft_store
.
delete_all_asset_metadata
(
self
.
course1
.
id
)
self
.
assertEquals
(
len
(
self
.
draft_store
.
get_all_asset_thumbnail_metadata
(
self
.
course1
.
id
)),
0
)
def
test_asset_object_equivalence
(
self
):
# Assets are only equivalent to themselves.
self
.
assertTrue
(
self
.
asset6_md
!=
self
.
thumb6_md
)
self
.
assertEquals
(
self
.
asset1_md
,
self
.
asset1_md
)
def
test_get_all_assets_with_paging
(
self
):
pass
def
test_copy_all_assets
(
self
):
pass
class
TestMongoModuleStoreWithNoAssetCollection
(
TestMongoModuleStore
):
'''
Tests a situation where no asset_collection is specified.
'''
@classmethod
def
add_asset_collection
(
cls
,
doc_store_config
):
"""
No asset collection.
"""
pass
@classmethod
def
setupClass
(
cls
):
super
(
TestMongoModuleStoreWithNoAssetCollection
,
cls
)
.
setupClass
()
@classmethod
def
teardownClass
(
cls
):
super
(
TestMongoModuleStoreWithNoAssetCollection
,
cls
)
.
teardownClass
()
def
test_no_asset_collection
(
self
):
courses
=
self
.
draft_store
.
get_courses
()
course
=
courses
[
0
]
# Confirm that no asset collection means no asset metadata.
self
.
assertEquals
(
self
.
draft_store
.
get_all_asset_metadata
(
course
.
id
),
None
)
# Now delete the non-existent asset metadata.
self
.
draft_store
.
delete_all_asset_metadata
(
course
.
id
)
# Should still be nothing.
self
.
assertEquals
(
self
.
draft_store
.
get_all_asset_metadata
(
course
.
id
),
None
)
class
TestMongoKeyValueStore
(
object
):
class
TestMongoKeyValueStore
(
object
):
"""
"""
Tests for MongoKeyValueStore.
Tests for MongoKeyValueStore.
...
...
lms/envs/common.py
View file @
5ffc6ac2
...
@@ -566,6 +566,7 @@ DOC_STORE_CONFIG = {
...
@@ -566,6 +566,7 @@ DOC_STORE_CONFIG = {
'host'
:
'localhost'
,
'host'
:
'localhost'
,
'db'
:
'xmodule'
,
'db'
:
'xmodule'
,
'collection'
:
'modulestore'
,
'collection'
:
'modulestore'
,
'asset_collection'
:
'assetstore'
,
}
}
MODULESTORE
=
{
MODULESTORE
=
{
'default'
:
{
'default'
:
{
...
...
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