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:
image: python:3.8
extends: .python_test
before_script:
- pip install .['testing'] wagtail~=2.11 django~=3.1
\ No newline at end of file
- pip install .['testing'] wagtail~=2.11 django~=3.1
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.
from wagtailvideos.edit_handlers import VideoChooserPanel
class HomePage(Page):
body = RichtextField()
header_video = models.ForeignKey('wagtailvideos.Video',
......@@ -68,6 +69,7 @@ A VideoChooserBlock is included
from wagtailvideos.blocks import VideoChooserBlock
class ContentPage(Page):
body = StreamField([
('video', VideoChooserBlock()),
......@@ -90,6 +92,8 @@ tag. The original video and all extra transcodes are added as
{% load wagtailvideos_tags %}
{% video self.header_video autoplay controls width=256 %}
Jinja2 extensions are also included.
How to transcode using ffmpeg:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
......@@ -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
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
---------------
- Some docs
- Richtext embed
- 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
from wagtailvideos.edit_handlers import VideoChooserPanel
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):
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'
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 django.utils.functional import cached_property
class VideoChooserBlock(ChooserBlock):
@cached_property
def target_model(self):
from wagtailvideos.models import Video
return Video
from wagtailvideos import get_video_model
return get_video_model()
@cached_property
def widget(self):
......
from jinja2.ext import Extension
from .models import Video
from . import get_video_model
Video = get_video_model()
def video(video, **attrs):
......
......@@ -19,10 +19,9 @@ from django.db.models.signals import post_save, pre_delete
from django.dispatch.dispatcher import receiver
from django.forms.utils import flatatt
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 enumchoicefield import ChoiceEnum, EnumChoiceField
from six import python_2_unicode_compatible
from taggit.managers import TaggableManager
from wagtail.core.models import CollectionMember
from wagtail.search import index
......@@ -79,7 +78,6 @@ def get_upload_to(instance, filename):
return instance.get_upload_to(filename)
@python_2_unicode_compatible
class AbstractVideo(CollectionMember, index.Indexed, models.Model):
title = models.CharField(max_length=255, verbose_name=_('title'))
file = models.FileField(
......
from wagtail.core.permission_policies.collections import (
CollectionOwnershipPermissionPolicy)
from wagtailvideos import get_video_model
from wagtailvideos.models import Video
permission_policy = CollectionOwnershipPermissionPolicy(
Video,
get_video_model(),
auth_model=Video,
owner_field_name='uploaded_by_user'
)
......@@ -11,7 +11,7 @@ from wagtail.images.views.chooser import get_chooser_js_data
from wagtail.search import index as search_index
from wagtailvideos.forms import get_video_form
from wagtailvideos.models import Video
from wagtailvideos import get_video_model
from wagtailvideos.permissions import permission_policy
if LooseVersion(wagtail.__version__) >= LooseVersion('2.7'):
......@@ -41,6 +41,7 @@ def get_video_json(video):
def chooser(request):
Video = get_video_model()
VideoForm = get_video_form(Video)
uploadform = VideoForm()
......@@ -101,7 +102,7 @@ def chooser(request):
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(
request, None, json_data={
......@@ -112,6 +113,7 @@ def video_chosen(request, video_id):
@permission_checker.require('add')
def chooser_upload(request):
Video = get_video_model()
VideoForm = get_video_form(Video)
searchform = SearchForm()
......
......@@ -10,7 +10,7 @@ from django.views.decorators.vary import vary_on_headers
from wagtail.search.backends import get_search_backends
from wagtailvideos.forms import get_video_form
from wagtailvideos.models import Video
from wagtailvideos import get_video_model
from wagtailvideos.permissions import permission_policy
if LooseVersion(wagtail.__version__) >= LooseVersion('2.7'):
......@@ -37,6 +37,7 @@ def get_video_edit_form(VideoModel):
@vary_on_headers('X-Requested-With')
def add(request):
Video = get_video_model()
VideoForm = get_video_form(Video)
collections = permission_policy.collections_user_has_permission_for(request.user, 'add')
......@@ -98,6 +99,7 @@ def add(request):
@require_POST
def edit(request, video_id, callback=None):
Video = get_video_model()
VideoForm = get_video_edit_form(Video)
video = get_object_or_404(Video, id=video_id)
......@@ -133,7 +135,7 @@ def edit(request, video_id, callback=None):
@require_POST
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():
return HttpResponseBadRequest("Cannot POST to this view without AJAX")
......
......@@ -12,9 +12,8 @@ from wagtail.admin.forms.search import SearchForm
from wagtail.core.models import Collection
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.models import Video
from wagtailvideos.permissions import permission_policy
if LooseVersion(wagtail.__version__) >= LooseVersion('2.7'):
......@@ -31,6 +30,7 @@ permission_checker = PermissionPolicyChecker(permission_policy)
@vary_on_headers('X-Requested-With')
def index(request):
# Get Videos (filtered by user permission)
Video = get_video_model()
videos = Video.objects.all()
# Search
......@@ -80,6 +80,7 @@ def index(request):
@permission_checker.require('change')
def edit(request, video_id):
Video = get_video_model()
VideoForm = get_video_form(Video)
video = get_object_or_404(Video, id=video_id)
......@@ -134,7 +135,7 @@ def edit(request, video_id):
def create_transcode(request, video_id):
if request.method != '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)
if transcode_form.is_valid():
......@@ -144,7 +145,7 @@ def create_transcode(request, video_id):
@permission_checker.require('delete')
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:
video.delete()
......@@ -158,6 +159,7 @@ def delete(request, video_id):
@permission_checker.require('add')
def add(request):
Video = get_video_model()
VideoForm = get_video_form(Video)
if request.POST:
......@@ -188,7 +190,7 @@ def add(request):
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)
page = paginator.get_page(request.GET.get('p'))
......
......@@ -10,10 +10,12 @@ from django.utils.html import format_html
from django.templatetags.static import static
from wagtailvideos import urls
from wagtailvideos.forms import GroupVideoPermissionFormSet
from wagtailvideos.models import Video
from wagtailvideos import get_video_model
from .permissions import permission_policy
Video = get_video_model()
@hooks.register('register_admin_urls')
def register_admin_urls():
......
......@@ -4,7 +4,7 @@ from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _
from wagtail.admin.widgets import AdminChooser
from wagtailvideos.models import Video
from wagtailvideos import get_video_model
class AdminVideoChooser(AdminChooser):
......@@ -14,7 +14,7 @@ class AdminVideoChooser(AdminChooser):
def __init__(self, **kwargs):
super(AdminVideoChooser, self).__init__(**kwargs)
self.video_model = Video
self.video_model = get_video_model()
def render_html(self, name, value, attrs):
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