Commit 7a4cf265 authored by Seb's avatar Seb
Browse files

Added ability to change the video model

parent 6d7c3bd8
...@@ -27,4 +27,15 @@ lts_211: ...@@ -27,4 +27,15 @@ lts_211:
image: python:3.8 image: python:3.8
extends: .python_test extends: .python_test
before_script: before_script:
- pip install .['testing'] wagtail~=2.11 django~=3.1 - pip install .['testing'] wagtail~=2.11 django~=3.1
\ No newline at end of file
build:
image: python:3.8
stage: release
before_script:
- pip install --upgrade setuptools wheel twine
script:
- ./setup.py sdist bdist_wheel
- twine upload dist/*
only:
- tags
...@@ -43,6 +43,7 @@ Implement as a ``ForeignKey`` relation, same as wagtailimages. ...@@ -43,6 +43,7 @@ Implement as a ``ForeignKey`` relation, same as wagtailimages.
from wagtailvideos.edit_handlers import VideoChooserPanel from wagtailvideos.edit_handlers import VideoChooserPanel
class HomePage(Page): class HomePage(Page):
body = RichtextField() body = RichtextField()
header_video = models.ForeignKey('wagtailvideos.Video', header_video = models.ForeignKey('wagtailvideos.Video',
...@@ -68,6 +69,7 @@ A VideoChooserBlock is included ...@@ -68,6 +69,7 @@ A VideoChooserBlock is included
from wagtailvideos.blocks import VideoChooserBlock from wagtailvideos.blocks import VideoChooserBlock
class ContentPage(Page): class ContentPage(Page):
body = StreamField([ body = StreamField([
('video', VideoChooserBlock()), ('video', VideoChooserBlock()),
...@@ -90,6 +92,8 @@ tag. The original video and all extra transcodes are added as ...@@ -90,6 +92,8 @@ tag. The original video and all extra transcodes are added as
{% load wagtailvideos_tags %} {% load wagtailvideos_tags %}
{% video self.header_video autoplay controls width=256 %} {% video self.header_video autoplay controls width=256 %}
Jinja2 extensions are also included.
How to transcode using ffmpeg: How to transcode using ffmpeg:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...@@ -99,8 +103,46 @@ be used to create new transcodes. It is assumed that your compiled ...@@ -99,8 +103,46 @@ be used to create new transcodes. It is assumed that your compiled
version of ffmpeg has the matching codec libraries required for the version of ffmpeg has the matching codec libraries required for the
transcode. transcode.
Custom Video models:
~~~~~~~~~~~~~~~~~~~~
Same as Wagtail Images, a custom model can be used to replace the built in Video model using the
``WAGTAILVIDEOS_VIDEO_MODEL`` setting.
.. code:: django
# settings.py
WAGTAILVIDEOS_VIDEO_MODEL = 'videos.AttributedVideo'
# app.videos.models
from django.db import models
from wagtailvideos.models import AbstractVideo, AbstractVideoTranscode
class AttributedVideo(AbstractVideo):
attribution = models.TextField()
admin_form_fields = (
'title',
'attribution',
'file',
'collection',
'thumbnail',
'tags',
)
class CustomTranscode(AbstractVideoTranscode):
video = models.ForeignKey(AttributedVideo, related_name='transcodes', on_delete=models.CASCADE)
class Meta:
unique_together = (
('video', 'media_format')
)
Future features Future features
--------------- ---------------
- Some docs
- Richtext embed - Richtext embed
- Transcoding via amazon service rather than ffmpeg - Transcoding via amazon service rather than ffmpeg
# Generated by Django 2.2.17 on 2021-01-27 23:53
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import enumchoicefield.fields
import taggit.managers
import wagtail.core.models
import wagtail.search.index
import wagtailvideos.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('taggit', '0003_taggeditem_add_unique_index'),
('wagtailcore', '0059_apply_collection_ordering'),
('app', '0002_testpage_video_streamfield'),
]
operations = [
migrations.CreateModel(
name='CustomVideoModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255, verbose_name='title')),
('file', models.FileField(upload_to=wagtailvideos.models.get_upload_to, verbose_name='file')),
('thumbnail', models.ImageField(blank=True, null=True, upload_to=wagtailvideos.models.get_upload_to)),
('created_at', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='created at')),
('duration', models.DurationField(blank=True, null=True)),
('file_size', models.PositiveIntegerField(editable=False, null=True)),
('attribution', models.TextField()),
('collection', models.ForeignKey(default=wagtail.core.models.get_root_collection_id, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailcore.Collection', verbose_name='collection')),
('tags', taggit.managers.TaggableManager(blank=True, help_text=None, through='taggit.TaggedItem', to='taggit.Tag', verbose_name='tags')),
('uploaded_by_user', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='uploaded by user')),
],
options={
'ordering': ['-created_at'],
'abstract': False,
},
bases=(wagtail.search.index.Indexed, models.Model),
),
migrations.CreateModel(
name='CustomVideoTranscode',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('media_format', enumchoicefield.fields.EnumChoiceField(enum_class=wagtailvideos.models.MediaFormats, max_length=4)),
('quality', enumchoicefield.fields.EnumChoiceField(default=wagtailvideos.models.VideoQuality(1), enum_class=wagtailvideos.models.VideoQuality, max_length=7)),
('processing', models.BooleanField(default=False)),
('file', models.FileField(blank=True, null=True, upload_to=wagtailvideos.models.get_upload_to, verbose_name='file')),
('error_message', models.TextField(blank=True)),
('video', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transcodes', to='app.CustomVideoModel')),
],
options={
'unique_together': {('video', 'media_format')},
},
),
]
...@@ -5,6 +5,30 @@ from wagtail.admin.edit_handlers import StreamFieldPanel ...@@ -5,6 +5,30 @@ from wagtail.admin.edit_handlers import StreamFieldPanel
from wagtailvideos.edit_handlers import VideoChooserPanel from wagtailvideos.edit_handlers import VideoChooserPanel
from wagtailvideos.blocks import VideoChooserBlock from wagtailvideos.blocks import VideoChooserBlock
from wagtailvideos.models import AbstractVideo, AbstractVideoTranscode
class CustomVideoModel(AbstractVideo):
attribution = models.TextField()
admin_form_fields = (
'title',
'attribution',
'file',
'collection',
'thumbnail',
'tags',
)
class CustomVideoTranscode(AbstractVideoTranscode):
video = models.ForeignKey(CustomVideoModel, related_name='transcodes', on_delete=models.CASCADE)
class Meta:
unique_together = (
('video', 'media_format')
)
class TestPage(Page): class TestPage(Page):
video_field = models.ForeignKey( video_field = models.ForeignKey(
......
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase, override_settings
from wagtail.tests.utils import WagtailTestUtils
from wagtailvideos import get_video_model, get_video_model_string
from wagtailvideos.models import Video
from tests.app.models import CustomVideoModel
class TestGetVideoModel(WagtailTestUtils, TestCase):
@override_settings(WAGTAILVIDEOS_VIDEO_MODEL='app.CustomVideoModel')
def test_custom_get_video_model(self):
self.assertIs(get_video_model(), CustomVideoModel)
@override_settings(WAGTAILVIDEOS_VIDEO_MODEL='app.CustomVideoModel')
def test_custom_get_video_model_string(self):
self.assertEqual(get_video_model_string(), 'app.CustomVideoModel')
def test_standard_get_video_model(self):
self.assertIs(get_video_model(), Video)
def test_standard_get_video_model_string(self):
self.assertEqual(get_video_model_string(), 'wagtailvideos.Video')
@override_settings(WAGTAILVIDEOS_VIDEO_MODEL='app.UnknownModel')
def test_unknown_get_video_model(self):
with self.assertRaises(ImproperlyConfigured):
get_video_model()
@override_settings(WAGTAILVIDEOS_VIDEO_MODEL='invalid-string')
def test_invalid_get_video_model(self):
with self.assertRaises(ImproperlyConfigured):
get_video_model()
\ No newline at end of file
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
default_app_config = 'wagtailvideos.apps.WagtailVideosApp' default_app_config = 'wagtailvideos.apps.WagtailVideosApp'
def get_video_model_string():
return getattr(settings, 'WAGTAILVIDEOS_VIDEO_MODEL', 'wagtailvideos.Video')
def get_video_model():
from django.apps import apps
model_string = get_video_model_string()
try:
return apps.get_model(model_string)
except ValueError:
raise ImproperlyConfigured("WAGTAILVIDEOS_VIDEO_MODEL must be of the form 'app_label.model_name'")
except LookupError:
raise ImproperlyConfigured(
"WAGTAILVIDEOS_VIDEO_MODEL refers to model '%s' that has not been installed" % model_string
)
\ No newline at end of file
from wagtail.core.blocks import ChooserBlock from wagtail.core.blocks import ChooserBlock
from django.utils.functional import cached_property from django.utils.functional import cached_property
class VideoChooserBlock(ChooserBlock): class VideoChooserBlock(ChooserBlock):
@cached_property @cached_property
def target_model(self): def target_model(self):
from wagtailvideos.models import Video from wagtailvideos import get_video_model
return Video return get_video_model()
@cached_property @cached_property
def widget(self): def widget(self):
......
from jinja2.ext import Extension from jinja2.ext import Extension
from .models import Video from . import get_video_model
Video = get_video_model()
def video(video, **attrs): def video(video, **attrs):
......
...@@ -19,10 +19,9 @@ from django.db.models.signals import post_save, pre_delete ...@@ -19,10 +19,9 @@ from django.db.models.signals import post_save, pre_delete
from django.dispatch.dispatcher import receiver from django.dispatch.dispatcher import receiver
from django.forms.utils import flatatt from django.forms.utils import flatatt
from django.urls import reverse from django.urls import reverse
from django.utils.html import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from enumchoicefield import ChoiceEnum, EnumChoiceField from enumchoicefield import ChoiceEnum, EnumChoiceField
from six import python_2_unicode_compatible
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from wagtail.core.models import CollectionMember from wagtail.core.models import CollectionMember
from wagtail.search import index from wagtail.search import index
...@@ -79,7 +78,6 @@ def get_upload_to(instance, filename): ...@@ -79,7 +78,6 @@ def get_upload_to(instance, filename):
return instance.get_upload_to(filename) return instance.get_upload_to(filename)
@python_2_unicode_compatible
class AbstractVideo(CollectionMember, index.Indexed, models.Model): class AbstractVideo(CollectionMember, index.Indexed, models.Model):
title = models.CharField(max_length=255, verbose_name=_('title')) title = models.CharField(max_length=255, verbose_name=_('title'))
file = models.FileField( file = models.FileField(
......
from wagtail.core.permission_policies.collections import ( from wagtail.core.permission_policies.collections import (
CollectionOwnershipPermissionPolicy) CollectionOwnershipPermissionPolicy)
from wagtailvideos import get_video_model
from wagtailvideos.models import Video from wagtailvideos.models import Video
permission_policy = CollectionOwnershipPermissionPolicy( permission_policy = CollectionOwnershipPermissionPolicy(
Video, get_video_model(),
auth_model=Video, auth_model=Video,
owner_field_name='uploaded_by_user' owner_field_name='uploaded_by_user'
) )
...@@ -11,7 +11,7 @@ from wagtail.images.views.chooser import get_chooser_js_data ...@@ -11,7 +11,7 @@ from wagtail.images.views.chooser import get_chooser_js_data
from wagtail.search import index as search_index from wagtail.search import index as search_index
from wagtailvideos.forms import get_video_form from wagtailvideos.forms import get_video_form
from wagtailvideos.models import Video from wagtailvideos import get_video_model
from wagtailvideos.permissions import permission_policy from wagtailvideos.permissions import permission_policy
if LooseVersion(wagtail.__version__) >= LooseVersion('2.7'): if LooseVersion(wagtail.__version__) >= LooseVersion('2.7'):
...@@ -41,6 +41,7 @@ def get_video_json(video): ...@@ -41,6 +41,7 @@ def get_video_json(video):
def chooser(request): def chooser(request):
Video = get_video_model()
VideoForm = get_video_form(Video) VideoForm = get_video_form(Video)
uploadform = VideoForm() uploadform = VideoForm()
...@@ -101,7 +102,7 @@ def chooser(request): ...@@ -101,7 +102,7 @@ def chooser(request):
def video_chosen(request, video_id): def video_chosen(request, video_id):
video = get_object_or_404(Video, id=video_id) video = get_object_or_404(get_video_model(), id=video_id)
return render_modal_workflow( return render_modal_workflow(
request, None, json_data={ request, None, json_data={
...@@ -112,6 +113,7 @@ def video_chosen(request, video_id): ...@@ -112,6 +113,7 @@ def video_chosen(request, video_id):
@permission_checker.require('add') @permission_checker.require('add')
def chooser_upload(request): def chooser_upload(request):
Video = get_video_model()
VideoForm = get_video_form(Video) VideoForm = get_video_form(Video)
searchform = SearchForm() searchform = SearchForm()
......
...@@ -10,7 +10,7 @@ from django.views.decorators.vary import vary_on_headers ...@@ -10,7 +10,7 @@ from django.views.decorators.vary import vary_on_headers
from wagtail.search.backends import get_search_backends from wagtail.search.backends import get_search_backends
from wagtailvideos.forms import get_video_form from wagtailvideos.forms import get_video_form
from wagtailvideos.models import Video from wagtailvideos import get_video_model
from wagtailvideos.permissions import permission_policy from wagtailvideos.permissions import permission_policy
if LooseVersion(wagtail.__version__) >= LooseVersion('2.7'): if LooseVersion(wagtail.__version__) >= LooseVersion('2.7'):
...@@ -37,6 +37,7 @@ def get_video_edit_form(VideoModel): ...@@ -37,6 +37,7 @@ def get_video_edit_form(VideoModel):
@vary_on_headers('X-Requested-With') @vary_on_headers('X-Requested-With')
def add(request): def add(request):
Video = get_video_model()
VideoForm = get_video_form(Video) VideoForm = get_video_form(Video)
collections = permission_policy.collections_user_has_permission_for(request.user, 'add') collections = permission_policy.collections_user_has_permission_for(request.user, 'add')
...@@ -98,6 +99,7 @@ def add(request): ...@@ -98,6 +99,7 @@ def add(request):
@require_POST @require_POST
def edit(request, video_id, callback=None): def edit(request, video_id, callback=None):
Video = get_video_model()
VideoForm = get_video_edit_form(Video) VideoForm = get_video_edit_form(Video)
video = get_object_or_404(Video, id=video_id) video = get_object_or_404(Video, id=video_id)
...@@ -133,7 +135,7 @@ def edit(request, video_id, callback=None): ...@@ -133,7 +135,7 @@ def edit(request, video_id, callback=None):
@require_POST @require_POST
def delete(request, video_id): def delete(request, video_id):
video = get_object_or_404(Video, id=video_id) video = get_object_or_404(get_video_model(), id=video_id)
if not request.is_ajax(): if not request.is_ajax():
return HttpResponseBadRequest("Cannot POST to this view without AJAX") return HttpResponseBadRequest("Cannot POST to this view without AJAX")
......
...@@ -12,9 +12,8 @@ from wagtail.admin.forms.search import SearchForm ...@@ -12,9 +12,8 @@ from wagtail.admin.forms.search import SearchForm
from wagtail.core.models import Collection from wagtail.core.models import Collection
from wagtail.search.backends import get_search_backends from wagtail.search.backends import get_search_backends
from wagtailvideos import ffmpeg from wagtailvideos import ffmpeg, get_video_model
from wagtailvideos.forms import VideoTranscodeAdminForm, get_video_form from wagtailvideos.forms import VideoTranscodeAdminForm, get_video_form
from wagtailvideos.models import Video
from wagtailvideos.permissions import permission_policy from wagtailvideos.permissions import permission_policy
if LooseVersion(wagtail.__version__) >= LooseVersion('2.7'): if LooseVersion(wagtail.__version__) >= LooseVersion('2.7'):
...@@ -31,6 +30,7 @@ permission_checker = PermissionPolicyChecker(permission_policy) ...@@ -31,6 +30,7 @@ permission_checker = PermissionPolicyChecker(permission_policy)
@vary_on_headers('X-Requested-With') @vary_on_headers('X-Requested-With')
def index(request): def index(request):
# Get Videos (filtered by user permission) # Get Videos (filtered by user permission)
Video = get_video_model()
videos = Video.objects.all() videos = Video.objects.all()
# Search # Search
...@@ -80,6 +80,7 @@ def index(request): ...@@ -80,6 +80,7 @@ def index(request):
@permission_checker.require('change') @permission_checker.require('change')
def edit(request, video_id): def edit(request, video_id):
Video = get_video_model()
VideoForm = get_video_form(Video) VideoForm = get_video_form(Video)
video = get_object_or_404(Video, id=video_id) video = get_object_or_404(Video, id=video_id)
...@@ -134,7 +135,7 @@ def edit(request, video_id): ...@@ -134,7 +135,7 @@ def edit(request, video_id):
def create_transcode(request, video_id): def create_transcode(request, video_id):
if request.method != 'POST': if request.method != 'POST':
return HttpResponseNotAllowed(['POST']) return HttpResponseNotAllowed(['POST'])
video = get_object_or_404(Video, id=video_id) video = get_object_or_404(get_video_model(), id=video_id)
transcode_form = VideoTranscodeAdminForm(data=request.POST, video=video) transcode_form = VideoTranscodeAdminForm(data=request.POST, video=video)
if transcode_form.is_valid(): if transcode_form.is_valid():
...@@ -144,7 +145,7 @@ def create_transcode(request, video_id): ...@@ -144,7 +145,7 @@ def create_transcode(request, video_id):
@permission_checker.require('delete') @permission_checker.require('delete')
def delete(request, video_id): def delete(request, video_id):
video = get_object_or_404(Video, id=video_id) video = get_object_or_404(get_video_model(), id=video_id)
if request.POST: if request.POST:
video.delete() video.delete()
...@@ -158,6 +159,7 @@ def delete(request, video_id): ...@@ -158,6 +159,7 @@ def delete(request, video_id):
@permission_checker.require('add') @permission_checker.require('add')
def add(request): def add(request):
Video = get_video_model()
VideoForm = get_video_form(Video) VideoForm = get_video_form(Video)
if request.POST: if request.POST:
...@@ -188,7 +190,7 @@ def add(request): ...@@ -188,7 +190,7 @@ def add(request):
def usage(request, image_id): def usage(request, image_id):
image = get_object_or_404(Video, id=image_id) image = get_object_or_404(get_video_model(), id=image_id)
paginator = Paginator(image.get_usage(), per_page=12) paginator = Paginator(image.get_usage(), per_page=12)
page = paginator.get_page(request.GET.get('p')) page = paginator.get_page(request.GET.get('p'))
......
...@@ -10,10 +10,12 @@ from django.utils.html import format_html ...@@ -10,10 +10,12 @@ from django.utils.html import format_html
from django.templatetags.static import static from django.templatetags.static import static
from wagtailvideos import urls from wagtailvideos import urls
from wagtailvideos.forms import GroupVideoPermissionFormSet from wagtailvideos.forms import GroupVideoPermissionFormSet
from wagtailvideos.models import Video from wagtailvideos import get_video_model
from .permissions import permission_policy from .permissions import permission_policy
Video = get_video_model()
@hooks.register('register_admin_urls') @hooks.register('register_admin_urls')
def register_admin_urls(): def register_admin_urls():
......
...@@ -4,7 +4,7 @@ from django.template.loader import render_to_string ...@@ -4,7 +4,7 @@ from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from wagtail.admin.widgets import AdminChooser from wagtail.admin.widgets import AdminChooser
from wagtailvideos.models import Video from wagtailvideos import get_video_model
class AdminVideoChooser(AdminChooser): class AdminVideoChooser(AdminChooser):
...@@ -14,7 +14,7 @@ class AdminVideoChooser(AdminChooser): ...@@ -14,7 +14,7 @@ class AdminVideoChooser(AdminChooser):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(AdminVideoChooser, self).__init__(**kwargs) super(AdminVideoChooser, self).__init__(**kwargs)
self.video_model = Video self.video_model = get_video_model()
def render_html(self, name, value, attrs): def render_html(self, name, value, attrs):
instance, value = self.get_instance_and_id(self.video_model, value) instance, value = self.get_instance_and_id(self.video_model, value)
......
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