diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7bbbcfd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 80 + +[*.{html,js}] +max_line_length = 120 + +[*.yml] +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af25823 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.pyc +*.egg-info +.DS_Store +.idea/ +.tox/ +dist/ +build/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e2aa7f8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +# Config file for automatic testing at travis-ci.org + +language: python + +sudo: false + +env: + - TOX_ENV=flake8 + - TOX_ENV=py27-latest + - TOX_ENV=py34-latest + # Django 1.8 + - TOX_ENV=py34-dj18-cms33 + - TOX_ENV=py34-dj18-cms32 + - TOX_ENV=py27-dj18-cms33 + - TOX_ENV=py27-dj18-cms32 + # Django 1.9 + - TOX_ENV=py34-dj19-cms33 + - TOX_ENV=py34-dj19-cms32 + - TOX_ENV=py27-dj19-cms33 + - TOX_ENV=py27-dj19-cms32 + +install: + - pip install tox + - pip install coveralls + +script: + - tox -e $TOX_ENV + +after_success: + - coveralls diff --git a/.tx/config b/.tx/config new file mode 100644 index 0000000..2a2e6b5 --- /dev/null +++ b/.tx/config @@ -0,0 +1,8 @@ +[main] +host = https://www.transifex.com + +[djangocms-audio.djangocms_audio] +file_filter = djangocms_audio/locale//LC_MESSAGES/django.po +source_file = djangocms_audio/locale/en/LC_MESSAGES/django.po +source_lang = en +type = PO diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..fe3f7f7 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,9 @@ +========= +Changelog +========= + + +0.1.0 (to be released) +================== + +* Initial release diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8211b89 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,24 @@ +Copyright (c) 2016, Divio AG +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Divio AG nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL DIVIO AG BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..09f7d83 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +recursive-include djangocms_audio/templates * +recursive-include djangocms_audio/static * +recursive-include djangocms_audio/locale * diff --git a/README.rst b/README.rst index 1c451bc..5ffcc6b 100644 --- a/README.rst +++ b/README.rst @@ -1 +1,98 @@ -# djangocms-audio +================ +django CMS Audio +================ + + +|pypi| |build| |coverage| + +**django CMS Audio** is a set of plugins for `django CMS `_ +that allow you to publish audio files on your site (using an HTML5 player by default, +but you can override this in your own templates if required). + +It uses files managed by `Django Filer `_. +The plugins allow you to select a single file or an entire folder of files. + +This addon is compatible with `Aldryn `_ and is also available on the +`django CMS Marketplace `_ +for easy installation. + +.. image:: preview.gif + + +Contributing +============ + +This is a an open-source project. We'll be delighted to receive your +feedback in the form of issues and pull requests. Before submitting your +pull request, please review our `contribution guidelines +`_. + +One of the easiest contributions you can make is helping to translate this addon on +`Transifex `_. + + +Documentation +============= + + +See ``REQUIREMENTS`` in the `setup.py +`_ file +for additional dependencies: + +* Python 2.7, 3.4 or higher +* Django 1.8 or higher + + +Installation +------------ + +For a manual install: + +* run ``pip install djangocms-audio`` +* add ``djangocms_audio`` to your ``INSTALLED_APPS`` +* run ``python manage.py migrate djangocms_audio`` + + +Configuration +------------- + +Note that the provided templates are very minimal by design. You are encouraged +to adapt and override them to your project's requirements. + +This addon provides a ``default`` template for all instances. You can provide +additional template choices by adding a ``DJANGOCMS_AUDIO_TEMPLATES`` +setting:: + + DJANGOCMS_AUDIO_TEMPLATES = [ + ('feature', _('Featured Version')), + ] + +You'll need to create the `feature` folder inside ``templates/djangocms_audio/`` +otherwise you will get a *template does not exist* error. You can do this by +copying the ``standard`` folder inside that directory and renaming it to +``feature``. + +``MP3`` and ``OGG`` files are allowed by default. We recommend using ``MP3`` +as it's supported across all major browsers. You can change the default +setting by overriding:: + + ALLOWED_EXTENSIONS = ['mp3', 'ogg', 'wav'] + + +Running Tests +------------- + +You can run tests by executing:: + + virtualenv env + source env/bin/activate + pip install -r tests/requirements.txt + python setup.py test + + +.. |pypi| image:: https://badge.fury.io/py/djangocms-audio.svg + :target: http://badge.fury.io/py/djangocms-audio +.. |build| image:: https://travis-ci.org/divio/djangocms-audio.svg?branch=master + :target: https://travis-ci.org/divio/djangocms-video +.. |coverage| image:: https://coveralls.io/repos/github/divio/djangocms-audio/badge.svg?branch=master + :target: https://coveralls.io/github/divio/djangocms-audio?branch=master diff --git a/addon.json b/addon.json new file mode 100644 index 0000000..33e7bdc --- /dev/null +++ b/addon.json @@ -0,0 +1,6 @@ +{ + "package-name": "djangocms-audio", + "installed-apps": [ + "djangocms_audio" + ] +} diff --git a/aldryn_config.py b/aldryn_config.py new file mode 100644 index 0000000..db0a117 --- /dev/null +++ b/aldryn_config.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from aldryn_client import forms +from django.template.defaultfilters import slugify + + +class Form(forms.BaseForm): + templates = forms.CharField( + 'List of additional templates (comma separated)', + required=False, + ) + extensions = forms.CharField( + 'List of allowed extensions, default "mp3, ogg" when empty (comma separated)', + required=False, + ) + + def clean(self, value): + for x in value.split(','): + yield (slugify(x.strip()), x) + + def to_settings(self, data, settings): + settings['DJANGOCMS_AUDIO_TEMPLATES'] = self.clean(templates) + if data[extensions]: + settings['DJANGOCMS_AUDIO_ALLOWED_EXTENSIONS'] = self.clean(extensions) + return settings diff --git a/djangocms_audio/__init__.py b/djangocms_audio/__init__.py new file mode 100644 index 0000000..a8d2807 --- /dev/null +++ b/djangocms_audio/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +__version__ = '0.1.0' diff --git a/djangocms_audio/cms_plugins.py b/djangocms_audio/cms_plugins.py new file mode 100644 index 0000000..81065f2 --- /dev/null +++ b/djangocms_audio/cms_plugins.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +from django.utils.translation import ugettext_lazy as _ + +from cms.plugin_base import CMSPluginBase +from cms.plugin_pool import plugin_pool + +from . import models + + +class AudioPlayerPlugin(CMSPluginBase): + model = models.AudioPlayer + name = _('Audio Player') + allow_children = True + child_classes = ['AudioFilePlugin', 'AudioFolderPlugin'] + + fieldsets = [ + (None, { + 'fields': ( + 'template', + 'label', + ) + }), + (_('Advanced settings'), { + 'classes': ('collapse',), + 'fields': ( + 'attributes', + ) + }) + ] + + def render(self, context, instance, placeholder): + context = super(AudioPlayerPlugin, self).render(context, instance, placeholder) + context['audio_template'] = instance.template + return context + + def get_render_template(self, context, instance, placeholder): + return 'djangocms_audio/{}/audio_player.html'.format(instance.template) + + +class AudioFilePlugin(CMSPluginBase): + model = models.AudioFile + name = _('File') + module = _('Audio player') + allow_children = True + require_parent = True + parent_classes = ['AudioPlayerPlugin'] + child_classes = ['AudioTrackPlugin'] + + fieldsets = [ + (None, { + 'fields': ( + 'audio_file', + 'text_title', + ) + }), + (_('Advanced settings'), { + 'classes': ('collapse',), + 'fields': ( + 'text_description', + 'attributes', + ) + }) + ] + + def get_render_template(self, context, instance, placeholder): + return 'djangocms_audio/{}/file.html'.format(context['audio_template']) + + +class AudioFolderPlugin(CMSPluginBase): + model = models.AudioFolder + name = _('Folder') + module = _('Audio player') + require_parent = True + parent_classes = ['AudioPlayerPlugin'] + + fieldsets = [ + (None, { + 'fields': ( + 'audio_folder', + ) + }), + (_('Advanced settings'), { + 'classes': ('collapse',), + 'fields': ( + 'attributes', + ) + }) + ] + + def get_render_template(self, context, instance, placeholder): + return 'djangocms_audio/{}/folder.html'.format(context['audio_template']) + + +class AudioTrackPlugin(CMSPluginBase): + model = models.AudioTrack + name = _('Track') + module = _('Audio player') + require_parent = True + parent_classes = ['AudioFilePlugin'] + + fieldsets = [ + (None, { + 'fields': ( + 'kind', + 'src', + 'srclang', + ) + }), + (_('Advanced settings'), { + 'classes': ('collapse',), + 'fields': ( + 'label', + 'attributes', + ) + }) + ] + + def get_render_template(self, context, instance, placeholder): + return 'djangocms_audio/{}/track.html'.format(context['audio_template']) + + +plugin_pool.register_plugin(AudioPlayerPlugin) +plugin_pool.register_plugin(AudioFilePlugin) +plugin_pool.register_plugin(AudioFolderPlugin) +plugin_pool.register_plugin(AudioTrackPlugin) diff --git a/djangocms_audio/locale/en/LC_MESSAGES/django.po b/djangocms_audio/locale/en/LC_MESSAGES/django.po new file mode 100644 index 0000000..a86f541 --- /dev/null +++ b/djangocms_audio/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,123 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-07-27 15:31+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: cms_plugins.py:12 +msgid "Audio Player" +msgstr "" + +#: cms_plugins.py:23 cms_plugins.py:56 cms_plugins.py:82 cms_plugins.py:109 +msgid "Advanced settings" +msgstr "" + +#: cms_plugins.py:42 models.py:81 +msgid "File" +msgstr "" + +#: cms_plugins.py:43 cms_plugins.py:72 cms_plugins.py:97 +msgid "Audio player" +msgstr "" + +#: cms_plugins.py:71 models.py:133 +msgid "Folder" +msgstr "" + +#: cms_plugins.py:96 +msgid "Track" +msgstr "" + +#: models.py:51 +msgid "Default" +msgstr "" + +#: models.py:56 +msgid "Template" +msgstr "" + +#: models.py:62 models.py:200 +msgid "Label" +msgstr "" + +#: models.py:67 models.py:97 models.py:140 models.py:205 +msgid "Attributes" +msgstr "" + +#: models.py:88 +msgid "Title" +msgstr "" + +#: models.py:93 +msgid "Description" +msgstr "" + +#: models.py:110 +#, python-format +msgid "Incorrect file type: %(extension)s." +msgstr "" + +#: models.py:117 +msgid "" +msgstr "" + +#: models.py:142 +msgid "Is applied to all audio file instances." +msgstr "" + +#: models.py:161 +msgid "" +msgstr "" + +#: models.py:175 +msgid "Subtitles" +msgstr "" + +#: models.py:176 +msgid "Captions" +msgstr "" + +#: models.py:177 +msgid "Descriptions" +msgstr "" + +#: models.py:178 +msgid "Chapters" +msgstr "" + +#: models.py:182 +msgid "Kind" +msgstr "" + +#: models.py:187 +msgid "Source file" +msgstr "" + +#: models.py:194 +msgid "Source language" +msgstr "" + +#: models.py:197 +msgid "Examples: \"en\" or \"de\" etc." +msgstr "" + +#: templates/djangocms_audio/default/audio_player.html:8 +msgid "No audio file available." +msgstr "" + +#: templates/djangocms_audio/default/folder.html:11 +msgid "No matching audio files were found in the specified folder." +msgstr "" diff --git a/djangocms_audio/migrations/0001_initial.py b/djangocms_audio/migrations/0001_initial.py new file mode 100644 index 0000000..b9ac7d8 --- /dev/null +++ b/djangocms_audio/migrations/0001_initial.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import filer.fields.file +import filer.fields.folder +import django.db.models.deletion +import djangocms_attributes_field.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('cms', '0016_auto_20160608_1535'), + ('filer', '0006_auto_20160623_1627'), + ] + + operations = [ + migrations.CreateModel( + name='AudioFile', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(parent_link=True, related_name='djangocms_audio_audiofile', auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin')), + ('text_title', models.CharField(max_length=200, verbose_name='Title', blank=True)), + ('text_description', models.TextField(verbose_name='Description', blank=True)), + ('attributes', djangocms_attributes_field.fields.AttributesField(default=dict, verbose_name='Attributes', blank=True)), + ('audio_file', filer.fields.file.FilerFileField(related_name='+', on_delete=django.db.models.deletion.SET_NULL, verbose_name='File', to='filer.File', null=True)), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + migrations.CreateModel( + name='AudioFolder', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(parent_link=True, related_name='djangocms_audio_audiofolder', auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin')), + ('attributes', djangocms_attributes_field.fields.AttributesField(default=dict, help_text='Is applied to all audio file instances.', verbose_name='Attributes', blank=True)), + ('audio_folder', filer.fields.folder.FilerFolderField(related_name='+', on_delete=django.db.models.deletion.SET_NULL, verbose_name='Folder', to='filer.Folder', null=True)), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + migrations.CreateModel( + name='AudioPlayer', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(parent_link=True, related_name='djangocms_audio_audioplayer', auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin')), + ('template', models.CharField(default=b'default', max_length=50, verbose_name='Template', choices=[(b'default', 'Default')])), + ('label', models.CharField(max_length=200, verbose_name='Label', blank=True)), + ('attributes', djangocms_attributes_field.fields.AttributesField(default=dict, verbose_name='Attributes', blank=True)), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + migrations.CreateModel( + name='AudioTrack', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(parent_link=True, related_name='djangocms_audio_audiotrack', auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin')), + ('kind', models.CharField(max_length=50, verbose_name='Kind', choices=[(b'subtitles', 'Subtitles'), (b'captions', 'Captions'), (b'descriptions', 'Descriptions'), (b'chapters', 'Chapters')])), + ('srclang', models.CharField(help_text='Examples: "en" or "de" etc.', max_length=10, verbose_name='Source language', blank=True)), + ('label', models.CharField(max_length=200, verbose_name='Label', blank=True)), + ('attributes', djangocms_attributes_field.fields.AttributesField(default=dict, verbose_name='Attributes', blank=True)), + ('src', filer.fields.file.FilerFileField(related_name='+', on_delete=django.db.models.deletion.SET_NULL, verbose_name='Source file', to='filer.File', null=True)), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + ] diff --git a/djangocms_audio/migrations/__init__.py b/djangocms_audio/migrations/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/djangocms_audio/migrations/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/djangocms_audio/models.py b/djangocms_audio/models.py new file mode 100644 index 0000000..99eb08b --- /dev/null +++ b/djangocms_audio/models.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +""" +Enables the user to add an "Audio player" plugin that serves as +a wrapper rendering the player and its options. + +The "Audio player" plugin allows to add either a single "File" or a reference +to a "Folder" as children. +""" +from django.db import models +from django.conf import settings +from django.core.exceptions import ValidationError +from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import ugettext, ugettext_lazy as _ + +from cms.models import CMSPlugin + +from djangocms_attributes_field.fields import AttributesField + +from filer.fields.file import FilerFileField +from filer.fields.folder import FilerFolderField + + +# mp3 is supported by all major browsers +ALLOWED_EXTENSIONS = getattr( + settings, + 'DJANGOCMS_AUDIO_ALLOWED_EXTENSIONS', + ['mp3', 'ogg'], +) + + +# Add additional choices through the ``settings.py``. +def get_templates(): + choices = getattr( + settings, + 'DJANGOCMS_AUDIO_TEMPLATES', + [], + ) + if choices: + return choices + return [] + + +@python_2_unicode_compatible +class AudioPlayer(CMSPlugin): + """ + Renders a container around the HTML