diff --git a/.travis.yml b/.travis.yml index 05b38aa..e3a5a0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ python: env: - DJANGO_VERSION=1.4.5 - DJANGO_VERSION=1.5.1 + - DJANGO_VERSION=1.5.2 install: - pip install -q Django==$DJANGO_VERSION --use-mirrors - pip install coveralls --use-mirrors diff --git a/CHANGES.rst b/CHANGES.rst index e98cf67..b32ef96 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,32 @@ Changes ******* +0.3 +------ + +- Security fix: faked urls are treated as invalid. See `this page + `_ + for more details. + +- Fixes: + + - allow of empty video field. + + - requirements in setup.py + +- Added simplier way to embed video in one-line template tag:: + + {{ 'http://www.youtube.com/watch?v=guXyvo2FfLs'|embed:'large' }} + +- ``backend`` variable in ``video`` template tag. + + Usage:: + + {% video item.video as my_video %} + Backend: {{ my_video.backend }} + {% endvideo %} + + 0.2 ----- diff --git a/README.rst b/README.rst index 9dc0817..ee81b53 100644 --- a/README.rst +++ b/README.rst @@ -65,6 +65,7 @@ Usage of variables: {% video item.video as my_video %} URL: {{ my_video.url }} Thumbnail: {{ my_video.thumbnail }} + Backend: {{ my_video.backend }} {% endvideo %} @@ -111,7 +112,15 @@ TODO - Vimeo thumbnail + +Websites using django-embed-video +********************************* + +- `Tchorici.cz `_ (`sources + `_) + +*Are you using django-embed-video? Send pull request!* + + + .. vim: set tw=80: - - - diff --git a/embed_video/base.py b/embed_video/base.py index ad2a75c..73de4c0 100644 --- a/embed_video/base.py +++ b/embed_video/base.py @@ -4,14 +4,13 @@ import requests import json DETECT_YOUTUBE = re.compile( - # r'^(http(s)?://(www\.)?)?youtu(\.?)be(\.com)?.*', re.I - r'(?:www.|)?youtu(\.?)be(\.com)?.*', re.I + r'^(http(s)?://(www\.)?)?youtu(\.?)be(\.com)?/.*', re.I ) DETECT_VIMEO = re.compile( - r'^(http(s)?://(www\.)?)?vimeo\.com.*', re.I + r'^(http(s)?://(www\.)?)?vimeo\.com/.*', re.I ) DETECT_SOUNDCLOUD = re.compile( - r'^(http(s)?://(www\.)?)?soundcloud\.com.*', re.I + r'^(http(s)?://(www\.)?)?soundcloud\.com/.*', re.I ) @@ -29,7 +28,7 @@ def detect_backend(url): elif DETECT_VIMEO.match(url): return VimeoBackend(url) elif DETECT_SOUNDCLOUD.match(url): - return SoundCloundBackend(url) + return SoundCloudBackend(url) else: raise UnknownBackendException @@ -38,6 +37,7 @@ class VideoBackend(object): def __init__(self, url): self._url = url + self.backend = self.__class__.__name__ self.code = self.get_code() self.url = self.get_url() self.thumbnail = self.get_thumbnail_url() @@ -54,7 +54,33 @@ class VideoBackend(object): return self.pattern_thumbnail_url % self.code -class SoundCloundBackend(VideoBackend): +class YoutubeBackend(VideoBackend): + re_code = re.compile( + r'youtu(?:be\.com/watch\?v=|\.be/)(?P[\w-]*)(&(amp;)?[\w\?=]*)?', + re.I + ) + pattern_url = 'http://www.youtube.com/embed/%s?wmode=opaque' + pattern_thumbnail_url = 'http://img.youtube.com/vi/%s/hqdefault.jpg' + + def get_code(self): + code = super(YoutubeBackend, self).get_code() + + if not code: + parse_data = urlparse.urlparse(self._url) + code = urlparse.parse_qs(parse_data.query)['v'][0] + + return code + + +class VimeoBackend(VideoBackend): + re_code = re.compile(r'vimeo\.com/(?P[0-9]+)', re.I) + pattern_url = 'http://player.vimeo.com/video/%s' + + def get_thumbnail_url(self): + pass # not implemented + + +class SoundCloudBackend(VideoBackend): base_url = 'http://soundcloud.com/oembed' re_code = re.compile(r'src=".*%2F(?P\d+)&show_artwork.*"', re.I) @@ -72,7 +98,7 @@ class SoundCloundBackend(VideoBackend): self.width = self.response.get('width') self.height = self.response.get('height') - super(SoundCloundBackend, self).__init__(url) + super(SoundCloudBackend, self).__init__(url) def get_thumbnail_url(self): return self.response.get('thumbnail_url') @@ -84,34 +110,3 @@ class SoundCloundBackend(VideoBackend): def get_code(self): match = self.re_code.search(self.response.get('html')) return match.group('code') - - -class YoutubeBackend(VideoBackend): - re_code = re.compile( - r'(?:http|https|)(?::\/\/|)(?:www.|)(?:youtu\.be\/|youtube\.com(?:\/embed\/|\/v\/|\/watch\?v=|\/ytscreeningroom\?v=|\/feeds\/api\/videos\/|\/user\S*[^\w\-\s]|\S*[^\w\-\s]))(?P[\w\-]{11})[a-z0-9;:@?&%=+\/\$_.-]*', - # r'(?:http|https|)(?::\/\/|)(?:www.|)(?:youtu\.be\/|youtube\.com(?:\/embed\/|\/v\/|\/watch\?v=|\/ytscreeningroom\?v=|\/feeds\/api\/videos\/|\/user\S*[^\w\-\s]|\S*[^\w\-\s]))(?P[\w\-]{11})[a-z0-9;:@?&%=+\/\$_.-]*', - # r'youtu(?:be\.com/watch\?v=|\.be/)(?P[\w-]*)(&(amp;)?[\w\?=]*)?', - re.I - ) - pattern_url = 'http://www.youtube.com/embed/%s?wmode=opaque' - pattern_thumbnail_url = 'http://img.youtube.com/vi/%s/hqdefault.jpg' - - def get_code(self): - code = super(YoutubeBackend, self).get_code() - - if not code: - parse_data = urlparse.urlparse(self._url) - try: - code = urlparse.parse_qs(parse_data.query)['v'][0] - except KeyError: - raise UnknownIdException - - return code - - -class VimeoBackend(VideoBackend): - re_code = re.compile(r'vimeo\.com/(?P[0-9]+)', re.I) - pattern_url = 'http://player.vimeo.com/video/%s' - - def get_thumbnail_url(self): - pass # not implemented diff --git a/embed_video/templatetags/embed_video_tags.py b/embed_video/templatetags/embed_video_tags.py index 89783ae..4772a25 100644 --- a/embed_video/templatetags/embed_video_tags.py +++ b/embed_video/templatetags/embed_video_tags.py @@ -1,7 +1,7 @@ from django.template import Library, Node, TemplateSyntaxError from django.utils.safestring import mark_safe, SafeText -from ..base import detect_backend, SoundCloundBackend +from ..base import detect_backend, SoundCloudBackend register = Library() @@ -43,6 +43,17 @@ def embed(backend, _size='small'): if isinstance(backend, SafeText): backend = detect_backend(backend) + size = _embed_get_size(_size) + params = _embed_get_params(backend, size) + + return mark_safe( + '' % params + ) + + +def _embed_get_size(size): sizes = { 'tiny': (420, 315), 'small': (480, 360), @@ -51,22 +62,22 @@ def embed(backend, _size='small'): 'huge': (1280, 960), } - if _size in sizes: - size = sizes[_size] - elif 'x' in _size: - size = _size.split('x') + if size in sizes: + return sizes[size] + elif 'x' in size: + return [int(x) for x in size.split('x')] + +def _embed_get_params(backend, size): params = { 'url': backend.url, - 'width': int(size[0]), - 'height': int(size[1]), + 'width': size[0], + 'height': size[1], } - if isinstance(backend, SoundCloundBackend): + if isinstance(backend, SoundCloudBackend): params.update({'height': backend.height}) - return mark_safe( - '' % params - ) + return params + + diff --git a/embed_video/tests/tests_backend.py b/embed_video/tests/tests_backend.py index 80f5be2..dee5e08 100644 --- a/embed_video/tests/tests_backend.py +++ b/embed_video/tests/tests_backend.py @@ -1,14 +1,17 @@ from unittest import TestCase -from django.http import HttpRequest -from django.template.base import Template -from django.template.context import RequestContext - from ..base import detect_backend, YoutubeBackend, VimeoBackend, \ - SoundCloundBackend + SoundCloudBackend, UnknownBackendException class EmbedVideoTestCase(TestCase): + unknown_backend_urls = ( + 'http://myurl.com/?video=http://www.youtube.com/watch?v=jsrRJyHBvzw', + 'http://myurl.com/?video=www.youtube.com/watch?v=jsrRJyHBvzw', + 'http://youtube.com.myurl.com/watch?v=jsrRJyHBvzw', + 'http://vimeo.com.myurl.com/66577491', + ) + youtube_urls = ( ('http://www.youtube.com/watch?v=jsrRJyHBvzw', 'jsrRJyHBvzw'), ('http://youtube.com/watch?v=jsrRJyHBvzw', 'jsrRJyHBvzw'), @@ -36,61 +39,9 @@ class EmbedVideoTestCase(TestCase): from django.conf import settings as django_settings self.django_settings = django_settings - def _grc(self): - return RequestContext(HttpRequest()) - - def test_embed(self): - template = Template(""" - {% load embed_video_tags %} - {% video 'http://www.youtube.com/watch?v=jsrRJyHBvzw' as ytb %} - {{ ytb|embed:'large' }} - {% endvideo %} - """) - rendered = u'' - - self.assertEqual(template.render(self._grc()).strip(), rendered) - - def test_direct_embed(self): - template = Template(""" - {% load embed_video_tags %} - {{ 'http://www.youtube.com/watch?v=jsrRJyHBvzw'|embed:'large' }} - """) - rendered = u'' - - self.assertEqual(template.render(self._grc()).strip(), rendered) - - def test_embed_user_size(self): - template = Template(""" - {% load embed_video_tags %} - {% video 'http://www.youtube.com/watch?v=jsrRJyHBvzw' as ytb %} - {{ ytb|embed:'800x800' }} - {% endvideo %} - """) - rendered = u'' - - self.assertEqual(template.render(self._grc()).strip(), rendered) - - def test_tag_youtube(self): - template = Template(""" - {% load embed_video_tags %} - {% video 'http://www.youtube.com/watch?v=jsrRJyHBvzw' as ytb %} - {{ ytb.url }} - {% endvideo %} - """) - rendered = 'http://www.youtube.com/embed/jsrRJyHBvzw?wmode=opaque' - - self.assertEqual(template.render(self._grc()).strip(), rendered) - - def test_tag_vimeo(self): - template = Template(""" - {% load embed_video_tags %} - {% video 'https://vimeo.com/66577491' as vimeo %} - {{ vimeo.url }} - {% endvideo %} - """) - rendered = 'http://player.vimeo.com/video/66577491' - - self.assertEqual(template.render(self._grc()).strip(), rendered) + def test_detect_bad_urls(self): + for url in self.unknown_backend_urls: + self.assertRaises(UnknownBackendException, detect_backend, url) def test_detect_youtube(self): for url in self.youtube_urls: @@ -105,7 +56,7 @@ class EmbedVideoTestCase(TestCase): def test_detect_soundcloud(self): for url in self.soundcloud_urls: backend = detect_backend(url[0]) - self.assertIsInstance(backend, SoundCloundBackend) + self.assertIsInstance(backend, SoundCloudBackend) def test_code_youtube(self): for url in self.youtube_urls: @@ -121,6 +72,6 @@ class EmbedVideoTestCase(TestCase): def test_code_soundcloud(self): for url in self.soundcloud_urls: - backend = SoundCloundBackend(url[0]) + backend = SoundCloudBackend(url[0]) code = backend.get_code() self.assertEqual(code, url[1]) diff --git a/embed_video/tests/tests_fields.py b/embed_video/tests/tests_fields.py index 8f98084..e392977 100644 --- a/embed_video/tests/tests_fields.py +++ b/embed_video/tests/tests_fields.py @@ -39,3 +39,9 @@ class EmbedVideoFormFieldTestCase(TestCase): mock_detect_backend.side_effect = UnknownIdException self.assertRaises(ValidationError, self.formfield.validate, ('http://youtube.com/v/123/',)) + + def test_validation_correct(self): + url = 'http://my-testing.url.com' + with patch('embed_video.fields.detect_backend') as mock_detect_backend: + mock_detect_backend.return_value = True + self.assertEqual(url, self.formfield.validate(url)) diff --git a/embed_video/tests/tests_tags.py b/embed_video/tests/tests_tags.py index 5b02cc8..9bd365c 100644 --- a/embed_video/tests/tests_tags.py +++ b/embed_video/tests/tests_tags.py @@ -2,8 +2,13 @@ from unittest import TestCase from mock import Mock from django.template import TemplateSyntaxError +from django.http import HttpRequest +from django.template.base import Template +from django.template.context import RequestContext -from ..templatetags.embed_video_tags import VideoNode +from embed_video.base import YoutubeBackend, SoundCloudBackend +from embed_video.templatetags.embed_video_tags import VideoNode, \ + _embed_get_size, _embed_get_params class EmbedVideoNodeTestCase(TestCase): @@ -11,11 +16,97 @@ class EmbedVideoNodeTestCase(TestCase): self.parser = Mock() self.token = Mock(methods=['split_contents']) + def _grc(self): + return RequestContext(HttpRequest()) + + def test_embed(self): + template = Template(""" + {% load embed_video_tags %} + {% video 'http://www.youtube.com/watch?v=jsrRJyHBvzw' as ytb %} + {{ ytb|embed:'large' }} + {% endvideo %} + """) + rendered = u'' + + self.assertEqual(template.render(self._grc()).strip(), rendered) + + def test_direct_embed(self): + template = Template(""" + {% load embed_video_tags %} + {{ 'http://www.youtube.com/watch?v=jsrRJyHBvzw'|embed:'large' }} + """) + rendered = u'' + + self.assertEqual(template.render(self._grc()).strip(), rendered) + + def test_embed_user_size(self): + template = Template(""" + {% load embed_video_tags %} + {% video 'http://www.youtube.com/watch?v=jsrRJyHBvzw' as ytb %} + {{ ytb|embed:'800x800' }} + {% endvideo %} + """) + rendered = u'' + + self.assertEqual(template.render(self._grc()).strip(), rendered) + + def test_tag_youtube(self): + template = Template(""" + {% load embed_video_tags %} + {% video 'http://www.youtube.com/watch?v=jsrRJyHBvzw' as ytb %} + {{ ytb.url }} + {% endvideo %} + """) + rendered = 'http://www.youtube.com/embed/jsrRJyHBvzw?wmode=opaque' + + self.assertEqual(template.render(self._grc()).strip(), rendered) + + def test_tag_vimeo(self): + template = Template(""" + {% load embed_video_tags %} + {% video 'https://vimeo.com/66577491' as vimeo %} + {{ vimeo.url }} + {% endvideo %} + """) + rendered = 'http://player.vimeo.com/video/66577491' + + self.assertEqual(template.render(self._grc()).strip(), rendered) + + def test_tag_backend_variable_vimeo(self): + template = Template(""" + {% load embed_video_tags %} + {% video 'https://vimeo.com/66577491' as vimeo %} + {{ vimeo.backend }} + {% endvideo %} + """) + rendered = 'VimeoBackend' + self.assertEqual(template.render(self._grc()).strip(), rendered) + + def test_tag_backend_variable_youtube(self): + template = Template(""" + {% load embed_video_tags %} + {% video 'http://www.youtube.com/watch?v=jsrRJyHBvz' as youtube %} + {{ youtube.backend }} + {% endvideo %} + """) + rendered = 'YoutubeBackend' + self.assertEqual(template.render(self._grc()).strip(), rendered) + + def test_tag_backend_variable_soundcloud(self): + template = Template(""" + {% load embed_video_tags %} + {% video 'https://soundcloud.com/glassnote/mumford-sons-i-will-wait' as soundcloud %} + {{ soundcloud.backend }} + {% endvideo %} + """) + rendered = 'SoundCloudBackend' + self.assertEqual(template.render(self._grc()).strip(), rendered) + def test_syntax_error(self): self.token.split_contents.return_value = [] try: - instance = VideoNode(self.parser, self.token) + VideoNode(self.parser, self.token) except TemplateSyntaxError: assert True @@ -28,4 +119,32 @@ class EmbedVideoNodeTestCase(TestCase): node = VideoNode(self.parser, self.token) self.assertEqual(str(node), '') + def test_embed_get_params(self): + url = 'http://youtu.be/13456' + backend = YoutubeBackend(url) + params = _embed_get_params(backend, (3, 8)) + + self.assertEqual('http://www.youtube.com/embed/13456?wmode=opaque', params['url']) + self.assertEqual(3, params['width']) + self.assertEqual(8, params['height']) + + def test_embed_get_params_soundcloud_height(self): + url = 'https://soundcloud.com/glassnote/mumford-sons-i-will-wait' + backend = SoundCloudBackend(url) + params = _embed_get_params(backend, (1, 2)) + + self.assertEqual(backend.height, params['height']) + + def test_videonode_iter(self): + out = ['a', 'b', 'c', 'd'] + + class FooNode(VideoNode): + nodelist_file = out + + def __init__(self): + pass + + node = FooNode() + self.assertEqual(out, [x for x in node]) + diff --git a/setup.py b/setup.py index 68b0a51..b98f947 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ CHANGES = read('CHANGES.rst') setup( name='django-embed-video', packages=find_packages(), - version='0.2.3', + version='0.3', author='Juda Kaleta', author_email='juda.kaleta@gmail.com', url='https://github.com/yetty/django-embed-video',