Unverified Commit ccd007ee authored by Seb Brown's avatar Seb Brown Committed by GitHub
Browse files

Merge pull request #97 from neon-jungle/feat_support_wagtail4

Feat support wagtail4
parents d66ec4eb c6198e49
......@@ -8,3 +8,4 @@ venv/
tests.sqlite3
.tox
build/
.prettierignore
\ No newline at end of file
......@@ -12,21 +12,22 @@ stages:
head:
extends: .python_test
image: python:3.8
image: python:3.10
before_script:
- pip install .['testing']
lts_211:
image: python:3.8
extends: .python_test
before_script:
- pip install .['testing'] wagtail~=2.11 django~=3.1
# TODO add 4.0+ LTS once released
# lts_211:
# image: python:3.8
# extends: .python_test
# before_script:
# - pip install .['testing'] wagtail~=2.11 django~=3.1
lts_215:
image: python:3.10
extends: .python_test
before_script:
- pip install .['testing'] wagtail~=2.15 django~=4.0
# lts_215:
# image: python:3.10
# extends: .python_test
# before_script:
# - pip install .['testing'] wagtail~=2.15 django~=4.0
flake8:
stage: lint
......@@ -38,14 +39,14 @@ flake8:
isort:
stage: lint
image: python:3.8
image: python:3.10
before_script:
- pip install isort -e .
script:
- isort --recursive --diff --check-only wagtailvideos/ tests/
build:
image: python:3.8
image: python:3.10
stage: release
before_script:
- pip install --upgrade setuptools wheel twine
......
......@@ -18,8 +18,8 @@ setup(
url='https://github.com/neon-jungle/wagtailvideos',
install_requires=[
'wagtail>=2.5',
'Django>=2.0',
'wagtail>=4.0',
'Django>=3.2',
'django-enumchoicefield>=1.1.0',
'bcp47==0.0.4',
],
......
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.admin.edit_handlers import StreamFieldPanel
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.core.fields import StreamField
from wagtail.core.models import Page
......@@ -51,5 +51,5 @@ class TestPage(Page):
content_panels = Page.content_panels + [
VideoChooserPanel('video_field'),
StreamFieldPanel('video_streamfield'),
FieldPanel('video_streamfield'),
]
......@@ -38,6 +38,7 @@ DATABASES = {
}
WAGTAIL_SITE_NAME = 'Wagtail Videos'
WAGTAILADMIN_BASE_URL = 'http://localhost:8080'
DEBUG = True
......
......@@ -62,7 +62,7 @@ class TestVideoAddView(TestCase, WagtailTestUtils):
# as standard, only the root collection exists and so no 'Collection' option
# is displayed on the form
self.assertNotContains(response, '<label for="id_collection">')
self.assertNotContains(response, '<label class="w-field__label" for="id_collection" id="id_collection-label">')
# Ensure the form supports file uploads
self.assertContains(response, 'enctype="multipart/form-data"')
......@@ -76,7 +76,7 @@ class TestVideoAddView(TestCase, WagtailTestUtils):
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailvideos/videos/add.html')
self.assertContains(response, '<label for="id_collection">')
self.assertContains(response, '<label class="w-field__label" for="id_collection" id="id_collection-label">')
self.assertContains(response, collection_name)
def test_add(self):
......@@ -302,18 +302,18 @@ class TestVideoChooserView(TestCase, WagtailTestUtils):
self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailvideos:chooser'), params)
return self.client.get(reverse('wagtailvideos_chooser:choose'), params)
def test_simple(self):
response = self.get()
self.assertEqual(response.status_code, 200)
response_json = json.loads(response.content.decode())
self.assertEqual(response_json['step'], 'chooser')
self.assertEqual(response_json['step'], 'choose')
def test_search(self):
response = self.get({'q': "Hello"})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['query_string'], "Hello")
self.assertEqual(response.context['search_query'], "Hello")
def test_pagination(self):
pages = ['0', '1', '-1', '9999', 'Not a page']
......@@ -351,13 +351,13 @@ class TestVideoChooserChosenView(TestCase, WagtailTestUtils):
)
def get(self, params={}):
return self.client.get(reverse('wagtailvideos:video_chosen', args=(self.video.id,)), params)
return self.client.get(reverse('wagtailvideos_chooser:chosen', args=(self.video.id,)), params)
def test_simple(self):
response = self.get()
self.assertEqual(response.status_code, 200)
response_json = json.loads(response.content.decode())
self.assertEqual(response_json['step'], 'video_chosen')
self.assertEqual(response_json['step'], 'chosen')
class TestVideoChooserUploadView(TestCase, WagtailTestUtils):
......@@ -365,17 +365,19 @@ class TestVideoChooserUploadView(TestCase, WagtailTestUtils):
self.login()
def get(self, params={}):
return self.client.get(reverse('wagtailvideos:chooser_upload'), params)
return self.client.get(reverse('wagtailvideos_chooser:create'), params)
def test_simple(self):
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailvideos/chooser/chooser.html')
self.assertTemplateUsed(
response, "wagtailadmin/generic/chooser/creation_form.html"
)
response_json = json.loads(response.content.decode())
self.assertEqual(response_json['step'], 'chooser')
self.assertEqual(response_json["step"], "reshow_creation_form")
def test_upload(self):
response = self.client.post(reverse('wagtailvideos:chooser_upload'), {
response = self.client.post(reverse('wagtailvideos_chooser:create'), {
'title': "Test video",
'file': SimpleUploadedFile('small.mp4', create_test_video_file().read(), "video/mp4"),
})
......@@ -388,34 +390,16 @@ class TestVideoChooserUploadView(TestCase, WagtailTestUtils):
self.assertEqual(videos.count(), 1)
def test_upload_no_file_selected(self):
response = self.client.post(reverse('wagtailvideos:chooser_upload'), {
response = self.client.post(reverse('wagtailvideos_chooser:create'), {
'title': "Test video",
})
# Shouldn't redirect anywhere
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailvideos/chooser/chooser.html')
self.assertTemplateUsed(response, 'wagtailadmin/generic/chooser/creation_form.html')
# The form should have an error
self.assertFormError(response, 'uploadform', 'file', "This field is required.")
def test_pagination_after_upload_form_error(self):
for i in range(0, 20):
Video.objects.create(
title="Test video %d" % i,
file=create_test_video_file(),
)
response = self.client.post(reverse('wagtailvideos:chooser_upload'), {
'title': "Test video",
})
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailvideos/chooser/chooser.html')
# The re-rendered video chooser listing should be paginated
self.assertContains(response, "Page 1 of ")
self.assertEqual(12, len(response.context['videos']))
self.assertFormError(response, 'form', 'file', "This field is required.")
class TestVideoChooserUploadViewWithLimitedPermissions(TestCase, WagtailTestUtils):
......@@ -448,22 +432,30 @@ class TestVideoChooserUploadViewWithLimitedPermissions(TestCase, WagtailTestUtil
self.client.login(username='moriarty', password='password')
def test_get(self):
response = self.client.get(reverse('wagtailvideos:chooser_upload'))
response = self.client.get(reverse('wagtailvideos_chooser:create'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailvideos/chooser/chooser.html')
self.assertTemplateUsed(
response, "wagtailadmin/generic/chooser/creation_form.html"
)
# user only has access to one collection, so no 'Collection' option
# is displayed on the form
self.assertNotContains(response, '<label for="id_collection">')
self.assertNotContains(
response,
'<label class="w-field__label" for="id_collection" id="id_collection-label">',
)
def test_get_chooser(self):
response = self.client.get(reverse('wagtailvideos:chooser'))
response = self.client.get(reverse('wagtailvideos_chooser:choose'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailvideos/chooser/chooser.html')
# user only has access to one collection, so no 'Collection' option
# is displayed on the form
self.assertNotContains(response, '<label for="id_collection">')
self.assertNotContains(
response,
'<label class="w-field__label" for="id_collection" id="id_collection-label">',
)
class TestMultipleVideoUploader(TestCase, WagtailTestUtils):
......
......@@ -8,8 +8,7 @@ from wagtailvideos import ffmpeg
def ffmpeg_check(app_configs, **kwargs):
messages = []
if (
not ffmpeg.installed()
and not getattr(settings, 'WAGTAIL_VIDEOS_DISABLE_TRANSCODE', False)
not ffmpeg.installed() and not getattr(settings, 'WAGTAIL_VIDEOS_DISABLE_TRANSCODE', False)
):
messages.append(
Warning(
......
......@@ -11,7 +11,7 @@ class VideoChooserBlock(ChooserBlock):
@cached_property
def widget(self):
from wagtailvideos.widgets import AdminVideoChooser
return AdminVideoChooser
return AdminVideoChooser()
def render_basic(self, value, context=None):
if value:
......
from wagtail.admin.edit_handlers import BaseChooserPanel
from wagtail.admin.panels import FieldPanel
from .widgets import AdminVideoChooser
class VideoChooserPanel(BaseChooserPanel):
model = None
field_name = None
_target_model = None
class VideoChooserPanel(FieldPanel):
object_type_name = "video"
def widget_overrides(self):
return {self.field_name: AdminVideoChooser}
def __init__(self, field_name, disable_comments=None, permission=None, **kwargs):
kwargs['widget'] = AdminVideoChooser
super().__init__(field_name, disable_comments=disable_comments, permission=permission, **kwargs)
$(function() {
// Redirect users that don't support filereader
if (!$('html').hasClass('filereader')) {
document.location.href = window.fileupload_opts.simple_upload_url;
return false;
}
// prevents browser default drag/drop
$(document).bind('drop dragover', function(e) {
e.preventDefault();
......
var VIDEO_CHOOSER_MODAL_ONLOAD_HANDLERS = {
chooser: function(modal, jsonData) {
var searchUrl = $('form.video-search', modal.body).attr('action');
/* currentTag stores the tag currently being filtered on, so that we can
preserve this when paginating */
var currentTag;
function ajaxifyLinks(context) {
$('.listing a', context).click(function() {
modal.loadUrl(this.href);
return false;
});
$('.pagination a', context).click(function() {
var page = this.getAttribute('data-page');
setPage(page);
return false;
});
}
function fetchResults(requestData) {
$.ajax({
url: searchUrl,
data: requestData,
success: function(data, status) {
$('#image-results').html(data);
ajaxifyLinks($('#image-results'));
}
});
}
function search() {
/* Searching causes currentTag to be cleared - otherwise there's
no way to de-select a tag */
currentTag = null;
fetchResults({
q: $('#id_q').val(),
collection_id: $('#collection_chooser_collection_id').val()
});
return false;
}
function setPage(page) {
var params = { p: page };
if ($('#id_q').val().length) {
params['q'] = $('#id_q').val();
}
if (currentTag) {
params['tag'] = currentTag;
}
params['collection_id'] = $('#collection_chooser_collection_id').val();
fetchResults(params);
return false;
}
ajaxifyLinks(modal.body);
$('form.video-upload', modal.body).submit(function() {
var formdata = new FormData(this);
$.ajax({
url: this.action,
data: formdata,
processData: false,
contentType: false,
type: 'POST',
dataType: 'text',
success: function(response) {
modal.loadResponseText(response);
},
error: function(response, textStatus, errorThrown) {
var message = jsonData['error_message'] + '<br />' + errorThrown + ' - ' + response.status;
$('#upload').append(
'<div class="help-block help-critical">' + '<strong>' + jsonData['error_label'] + ': </strong>' + message + '</div>'
);
}
});
return false;
});
$('form.video-search', modal.body).submit(search);
$('#id_q').on('input', function() {
clearTimeout($.data(this, 'timer'));
var wait = setTimeout(search, 200);
$(this).data('timer', wait);
});
$('#collection_chooser_collection_id').change(search);
$('a.suggested-tag').click(function() {
currentTag = $(this).text();
$('#id_q').val('');
fetchResults({
tag: currentTag,
collection_id: $('#collection_chooser_collection_id').val()
});
return false;
});
/* Add tag entry interface (with autocompletion) to the tag field of the image upload form */
// $('#id_tags', modal.body).tagit({
// autocomplete: {source: "{{ autocomplete_url|addslashes }}"}
// });
},
video_chosen: function(modal, jsonData) {
modal.respond('videoChosen', jsonData['result']);
modal.close();
}
};
const ImageChooserFactory = window.telepath.constructors['wagtail.images.widgets.ImageChooser'];
class VideoChooserFactory extends ImageChooserFactory {
// eslint-disable-next-line no-undef
widgetClass = VideoChooser;
}
window.telepath.register('wagtailvideos.widgets.VideoChooser', VideoChooserFactory);
function createVideoChooser(id) {
var chooserElement = $('#' + id + '-chooser');
var previewVideo = chooserElement.find('.preview-image img');
var input = $('#' + id);
var editLink = chooserElement.find('.edit-link');
document.addEventListener('DOMContentLoaded', event => {
$.ajax(window.chooserUrls.videoChooser + input.val()).done(data => {
let videoData = data.result
input.val(videoData.id);
previewVideo.attr({
src: videoData.preview.url,
alt: videoData.title
});
chooserElement.removeClass('blank');
editLink.attr('href', videoData.edit_link);
});
});
$('.action-choose', chooserElement).click(function() {
ModalWorkflow({
url: window.chooserUrls.videoChooser,
onload: VIDEO_CHOOSER_MODAL_ONLOAD_HANDLERS,
responses: {
videoChosen: function(videoData) {
input.val(videoData.id);
previewVideo.attr({
src: videoData.preview.url,
alt: videoData.title
});
chooserElement.removeClass('blank');
editLink.attr('href', videoData.edit_link);
}
}
});
});
$('.action-clear', chooserElement).click(function() {
input.val('');
chooserElement.addClass('blank');
});
class VideoChooser extends window.ImageChooser {
}
window.VideoChooser = VideoChooser;
{% load i18n %}
{% trans "Choose a video" as choose_str %}
{% include "wagtailadmin/shared/header.html" with title=choose_str merged=1 tabbed=1 icon="media" %}
{% extends "wagtailadmin/generic/chooser/chooser.html" %}
{% if uploadform %}
<ul class="tab-nav merged" data-tab-nav>
<li class="{% if not uploadform.errors %}active{% endif %}"><a href="#search" >{% trans "Search" %}</a></li>
<li class="{% if uploadform.errors %}active{% endif %}"><a href="#upload">{% trans "Upload" %}</a></li>
</ul>
{% endif %}
{% load i18n wagtailadmin_tags %}
<div class="tab-content">
<section id="search" class="{% if not uploadform.errors %}active{% endif %} nice-padding">
<form class="video-search search-bar" action="{% url 'wagtailvideos:chooser' %}" method="GET" autocomplete="off">
<ul class="fields">
{% for field in searchform %}
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
{% endfor %}
{% if collections %}
{% include "wagtailadmin/shared/collection_chooser.html" %}
{% endif %}
{% if popular_tags %}
<li class="taglist">
<h3>{% trans 'Popular tags' %}</h3>
{% for tag in popular_tags %}
<a class="suggested-tag tag" href="{% url 'wagtailvideos:index' %}?tag={{ tag.name|urlencode }}">{{ tag.name }}</a>
{% endfor %}
</li>
{% endif %}
</ul>
</form>
<div id="image-results">
{% include "wagtailvideos/chooser/results.html" %}
</div>
</section>
{% if uploadform %}
<section id="upload" class="{% if uploadform.errors %}active{% endif %} nice-padding">
<form class="video-upload" action="{% url 'wagtailvideos:chooser_upload' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<ul class="fields">
{% for field in uploadform %}
{% if field.is_hidden %}
{{ field }}
{% else %}
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
{% endif %}
{% block filter_form %}
<form
data-chooser-modal-search
class="search-bar"
action="{{ results_url }}"
method="GET"
autocomplete="off"
novalidate
>
<ul class="fields">
{% for field in filter_form %}
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
{% endfor %}
{% if popular_tags %}
<li class="taglist w-label-3">
<h3>{% trans 'Popular tags' %}</h3>
{% for tag in popular_tags %}
<a class="suggested-tag tag" href="{% url 'wagtailvideos:index' %}?tag={{ tag.name|urlencode }}">{{ tag.name }}</a>
{% endfor %}
<li>
<button type="submit" class="button button-longrunning" data-clicked-text="{% trans 'Uploading...' %}"><span class="icon icon-spinner"></span><em>{% trans 'Upload' %}</em></button>
</li>
</ul>
</form>
</section>
{% endif %}
</div>
</li>
{% endif %}
</ul>
</form>
{% endblock %}
{% load wagtailadmin_tags %}
{% load i18n %}
{% if videos %}
{% if is_searching %}
<h2>
{% blocktrans count counter=videos.paginator.count %}
There is one match
{% plural %}
There are {{ counter }} matches
{% endblocktrans %}
</h2>
{% else %}
<h2>{% trans "Latest videos" %}</h2>
{% endif %}
{% extends "wagtailadmin/generic/chooser/results.html" %}
{% load i18n wagtailadmin_tags %}
{% block listing_title %}<h2>{% trans "Latest videos" %}</h2>{% endblock %}
{% block results_listing %}
<ul class="listing horiz images chooser">
{% for video in videos %}
{% for video in results %}
<li>
<a class="image-choice" href="{% if will_select_format %}{% url 'wagtailvideos:chooser_select_format' video.id %}{% else %}{% url 'wagtailvideos:video_chosen' video.id %}{% endif %}">
<div class="image">
<a
data-chooser-modal-choice
class="image-choice"
title="{{ video.title }}"
href="{% url 'wagtailvideos_chooser:chosen' video.id %}"
>
<figure>
{% if video.thumbnail %}
<img src="{{ video.thumbnail.url }}" width="165" height="165" class="show-transparency" alt="{% trans 'Video thumbnail' %}"/>
<img
src="{{ video.thumbnail.url }}"
width="165"
height="165"
class="show-transparency"
alt="{% trans 'Video thumbnail' %}"
/>
{% else %}
<img width="165" height="165" class="show-transparency" alt=""/>
<img
width="165"
height="165"
class="show-transparency"
alt=""
/>
{% endif %}
</div>
<h3>{{ video.title|ellipsistrim:60 }}</h3>
<figcaption>{{ video.title|ellipsistrim:60 }}</figcaption>
</figure>
</a>
</li>
{% endfor %}
</ul>
{% include "wagtailadmin/shared/ajax_pagination_nav.html" with items=videos %}
{% endif %}
{% endblock %}
{% block no_items_message %}
<p>
{% if is_filtering_by_collection %}
{% trans "You haven't uploaded any videos in this collection." %}
{% else %}
{% trans "You haven't uploaded any videos." %}
{% endif %}
{% if can_create %}
{% blocktrans trimmed %}
Why not <a class="upload-one-now" href="#tab-upload" data-tab-trigger>upload one now</a>?
{% endblocktrans %}
{% endif %}
</p>
{% endblock %}
{% load i18n wagtailadmin_tags %}
<li class="icon icon-media">
<li>
{% icon name="media" %}
<a href="{% url 'wagtailvideos:index' %}">
{% blocktrans count counter=total_videos with total_videos|intcomma as total %}
<span>{{ total }}</span> Video <span class="visuallyhidden">created in {{ site_name }}</span>
{% plural %}
<span>{{ total }}</span> Videos <span class="visuallyhidden">created in {{ site_name }}</span>
{% endblocktrans %}
{% blocktrans count counter=total_videos with total_videos|intcomma as total %}
<span>{{ total }}</span> Video <span class="visuallyhidden">created in {{ site_name }}</span>
{% plural %}
<span>{{ total }}</span> Videos <span class="visuallyhidden">created in {{ site_name }}</span>
{% endblocktrans %}
</a>
</li>
{% extends "wagtailadmin/base.html" %}
{% load i18n static %}
{% load i18n static wagtailadmin_tags %}
{% block titletag %}{% trans "Add multiple videos" %}{% endblock %}
{% block extra_css %}
{{ block.super }}
<!-- it's cheating but we're just going to use wagtailimages css since they should look identical anyways -->
<link rel="stylesheet" href="{% static 'wagtailimages/css/add-multiple.css' %}" type="text/css" />
{% endblock %}
{% block content %}
......@@ -67,10 +66,14 @@
{{ block.super }}
<!-- this exact order of plugins is vital -->
<script src="{% static 'wagtailadmin/js/vendor/jquery.iframe-transport.js' %}"></script>
<script src="{% static 'wagtailadmin/js/vendor/jquery.fileupload.js' %}"></script>
<script src="{% static 'wagtailadmin/js/vendor/jquery.fileupload-process.js' %}"></script>
<script src="{% static 'wagtailadmin/js/vendor/tag-it.js' %}"></script>
<script src="{% versioned_static 'wagtailimages/js/vendor/load-image.min.js' %}"></script>
<script src="{% versioned_static 'wagtailimages/js/vendor/canvas-to-blob.min.js' %}"></script>
<script src="{% versioned_static 'wagtailadmin/js/vendor/jquery.iframe-transport.js' %}"></script>
<script src="{% versioned_static 'wagtailadmin/js/vendor/jquery.fileupload.js' %}"></script>
<script src="{% versioned_static 'wagtailadmin/js/vendor/jquery.fileupload-process.js' %}"></script>
<script src="{% versioned_static 'wagtailimages/js/vendor/jquery.fileupload-image.js' %}"></script>
<script src="{% versioned_static 'wagtailimages/js/vendor/jquery.fileupload-validate.js' %}"></script>
<script src="{% versioned_static 'wagtailadmin/js/vendor/tag-it.js' %}"></script>
<!-- Main script -->
<script src="{% static 'wagtailvideos/js/add-multiple.js' %}"></script>
......
......@@ -8,7 +8,7 @@
window.headerSearch = {
url: "{% url 'wagtailvideos:index' %}",
termInput: "#id_q",
targetOutput: "#video-results"
targetOutput: "#image-results"
}
$(function() {
......
......@@ -19,12 +19,14 @@
{% for video in videos %}
<li>
<a class="image-choice" href="{% url 'wagtailvideos:edit' video.id %}">
<div class="image">
<figure>
{% if video.thumbnail %}
<img src="{{ video.thumbnail.url }}" height="165" width="165" class="show-transparency" alt="{% trans 'Video thumbnail' %}"/>
<img src="{{ video.thumbnail.url }}" height="165" width="165" class="show-transparency" alt="{% trans 'Video thumbnail' %}"/>
{% else %}
<img width="165" height="165" class="show-transparency" alt="" />
{% endif %}
</div>
<h3>{{ video.title|ellipsistrim:60 }}</h3>
<figcaption>{{ video.title|ellipsistrim:60 }}</figcaption>
</figure>
</a>
</li>
{% endfor %}
......
{% extends "wagtailadmin/widgets/chooser.html" %}
{% load i18n %}
{% block chooser_class %}image-chooser{% endblock %}
{% block chosen_state_view %}
<div class="preview-image">
{% if video and video.thumbnail %}
<img src="{{ video.thumbnail.url }}" width="165" height="165" class="show-transparency" alt="{% trans 'Video thumbnail' %}"/>
{% else %}
<img width="165" height="165" class="show-transparency" alt=""/>
{% endif %}
</div>
{% endblock %}
{% block edit_chosen_item_url %}{% if video %}{% url 'wagtailvideos:edit' video.id %}{% endif %}{% endblock %}
{% block chosen_icon %}
{# Empty alt because the chosen item’s title is already displayed next to the image. #}
<img
class="chooser__image"
data-chooser-image
alt=""
class="show-transparency"
height="{{ preview.height }}"
width="{{ preview.width }}"
src="{{ preview.url }}"
/>
{% endblock chosen_icon %}
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