Commit 244197e3 by muhammad-ammar

add support in model for multiple video image urls

parent 9a878df0
...@@ -10,6 +10,5 @@ script: ...@@ -10,6 +10,5 @@ script:
branches: branches:
only: only:
- master - master
- ammar/course-rerun-import-export
after_success: after_success:
coveralls coveralls
...@@ -21,6 +21,7 @@ class Migration(migrations.Migration): ...@@ -21,6 +21,7 @@ class Migration(migrations.Migration):
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('image', edxval.models.CustomizableImageField(null=True, blank=True)), ('image', edxval.models.CustomizableImageField(null=True, blank=True)),
('generated_images', edxval.models.ListField()),
('course_video', models.OneToOneField(related_name='video_image', to='edxval.CourseVideo')), ('course_video', models.OneToOneField(related_name='video_image', to='edxval.CourseVideo')),
], ],
options={ options={
......
...@@ -12,12 +12,14 @@ invalid profile_name will be returned. ...@@ -12,12 +12,14 @@ invalid profile_name will be returned.
""" """
from contextlib import closing from contextlib import closing
import json
import logging import logging
import os import os
from uuid import uuid4 from uuid import uuid4
from django.db import models from django.db import models
from django.dispatch import receiver from django.dispatch import receiver
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, RegexValidator from django.core.validators import MinValueValidator, RegexValidator
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -28,6 +30,7 @@ from edxval.utils import video_image_path, get_video_image_storage ...@@ -28,6 +30,7 @@ from edxval.utils import video_image_path, get_video_image_storage
logger = logging.getLogger(__name__) # pylint: disable=C0103 logger = logging.getLogger(__name__) # pylint: disable=C0103
URL_REGEX = r'^[a-zA-Z0-9\-_]*$' URL_REGEX = r'^[a-zA-Z0-9\-_]*$'
LIST_MAX_ITEMS = 3
class ModelFactoryWithValidation(object): class ModelFactoryWithValidation(object):
...@@ -199,12 +202,70 @@ class CustomizableImageField(models.ImageField): ...@@ -199,12 +202,70 @@ class CustomizableImageField(models.ImageField):
return name, path, args, kwargs return name, path, args, kwargs
class ListField(models.TextField):
"""
ListField use to store and retrieve list data.
"""
__metaclass__ = models.SubfieldBase
def get_prep_value(self, value):
"""
Converts a list to its json represetation to store in database as text.
"""
return json.dumps(value)
def to_python(self, value):
"""
Converts the value into a list.
"""
if not value:
value = []
# If a list is set then validated its items
if isinstance(value, list):
return self.validate(value)
else: # try to de-serialize value and expect list and then validate
try:
py_list = json.loads(value)
if not isinstance(py_list, list):
raise TypeError
self.validate(py_list)
except (ValueError, TypeError):
raise ValidationError(u'Must be a valid list of strings.')
return py_list
def validate(self, value):
"""
Validate data before saving to database.
Arguemtns:
value(list): list to be validated
Returns:
list if validation is successful
Raises:
ValidationError
"""
if len(value) > LIST_MAX_ITEMS:
raise ValidationError(u'list must not contain more than {} items.'.format(LIST_MAX_ITEMS))
if all(isinstance(item, str) for item in value) is False:
raise ValidationError(u'list must only contain strings.')
return value
class VideoImage(TimeStampedModel): class VideoImage(TimeStampedModel):
""" """
Image model for course video. Image model for course video.
""" """
course_video = models.OneToOneField(CourseVideo, related_name="video_image") course_video = models.OneToOneField(CourseVideo, related_name="video_image")
image = CustomizableImageField() image = CustomizableImageField()
generated_images = ListField()
@classmethod @classmethod
def create_or_update(cls, course_video, file_name, image_data=None): def create_or_update(cls, course_video, file_name, image_data=None):
......
...@@ -7,6 +7,7 @@ import mock ...@@ -7,6 +7,7 @@ import mock
from mock import patch from mock import patch
from lxml import etree from lxml import etree
from django.core.exceptions import ValidationError
from django.core.files.images import ImageFile from django.core.files.images import ImageFile
from django.test import TestCase from django.test import TestCase
from django.db import DatabaseError from django.db import DatabaseError
...@@ -14,7 +15,7 @@ from django.core.urlresolvers import reverse ...@@ -14,7 +15,7 @@ from django.core.urlresolvers import reverse
from rest_framework import status from rest_framework import status
from ddt import ddt, data, unpack from ddt import ddt, data, unpack
from edxval.models import Profile, Video, EncodedVideo, CourseVideo, VideoImage from edxval.models import Profile, Video, EncodedVideo, CourseVideo, VideoImage, LIST_MAX_ITEMS
from edxval import api as api from edxval import api as api
from edxval.api import ( from edxval.api import (
SortDirection, SortDirection,
...@@ -1335,3 +1336,53 @@ class CourseVideoImageTest(TestCase): ...@@ -1335,3 +1336,53 @@ class CourseVideoImageTest(TestCase):
does_not_course_id does_not_course_id
) )
) )
def test_video_image_urls_field(self):
"""
Test `VideoImage.generated_images` field works as expected.
"""
image_urls = ['video-images/a.png', 'video-images/b.png']
# an empty list should be returned when there is no value for urls
self.assertEqual(self.course_video.video_image.generated_images, [])
# set a list with data and expect the same list to be returned
course_video = CourseVideo.objects.create(video=self.video, course_id='course101')
video_image = VideoImage.objects.create(course_video=course_video)
video_image.generated_images = image_urls
video_image.save()
self.assertEqual(video_image.generated_images, image_urls)
self.assertEqual(course_video.video_image.generated_images, image_urls)
def test_video_image_urls_field_validation(self):
"""
Test `VideoImage.generated_images` field validation.
"""
course_video = CourseVideo.objects.create(video=self.video, course_id='course101')
video_image = VideoImage.objects.create(course_video=course_video)
# expect a validation error if we try to set a list with more than 3 items
with self.assertRaises(ValidationError) as set_exception:
video_image.generated_images = ['a', 'b', 'c', 'd']
self.assertEqual(
set_exception.exception.message,
u'list must not contain more than {} items.'.format(LIST_MAX_ITEMS)
)
# expect a validation error if we try to a list with non-string items
with self.assertRaises(ValidationError) as set_exception:
video_image.generated_images = ['a', 1, 2]
self.assertEqual(set_exception.exception.message, u'list must only contain strings.')
# expect a validation error if we try to set non list data
exception_messages = set()
for item in ('a string', 555, {'a': 1}, (1,), video_image):
with self.assertRaises(ValidationError) as set_exception:
video_image.generated_images = item
exception_messages.add(set_exception.exception.message)
self.assertEqual(len(exception_messages), 1)
self.assertEqual(exception_messages.pop(), u'Must be a valid list of strings.')
...@@ -39,7 +39,7 @@ def load_requirements(*requirements_paths): ...@@ -39,7 +39,7 @@ def load_requirements(*requirements_paths):
setup( setup(
name='edxval', name='edxval',
version='0.0.15', version='0.0.14',
author='edX', author='edX',
url='http://github.com/edx/edx-val', url='http://github.com/edx/edx-val',
description='edx-val', description='edx-val',
......
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