Commit 4b53f4df by Nimisha Asthagiri

Studio video upload CSV download changes.

parent 8d8ca83d
......@@ -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']
\ No newline at end of file
complete_apps = ['contentstore']
"""
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))
......
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