From 5e472afc2a667bdf7be68e50689b594a9b022007 Mon Sep 17 00:00:00 2001 From: Thijs Triemstra Date: Thu, 14 Apr 2016 17:08:38 +0200 Subject: [PATCH] add HTML5 audio and video backends --- .coveragerc | 2 +- .gitignore | 1 + .travis.yml | 43 ++++----- MANIFEST.in | 5 + docs/development/index.rst | 2 +- docs/development/testing.rst | 27 +++--- docs/example-project.rst | 5 +- docs/examples.rst | 18 ++-- docs/index.rst | 8 +- docs/installation.rst | 11 ++- embed_video/admin.py | 2 +- embed_video/backends.py | 92 +++++++++++++++++-- embed_video/fields.py | 2 +- embed_video/settings.py | 2 + .../templates/embed_video/html5_audio.html | 1 + .../templates/embed_video/html5_video.html | 1 + .../tests/backends/tests_html5backend.py | 28 ++++++ embed_video/tests/django_settings.py | 2 + embed_video/tests/test-requirements.txt | 7 ++ embed_video/utils.py | 2 +- example_project/README.rst | 11 ++- example_project/example_project/settings.py | 47 ++++++---- .../example_project/templates/base.html | 2 +- example_project/example_project/urls.py | 13 +-- example_project/example_project/wsgi.py | 2 +- .../posts/migrations/0001_initial.py | 25 +++++ example_project/posts/migrations/__init__.py | 0 example_project/posts/models.py | 6 +- example_project/posts/urls.py | 6 +- setup.cfg | 3 + tox.ini | 22 +++++ 31 files changed, 291 insertions(+), 107 deletions(-) create mode 100644 embed_video/templates/embed_video/html5_audio.html create mode 100644 embed_video/templates/embed_video/html5_video.html create mode 100644 embed_video/tests/backends/tests_html5backend.py create mode 100644 embed_video/tests/test-requirements.txt create mode 100644 example_project/posts/migrations/0001_initial.py create mode 100644 example_project/posts/migrations/__init__.py create mode 100644 setup.cfg create mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc index af8fbca..d5cf153 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,2 @@ -[report] +[run] include = embed_video/* diff --git a/.gitignore b/.gitignore index 565372c..688d829 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ dist .coverage htmlcov .idea +.tox diff --git a/.travis.yml b/.travis.yml index de1ddf5..01fca11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,20 @@ -sudo: false language: python -python: - - "2.7" - - "3.4" -cache: pip +cache: + directories: + - $HOME/.cache/pip env: - - DJANGO_VERSION=1.5.2 - - DJANGO_VERSION=1.6.5 - - DJANGO_VERSION=1.7.0 - - DJANGO_VERSION=1.8.0 - - DJANGO_VERSION=1.8.6 - - DJANGO_VERSION=1.9.4 -install: - - pip install -q Django==$DJANGO_VERSION - - pip install coveralls - - pip install mock - - pip install south - - pip install nose - - pip install testfixtures -script: - - python setup.py build - - PYTHONHASHSEED=0 python setup.py nosetests --verbosity 2 --with-coverage --cover-tests --cover-erase -after_success: - - coveralls -notifications: - email: false + - TOXENV=py27-django15 + - TOXENV=py27-django16 + - TOXENV=py27-django17 + - TOXENV=py27-django18 + - TOXENV=py27-django19 + - TOXENV=py27-django110 + - TOXENV=py34-django15 + - TOXENV=py34-django16 + - TOXENV=py34-django17 + - TOXENV=py34-django18 + - TOXENV=py34-django19 + - TOXENV=py34-django110 +install: pip install tox coveralls +script: tox +after_success: coveralls diff --git a/MANIFEST.in b/MANIFEST.in index bdd1844..7193225 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,3 +3,8 @@ include CHANGES.rst include LICENSE recursive-include embed_video/templates *.html + +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] +recursive-exclude * *~ +recursive-exclude * .coverage diff --git a/docs/development/index.rst b/docs/development/index.rst index d875990..ad0733a 100644 --- a/docs/development/index.rst +++ b/docs/development/index.rst @@ -1,4 +1,4 @@ -Development +Development =========== diff --git a/docs/development/testing.rst b/docs/development/testing.rst index fa21d58..342ab9e 100644 --- a/docs/development/testing.rst +++ b/docs/development/testing.rst @@ -4,23 +4,22 @@ Testing Requirements ------------ -The library needs ``Django`` and ``requests`` and ``nose``, ``mock``, -``south`` and ``testfixtures`` libraries to run tests. - -:: +The library needs a copy of ``Django``, e.g.:: pip install Django - pip install requests - pip install nose - pip install mock - pip install south - pip install testfixtures +And several libraries that can be installed from the root of +a source checkout:: + + pip install -r embed_video/tests/test-requirements.txt + +It's recommended to make a separate virtual environment where you +install these packages. Running tests ------------- -Run tests with this command: +Run tests in your virtual environment with this command: :: @@ -34,7 +33,13 @@ Run tests with coverage: :: - pip install coverage nosetests --with-coverage --cover-package=embed_video +Use tox if you want to test against all supported Django versions, +similar to the Travis build. Install it once:: + pip install tox>=2.0 + +Run the tests with:: + + tox diff --git a/docs/example-project.rst b/docs/example-project.rst index 07dd684..b1d0bb5 100644 --- a/docs/example-project.rst +++ b/docs/example-project.rst @@ -1,7 +1,8 @@ Example project =============== -For easy start with using django-embed-video, you can take a look at example -project. It is located in example_project directory in root of repository. +To get started with django-embed-video, you can take a look at the example +project. It is located in the ``example_project`` directory in the root of +the repository. .. include:: ../example_project/README.rst diff --git a/docs/examples.rst b/docs/examples.rst index b78e3c3..45f8968 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -48,8 +48,8 @@ Default sizes are ``tiny`` (420x315), ``small`` (480x360), ``medium`` (640x480), {% video my_video '100% x 50%' %} -It is possible to set backend options via parameters in template tag. It is -useful for example to enforce HTTPS protocol or set different query appended +It is possible to set backend options via parameters in the template tag. It is +useful to enforce HTTPS protocol for example or to set different query appended to url. :: @@ -73,8 +73,8 @@ to url. .. tip:: - You can overwrite default template of embed code located in - ``templates/embed_video/embed_code.html`` or set own file for custom + You can overwrite the default template of the embed code located in + ``templates/embed_video/embed_code.html`` or use your own file for a custom backend (:py:data:`~embed_video.backends.VideoBackend.template_name`). .. versionadded:: 0.9 @@ -87,7 +87,7 @@ Model examples .. highlight:: python -Using the ``EmbedVideoField`` provides you validation of URLs. +Using the ``EmbedVideoField`` provides validation of URLs. :: @@ -121,8 +121,8 @@ Use ``AdminVideoMixin`` in ``admin.py``. Custom backends ############### -If you have specific needs and default backends don't suits you, you can write -your custom backend. +If you have specific needs and the default backend doesn't suit you, +you can write a custom backend. ``my_project/my_app/backends.py``:: @@ -139,7 +139,7 @@ your custom backend. template_name = 'embed_video/custombackend_embed_code.html' # added in v0.9 You can also overwrite :py:class:`~embed_video.backends.VideoBackend` methods, -if using regular expressions isn't well enough. +if using regular expressions isn't good enough. ``my_project/my_project/settings.py``:: @@ -155,7 +155,7 @@ if using regular expressions isn't well enough. Low level API examples ###################### -You can get instance of :py:class:`~embed_video.backends.VideoBackend` in your +You can get an instance of :py:class:`~embed_video.backends.VideoBackend` in your python code thanks to :py:func:`~embed_video.backends.detect_backend`: :: diff --git a/docs/index.rst b/docs/index.rst index bc2a4d0..09ae73c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,12 +1,11 @@ django-embed-video's documentation ================================== -Django app for easy embeding YouTube and Vimeo videos and music from SoundCloud. +Django app for easy embedding YouTube and Vimeo videos and music from SoundCloud. Repository is located on GitHub: https://github.com/jazzband/django-embed-video - .. toctree:: :maxdepth: 2 :glob: @@ -15,7 +14,7 @@ Repository is located on GitHub: https://github.com/jazzband/django-embed-video examples example-project - development/index + development/index websites @@ -27,7 +26,7 @@ Library API .. toctree:: :maxdepth: 2 - api/index + api/index @@ -37,4 +36,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff --git a/docs/installation.rst b/docs/installation.rst index 6b1baa9..17ead56 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -4,14 +4,14 @@ Installation & Setup Installation ############ -The simpliest way is to use pip to install package: +The simpliest way is to use pip to install the package: .. code-block:: bash pip install django-embed-video -If you want latest version, you may use Git. It is fresh, but unstable. +If you want the latest version, you may use Git. It is fresh, but unstable. .. code-block:: bash @@ -28,12 +28,13 @@ settings. INSTALLED_APPS = ( ... - + 'embed_video', ) -To detect HTTP/S you must use :py:class:`~django.core.context_processors.request` -context processor: +To detect HTTP/S you must add the :py:class:`~django.template.context_processors.request` +(or :py:class:`~django.core.context_processors.request` for Django < 1.9) context +processor: .. code-block:: python diff --git a/embed_video/admin.py b/embed_video/admin.py index 88f139d..6a58ef4 100644 --- a/embed_video/admin.py +++ b/embed_video/admin.py @@ -14,7 +14,7 @@ class AdminVideoWidget(forms.TextInput): .. todo:: - Django 1.6 provides better parent for this widget - + Django 1.6 provides a better parent for this widget - :py:class:`django.forms.URLInput`. """ diff --git a/embed_video/backends.py b/embed_video/backends.py index 6fc957a..bc2524a 100644 --- a/embed_video/backends.py +++ b/embed_video/backends.py @@ -19,17 +19,23 @@ from .settings import EMBED_VIDEO_BACKENDS, EMBED_VIDEO_TIMEOUT, \ class EmbedVideoException(Exception): - """ Parental class for all embed_video exceptions """ + """ + Parental class for all embed_video exceptions. + """ pass class VideoDoesntExistException(EmbedVideoException): - """ Exception thrown if video doesn't exist """ + """ + Exception thrown if video doesn't exist. + """ pass class UnknownBackendException(EmbedVideoException): - """ Exception thrown if video backend is not recognized. """ + """ + Exception thrown if video backend is not recognized. + """ pass @@ -53,7 +59,6 @@ def detect_backend(url): :return: Returns recognized VideoBackend :rtype: VideoBackend """ - for backend_name in EMBED_VIDEO_BACKENDS: backend = import_by_path(backend_name) if backend.is_valid(url): @@ -97,7 +102,7 @@ class VideoBackend(object): re_detect = None """ - Compilede regec (:py:func:`re.compile`) to detect, if input URL is valid + Compiled regex (:py:func:`re.compile`) to detect, if input URL is valid for current backend. Example: ``re.compile(r'^http://myvideo\.com/.*')`` @@ -140,7 +145,7 @@ class VideoBackend(object): default_query = '' """ - Default query string or `QueryDict` appended to url + Default query string or ``QueryDict`` appended to url. :type: str """ @@ -154,7 +159,7 @@ class VideoBackend(object): def __init__(self, url): """ - First it tries to load data from cache and if it don't succeed, run + First it tries to load data from cache and if it doesn't succeed, run :py:meth:`init` and then save it to cache. :type url: str @@ -201,7 +206,7 @@ class VideoBackend(object): @property def query(self): """ - String transformed to QueryDict appended to url. + String transformed to ``QueryDict`` appended to url. """ return self._query @@ -218,7 +223,7 @@ class VideoBackend(object): def is_valid(cls, url): """ Class method to control if passed url is valid for current backend. By - default it is done by :py:data:`re_detect` regex. + default this is done using the :py:data:`re_detect` regex. :type url: str """ @@ -280,6 +285,75 @@ class VideoBackend(object): setattr(self, key, options[key]) +class Html5Backend(VideoBackend): + """ + Backend for HTML5 media. + """ + def get_url(self): + """ + Returns URL. + """ + return self._url + + def get_code(self): + """ + Returns video code. + + :rtype: str + """ + return None + + def get_thumbnail_url(self): + """ + Returns thumbnail URL. + + :rtype: str + """ + return None + + +class Html5VideoBackend(Html5Backend): + """ + Backend for HTML5 video. + """ + template_name = 'embed_video/html5_video.html' + + @classmethod + def is_valid(cls, url): + """ + Class method to control if passed url is valid for current backend. + + :type url: str + """ + try: + r = requests.head(url, timeout=EMBED_VIDEO_TIMEOUT) + except: + raise VideoDoesntExistException() + + return r.headers.get('content-type').startswith('video') + + +class Html5AudioBackend(Html5Backend): + """ + Backend for HTML5 audio. + """ + template_name = 'embed_video/html5_audio.html' + + @classmethod + def is_valid(cls, url): + """ + Class method to control if passed url is valid for current backend. + + :type url: str + """ + try: + r = requests.head(url, timeout=EMBED_VIDEO_TIMEOUT) + except: + raise VideoDoesntExistException() + + return r.headers.get('content-type').startswith('audio') + + class YoutubeBackend(VideoBackend): """ Backend for YouTube URLs. diff --git a/embed_video/fields.py b/embed_video/fields.py index 560c840..d4b8d86 100644 --- a/embed_video/fields.py +++ b/embed_video/fields.py @@ -33,7 +33,7 @@ class EmbedVideoField(models.URLField): class EmbedVideoFormField(forms.URLField): """ - Form field for embeded video. Descendant of + Form field for embedded video. Descendant of :py:class:`django.forms.URLField` """ diff --git a/embed_video/settings.py b/embed_video/settings.py index f24dcdc..d715e47 100644 --- a/embed_video/settings.py +++ b/embed_video/settings.py @@ -6,6 +6,8 @@ EMBED_VIDEO_BACKENDS = getattr(settings, 'EMBED_VIDEO_BACKENDS', ( 'embed_video.backends.VimeoBackend', 'embed_video.backends.WistiaBackend', 'embed_video.backends.SoundCloudBackend', + 'embed_video.backends.Html5VideoBackend', + 'embed_video.backends.Html5AudioBackend', )) """ :type: tuple[str] """ diff --git a/embed_video/templates/embed_video/html5_audio.html b/embed_video/templates/embed_video/html5_audio.html new file mode 100644 index 0000000..0000ee0 --- /dev/null +++ b/embed_video/templates/embed_video/html5_audio.html @@ -0,0 +1 @@ + diff --git a/embed_video/templates/embed_video/html5_video.html b/embed_video/templates/embed_video/html5_video.html new file mode 100644 index 0000000..988162f --- /dev/null +++ b/embed_video/templates/embed_video/html5_video.html @@ -0,0 +1 @@ + diff --git a/embed_video/tests/backends/tests_html5backend.py b/embed_video/tests/backends/tests_html5backend.py new file mode 100644 index 0000000..bdea65f --- /dev/null +++ b/embed_video/tests/backends/tests_html5backend.py @@ -0,0 +1,28 @@ +from unittest import TestCase + +from . import BackendTestMixin +from embed_video import backends + + +class HTML5BackendTestCase(TestCase): + def test_url(self): + url = 'https://collab-project.github.io/videojs-wavesurfer/examples/media/heres_johnny.wav' + backend = backends.detect_backend(url) + self.assertEqual(backend.get_url(), url) + + +class Html5VideoBackendTestCase(BackendTestMixin, TestCase): + urls = ( + ('http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4', None), + ('https://collab-project.github.io/videojs-wavesurfer/examples/media/example.mp4', None), + ) + + instance = backends.Html5VideoBackend + + +class Html5AudioBackendTestCase(BackendTestMixin, TestCase): + urls = ( + ('https://collab-project.github.io/videojs-wavesurfer/examples/media/heres_johnny.wav', None), + ) + + instance = backends.Html5AudioBackend diff --git a/embed_video/tests/django_settings.py b/embed_video/tests/django_settings.py index b2f7f4f..13d9a06 100644 --- a/embed_video/tests/django_settings.py +++ b/embed_video/tests/django_settings.py @@ -20,6 +20,8 @@ EMBED_VIDEO_BACKENDS = ( 'embed_video.backends.VimeoBackend', 'embed_video.backends.WistiaBackend', 'embed_video.backends.SoundCloudBackend', + 'embed_video.backends.Html5VideoBackend', + 'embed_video.backends.Html5AudioBackend', 'embed_video.tests.backends.tests_custom_backend.CustomBackend', ) diff --git a/embed_video/tests/test-requirements.txt b/embed_video/tests/test-requirements.txt new file mode 100644 index 0000000..a6933ec --- /dev/null +++ b/embed_video/tests/test-requirements.txt @@ -0,0 +1,7 @@ +requests +nose +mock +south +testfixtures +coverage +flake8 \ No newline at end of file diff --git a/embed_video/utils.py b/embed_video/utils.py index 7674165..501035e 100644 --- a/embed_video/utils.py +++ b/embed_video/utils.py @@ -6,7 +6,7 @@ from django.core.exceptions import ImproperlyConfigured def import_by_path(dotted_path, error_prefix=''): """ Import a dotted module path and return the attribute/class designated by - the last name in the path. Raise ImproperlyConfigured if something goes + the last name in the path. Raise ``ImproperlyConfigured`` if something goes wrong. .. warning:: diff --git a/example_project/README.rst b/example_project/README.rst index 113a29d..ca3c1a9 100644 --- a/example_project/README.rst +++ b/example_project/README.rst @@ -8,18 +8,21 @@ Running example project #. Create database:: - python manage.py syncdb --noinput + python manage.py migrate --noinput + +#. And superuser:: + + python manage.py createsuperuser #. Run testing server:: python manage.py runserver -#. Take a look at http://localhost:8000 . You can log in to administration with username ``admin`` - and password ``admin``. +#. Create new posts at http://localhost:8000/admin/ and view them on http://localhost:8000. Testing HTTPS ************* -To test HTTPS on development server, `follow this instructions +To test HTTPS on a development server, `follow these instructions `_. diff --git a/example_project/example_project/settings.py b/example_project/example_project/settings.py index 7e153ff..ee20caf 100644 --- a/example_project/example_project/settings.py +++ b/example_project/example_project/settings.py @@ -1,7 +1,6 @@ # Django settings for example_project project. DEBUG = True -TEMPLATE_DEBUG = DEBUG DATABASES = { 'default': { @@ -14,24 +13,37 @@ SITE_ID = 1 SECRET_KEY = 'u%38dln@$1!7w#cxi4np504^sa3_skv5aekad)jy_u0v2mc+nr' -TEMPLATE_CONTEXT_PROCESSORS = ( - 'django.contrib.auth.context_processors.auth', - 'django.core.context_processors.debug', - 'django.core.context_processors.i18n', - 'django.core.context_processors.media', - 'django.core.context_processors.static', - 'django.core.context_processors.tz', - 'django.contrib.messages.context_processors.messages', - 'django.core.context_processors.request', -) - -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -) - APPEND_SLASH = True +MIDDLEWARE_CLASSES = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'example_project.wsgi.application' + ROOT_URLCONF = 'example_project.urls' STATIC_URL = '/static/' @@ -42,6 +54,7 @@ DJANGO_APPS = ( 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', + 'django.contrib.staticfiles', 'django.contrib.admin', ) diff --git a/example_project/example_project/templates/base.html b/example_project/example_project/templates/base.html index e625066..c0740d4 100644 --- a/example_project/example_project/templates/base.html +++ b/example_project/example_project/templates/base.html @@ -7,7 +7,7 @@ django-embed-video example project - +