Studio video upload CSV download changes.
This commit is contained in:
@@ -5,6 +5,6 @@ Admin site bindings for contentstore
|
||||
from django.contrib import admin
|
||||
|
||||
from config_models.admin import ConfigurationModelAdmin
|
||||
from contentstore.models import VideoEncodingDownloadConfig
|
||||
from contentstore.models import VideoUploadConfig
|
||||
|
||||
admin.site.register(VideoEncodingDownloadConfig, ConfigurationModelAdmin)
|
||||
admin.site.register(VideoUploadConfig, ConfigurationModelAdmin)
|
||||
|
||||
@@ -8,8 +8,8 @@ from django.db import models
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'VideoEncodingDownloadConfig'
|
||||
db.create_table('contentstore_videoencodingdownloadconfig', (
|
||||
# Adding model 'VideoUploadConfig'
|
||||
db.create_table('contentstore_videouploadconfig', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('change_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
('changed_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.PROTECT)),
|
||||
@@ -17,13 +17,17 @@ class Migration(SchemaMigration):
|
||||
('profile_whitelist', self.gf('django.db.models.fields.TextField')(blank=True)),
|
||||
('status_whitelist', self.gf('django.db.models.fields.TextField')(blank=True)),
|
||||
))
|
||||
db.send_create_signal('contentstore', ['VideoEncodingDownloadConfig'])
|
||||
db.send_create_signal('contentstore', ['VideoUploadConfig'])
|
||||
|
||||
if not db.dry_run:
|
||||
orm.VideoUploadConfig.objects.create(
|
||||
profile_whitelist="desktop_mp4,desktop_webm,mobile_low,youtube",
|
||||
status_whitelist="Uploading,In Progress,Complete,Failed,Invalid Token,Unknown"
|
||||
)
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'VideoEncodingDownloadConfig'
|
||||
db.delete_table('contentstore_videoencodingdownloadconfig')
|
||||
|
||||
# Deleting model 'VideoUploadConfig'
|
||||
db.delete_table('contentstore_videouploadconfig')
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
@@ -55,8 +59,8 @@ class Migration(SchemaMigration):
|
||||
'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'})
|
||||
},
|
||||
'contentstore.videoencodingdownloadconfig': {
|
||||
'Meta': {'object_name': 'VideoEncodingDownloadConfig'},
|
||||
'contentstore.videouploadconfig': {
|
||||
'Meta': {'object_name': 'VideoUploadConfig'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
@@ -73,4 +77,4 @@ class Migration(SchemaMigration):
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['contentstore']
|
||||
complete_apps = ['contentstore']
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
"""
|
||||
Models for contentstore
|
||||
"""
|
||||
# pylint: disable=no-member
|
||||
|
||||
from django.db.models.fields import TextField
|
||||
|
||||
from config_models.models import ConfigurationModel
|
||||
|
||||
|
||||
class VideoEncodingDownloadConfig(ConfigurationModel):
|
||||
"""Configuration for what to include in video encoding downloads"""
|
||||
class VideoUploadConfig(ConfigurationModel):
|
||||
"""Configuration for the video upload feature."""
|
||||
profile_whitelist = TextField(
|
||||
blank=True,
|
||||
help_text="A comma-separated list of names of profiles to include in video encoding downloads"
|
||||
help_text="A comma-separated list of names of profiles to include in video encoding downloads."
|
||||
)
|
||||
status_whitelist = TextField(
|
||||
blank=True,
|
||||
help_text="A comma-separated list of status values; only videos with these status values will be included in video encoding downloads"
|
||||
help_text=(
|
||||
"A comma-separated list of Studio status values;" +
|
||||
" only videos with these status values will be included in video encoding downloads."
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -15,8 +15,8 @@ from mock import Mock, patch
|
||||
|
||||
from edxval.api import create_profile, create_video, get_video_info
|
||||
|
||||
from contentstore.models import VideoEncodingDownloadConfig
|
||||
from contentstore.views.videos import KEY_EXPIRATION_IN_SECONDS, VIDEO_ASSET_TYPE
|
||||
from contentstore.models import VideoUploadConfig
|
||||
from contentstore.views.videos import KEY_EXPIRATION_IN_SECONDS, VIDEO_ASSET_TYPE, status_display_string
|
||||
from contentstore.tests.utils import CourseTestCase
|
||||
from contentstore.utils import reverse_course_url
|
||||
from xmodule.assetstore import AssetMetadata
|
||||
@@ -59,7 +59,7 @@ class VideoUploadTestMixin(object):
|
||||
"edx_video_id": "test1",
|
||||
"client_video_id": "test1.mp4",
|
||||
"duration": 42.0,
|
||||
"status": "transcode_active",
|
||||
"status": "upload",
|
||||
"encoded_videos": [],
|
||||
},
|
||||
{
|
||||
@@ -86,7 +86,7 @@ class VideoUploadTestMixin(object):
|
||||
"edx_video_id": "non-ascii",
|
||||
"client_video_id": u"nón-ascii-näme.mp4",
|
||||
"duration": 256.0,
|
||||
"status": "file_delivered",
|
||||
"status": "transcode_active",
|
||||
"encoded_videos": [
|
||||
{
|
||||
"profile": "profile1",
|
||||
@@ -169,8 +169,9 @@ class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
set(["edx_video_id", "client_video_id", "created", "duration", "status"])
|
||||
)
|
||||
dateutil.parser.parse(response_video["created"])
|
||||
for field in ["edx_video_id", "client_video_id", "duration", "status"]:
|
||||
for field in ["edx_video_id", "client_video_id", "duration"]:
|
||||
self.assertEqual(response_video[field], original_video[field])
|
||||
self.assertEqual(response_video["status"], status_display_string(original_video["status"]))
|
||||
|
||||
def test_get_html(self):
|
||||
response = self.client.get(self.url)
|
||||
@@ -312,9 +313,12 @@ class VideoUrlsCsvTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VideoUrlsCsvTestCase, self).setUp()
|
||||
VideoEncodingDownloadConfig(
|
||||
VideoUploadConfig(
|
||||
profile_whitelist="profile1",
|
||||
status_whitelist="file_delivered,file_complete"
|
||||
status_whitelist=(
|
||||
status_display_string("file_complete") + "," +
|
||||
status_display_string("transcode_active")
|
||||
)
|
||||
).save()
|
||||
|
||||
def _check_csv_response(self, expected_video_ids, expected_profiles):
|
||||
@@ -333,7 +337,7 @@ class VideoUrlsCsvTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
self.assertEqual(
|
||||
reader.fieldnames,
|
||||
(
|
||||
["Name", "Duration", "Date Added", "Video ID"] +
|
||||
["Name", "Duration", "Date Added", "Video ID", "Status"] +
|
||||
["{} URL".format(profile) for profile in expected_profiles]
|
||||
)
|
||||
)
|
||||
@@ -366,9 +370,13 @@ class VideoUrlsCsvTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
self._check_csv_response(["test2", "non-ascii"], ["profile1"])
|
||||
|
||||
def test_config(self):
|
||||
VideoEncodingDownloadConfig(
|
||||
VideoUploadConfig(
|
||||
profile_whitelist="profile1,profile2",
|
||||
status_whitelist="file_delivered,file_complete,transcode_active"
|
||||
status_whitelist=(
|
||||
status_display_string("file_complete") + "," +
|
||||
status_display_string("transcode_active") + "," +
|
||||
status_display_string("upload")
|
||||
)
|
||||
).save()
|
||||
self._check_csv_response(["test1", "test2", "non-ascii"], ["profile1", "profile2"])
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import rfc6266
|
||||
from edxval.api import create_video, get_videos_for_ids
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from contentstore.models import VideoEncodingDownloadConfig
|
||||
from contentstore.models import VideoUploadConfig
|
||||
from contentstore.utils import reverse_course_url
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from util.json_request import expect_json, JsonResponse
|
||||
@@ -35,6 +35,43 @@ VIDEO_ASSET_TYPE = "video"
|
||||
KEY_EXPIRATION_IN_SECONDS = 86400
|
||||
|
||||
|
||||
class StatusDisplayStrings(object):
|
||||
"""
|
||||
Enum of display strings for Video Status presented in Studio (e.g., in UI and in CSV download).
|
||||
"""
|
||||
# Translators: This is the status of an active video upload
|
||||
UPLOADING = _("Uploading")
|
||||
# Translators: This is the status for a video that the servers are currently processing
|
||||
IN_PROGRESS = _("In Progress")
|
||||
# Translators: This is the status for a video that the servers have successfully processed
|
||||
COMPLETE = _("Complete")
|
||||
# Translators: This is the status for a video that the servers have failed to process
|
||||
FAILED = _("Failed"),
|
||||
# Translators: This is the status for a video for which an invalid
|
||||
# processing token was provided in the course settings
|
||||
INVALID_TOKEN = _("Invalid Token"),
|
||||
# Translators: This is the status for a video that is in an unknown state
|
||||
UNKNOWN = _("Unknown")
|
||||
|
||||
|
||||
def status_display_string(val_status):
|
||||
"""
|
||||
Converts VAL status string to Studio status string.
|
||||
"""
|
||||
status_map = {
|
||||
"upload": StatusDisplayStrings.UPLOADING,
|
||||
"ingest": StatusDisplayStrings.IN_PROGRESS,
|
||||
"transcode_queue": StatusDisplayStrings.IN_PROGRESS,
|
||||
"transcode_active": StatusDisplayStrings.IN_PROGRESS,
|
||||
"file_delivered": StatusDisplayStrings.COMPLETE,
|
||||
"file_complete": StatusDisplayStrings.COMPLETE,
|
||||
"file_corrupt": StatusDisplayStrings.FAILED,
|
||||
"pipeline_error": StatusDisplayStrings.FAILED,
|
||||
"invalid_token": StatusDisplayStrings.INVALID_TOKEN
|
||||
}
|
||||
return status_map.get(val_status, StatusDisplayStrings.UNKNOWN)
|
||||
|
||||
|
||||
@expect_json
|
||||
@login_required
|
||||
@require_http_methods(("GET", "POST"))
|
||||
@@ -73,8 +110,8 @@ def video_encodings_download(request, course_key_string):
|
||||
Returns a CSV report containing the encoded video URLs for video uploads
|
||||
in the following format:
|
||||
|
||||
Video ID,Name,Profile1 URL,Profile2 URL
|
||||
aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa,video.mp4,http://example.com/profile1.mp4,http://example.com/profile2.mp4
|
||||
Video ID,Name,Status,Profile1 URL,Profile2 URL
|
||||
aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa,video.mp4,Complete,http://example.com/prof1.mp4,http://example.com/prof2.mp4
|
||||
"""
|
||||
course = _get_and_validate_course(course_key_string, request.user)
|
||||
|
||||
@@ -88,14 +125,15 @@ def video_encodings_download(request, course_key_string):
|
||||
# (e.g. desktop, mobile high quality, mobile low quality)
|
||||
return _("{profile_name} URL").format(profile_name=profile)
|
||||
|
||||
profile_whitelist = VideoEncodingDownloadConfig.get_profile_whitelist()
|
||||
status_whitelist = VideoEncodingDownloadConfig.get_status_whitelist()
|
||||
profile_whitelist = VideoUploadConfig.get_profile_whitelist()
|
||||
status_whitelist = VideoUploadConfig.get_status_whitelist()
|
||||
|
||||
videos = list(_get_videos(course))
|
||||
name_col = _("Name")
|
||||
duration_col = _("Duration")
|
||||
added_col = _("Date Added")
|
||||
video_id_col = _("Video ID")
|
||||
status_col = _("Status")
|
||||
profile_cols = [get_profile_header(profile) for profile in profile_whitelist]
|
||||
|
||||
def make_csv_dict(video):
|
||||
@@ -115,6 +153,7 @@ def video_encodings_download(request, course_key_string):
|
||||
(duration_col, duration_val),
|
||||
(added_col, video["created"].isoformat()),
|
||||
(video_id_col, video["edx_video_id"]),
|
||||
(status_col, video["status"]),
|
||||
] +
|
||||
[
|
||||
(get_profile_header(encoded_video["profile"]), encoded_video["url"])
|
||||
@@ -138,7 +177,7 @@ def video_encodings_download(request, course_key_string):
|
||||
)
|
||||
writer = csv.DictWriter(
|
||||
response,
|
||||
[name_col, duration_col, added_col, video_id_col] + profile_cols,
|
||||
[name_col, duration_col, added_col, video_id_col, status_col] + profile_cols,
|
||||
dialect=csv.excel
|
||||
)
|
||||
writer.writeheader()
|
||||
@@ -173,13 +212,20 @@ def _get_and_validate_course(course_key_string, user):
|
||||
def _get_videos(course):
|
||||
"""
|
||||
Retrieves the list of videos from VAL corresponding to the videos listed in
|
||||
the asset metadata store
|
||||
the asset metadata store.
|
||||
"""
|
||||
edx_videos_ids = [
|
||||
v.asset_id.path
|
||||
for v in modulestore().get_all_asset_metadata(course.id, VIDEO_ASSET_TYPE)
|
||||
]
|
||||
return get_videos_for_ids(edx_videos_ids)
|
||||
|
||||
videos = list(get_videos_for_ids(edx_videos_ids))
|
||||
|
||||
# convert VAL's status to studio's Video Upload feature status.
|
||||
for video in videos:
|
||||
video["status"] = status_display_string(video["status"])
|
||||
|
||||
return videos
|
||||
|
||||
|
||||
def _get_index_videos(course):
|
||||
|
||||
@@ -67,16 +67,12 @@ define(
|
||||
|
||||
_.each(
|
||||
[
|
||||
{status: "upload", expected: "Uploading"},
|
||||
{status: "ingest", expected: "In Progress"},
|
||||
{status: "transcode_queue", expected: "In Progress"},
|
||||
{status: "transcode_active", expected: "In Progress"},
|
||||
{status: "file_delivered", expected: "Complete"},
|
||||
{status: "file_complete", expected: "Complete"},
|
||||
{status: "file_corrupt", expected: "Failed"},
|
||||
{status: "pipeline_error", expected: "Failed"},
|
||||
{status: "invalid_token", expected: "Invalid Token"},
|
||||
{status: "unexpected_status_string", expected: "Unknown"}
|
||||
{status: "Uploading", expected: "Uploading"},
|
||||
{status: "In Progress", expected: "In Progress"},
|
||||
{status: "Complete", expected: "Complete"},
|
||||
{status: "Failed", expected: "Failed"},
|
||||
{status: "Invalid Token", expected: "Invalid Token"},
|
||||
{status: "Unknown", expected: "Unknown"}
|
||||
],
|
||||
function(caseInfo) {
|
||||
it("should render " + caseInfo.status + " status correctly", function() {
|
||||
|
||||
@@ -3,38 +3,6 @@ define(
|
||||
function(gettext, DateUtils, BaseView) {
|
||||
"use strict";
|
||||
|
||||
var statusDisplayStrings = {
|
||||
// Translators: This is the status of an active video upload
|
||||
UPLOADING: gettext("Uploading"),
|
||||
// Translators: This is the status for a video that the servers
|
||||
// are currently processing
|
||||
IN_PROGRESS: gettext("In Progress"),
|
||||
// Translators: This is the status for a video that the servers
|
||||
// have successfully processed
|
||||
COMPLETE: gettext("Complete"),
|
||||
// Translators: This is the status for a video that the servers
|
||||
// have failed to process
|
||||
FAILED: gettext("Failed"),
|
||||
// Translators: This is the status for a video for which an invalid
|
||||
// processing token was provided in the course settings
|
||||
INVALID_TOKEN: gettext("Invalid Token"),
|
||||
// Translators: This is the status for a video that is in an unknown
|
||||
// state
|
||||
UNKNOWN: gettext("Unknown")
|
||||
};
|
||||
|
||||
var statusMap = {
|
||||
"upload": statusDisplayStrings.UPLOADING,
|
||||
"ingest": statusDisplayStrings.IN_PROGRESS,
|
||||
"transcode_queue": statusDisplayStrings.IN_PROGRESS,
|
||||
"transcode_active": statusDisplayStrings.IN_PROGRESS,
|
||||
"file_delivered": statusDisplayStrings.COMPLETE,
|
||||
"file_complete": statusDisplayStrings.COMPLETE,
|
||||
"file_corrupt": statusDisplayStrings.FAILED,
|
||||
"pipeline_error": statusDisplayStrings.FAILED,
|
||||
"invalid_token": statusDisplayStrings.INVALID_TOKEN
|
||||
};
|
||||
|
||||
var PreviousVideoUploadView = BaseView.extend({
|
||||
tagName: "tr",
|
||||
|
||||
@@ -57,7 +25,7 @@ define(
|
||||
// the servers where its duration is determined.
|
||||
duration: duration > 0 ? this.renderDuration(duration) : gettext("Pending"),
|
||||
created: DateUtils.renderDate(this.model.get("created")),
|
||||
status: statusMap[this.model.get("status")] || statusDisplayStrings.UNKNOWN
|
||||
status: this.model.get("status")
|
||||
};
|
||||
this.$el.html(
|
||||
this.template(_.extend({}, this.model.attributes, renderedAttributes))
|
||||
|
||||
Reference in New Issue
Block a user