episode-form: add tracks inline formset

This commit is contained in:
Chris Tactic 2024-02-05 09:49:17 +01:00
parent ab1b152a46
commit 7e0e6e9652
8 changed files with 183 additions and 5 deletions

View File

@ -55,3 +55,4 @@ class EpisodeForm(ModelForm):
sound_file.sync(
program=self.instance.program, episode=self.instance, type=0, is_public=True, is_downloadable=True
)
super().save(commit=commit)

View File

@ -0,0 +1,29 @@
/* global CKEDITOR, django */
/* Modified in order to be manually loaded after vue.js */
function initialiseCKEditor() {
var textareas = Array.prototype.slice.call(
document.querySelectorAll("textarea[data-type=ckeditortype]"),
)
for (var i = 0; i < textareas.length; ++i) {
var t = textareas[i]
if (
t.getAttribute("data-processed") == "0" &&
t.id.indexOf("__prefix__") == -1
) {
t.setAttribute("data-processed", "1")
var ext = JSON.parse(t.getAttribute("data-external-plugin-resources"))
for (var j = 0; j < ext.length; ++j) {
CKEDITOR.plugins.addExternal(ext[j][0], ext[j][1], ext[j][2])
}
CKEDITOR.replace(t.id, JSON.parse(t.getAttribute("data-config")))
}
}
}
function initialiseCKEditorInInlinedForms() {
if (typeof django === "object" && django.jQuery) {
django.jQuery(document).on("formset:added", initialiseCKEditor)
}
}
//})()

View File

@ -1,12 +1,17 @@
{% extends "aircox/basepage_detail.html" %}
{% load static i18n humanize honeypot aircox %}
{% block head_extra %}
{{ form.media }}
<script type="text/javascript" src="{% static "aircox/js/admin.js" %}"></script>
<script type="text/javascript" src="{% static "aircox/js/ckeditor-init.js" %}"></script>
<!-- <script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script> -->
<script type="text/javascript" src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>
{% endblock %}
{% block init-scripts %}
aircox.init(null, {hotReload:false, initPlayer:false, initApp:true})
initialiseCKEditor()
initialiseCKEditorInInlinedForms()
{% endblock %}
{% block comments %}
@ -21,6 +26,11 @@
</table>
<br/>
<input type="submit" value="Update" class="button is-success">
{% include "aircox/playlist_inline.html" %}
<input type="submit" value="Update" class="button is-success">
</form>
</section>
{% endblock %}

View File

@ -0,0 +1,70 @@
{% comment %}Inline block to edit playlists{% endcomment %}
{% load aircox aircox_admin static i18n %}
<div id="inline-tracks" class="box mb-5">
{{ formset.non_form_errors }}
<!-- formset.management_form -->
<a-playlist-editor
:labels="{% track_inline_labels %}"
:init-data="{% track_inline_data formset=formset %}"
settings-url="{% url "api:user-settings" %}"
data-prefix="{{ formset.prefix }}-">
<template #title>
<h5 class="title is-4">{% trans "Playlist" %}</h5>
</template>
<template #top="{items}">
<input type="hidden" name="{{ formset.prefix }}-TOTAL_FORMS"
:value="items.length || 0"/>
<input type="hidden" name="{{ formset.prefix }}-INITIAL_FORMS"
value="{{ formset.initial_form_count }}"/>
<input type="hidden" name="{{ formset.prefix }}-MIN_NUM_FORMS"
value="{{ formset.min_num }}"/>
<input type="hidden" name="{{ formset.prefix }}-MAX_NUM_FORMS"
value="{{ formset.max_num }}"/>
</template>
<template #rows-header-head>
<th style="max-width:2em" title="{% trans "Track Position" %}"
aria-description="{% trans "Track Position" %}">
<span class="icon">
<i class="fa fa-arrow-down-1-9"></i>
</span>
</th>
</template>
<template v-slot:row-head="{item,row}">
<td>
[[ row+1 ]]
<input type="hidden"
:name="'{{ formset.prefix }}-' + row + '-position'"
:value="row"/>
<input t-if="item.data.id" type="hidden"
:name="'{{ formset.prefix }}-' + row + '-id'"
:value="item.data.id || item.id"/>
{% for field in fields %}
{% if field != 'position' %}
<input type="hidden"
:name="'{{ formset.prefix }}-' + row + '-{{ field.name }}'"
v-model="item.data[attr]"/>
{% endif %}
{% endfor %}
</td>
</template>
{% for field in fields %}
<template v-slot:row-{{ field }}="{item,cell,value,attr,emit}">
<div class="field">
<a-autocomplete
:input-class="['input', item.error(attr) ? 'is-danger' : 'half-field']"
url="{% url 'api:track-autocomplete' %}?{{ field }}=${query}&field={{ field }}"
:name="'{{ formset.prefix }}-' + cell.row + '-{{ field }}'"
v-model="item.data[attr]"
title="{{ field }}"
@change="emit('change', col)"/>
<p v-for="error in item.error(attr)" class="help is-danger">
[[ error ]] !
</p>
</div>
</template>
{% endfor %}
</a-playlist-editor>
</div>

View File

@ -34,3 +34,13 @@ def test_group_change_program(user, client, program):
user.groups.add(program.editors)
response = client.get(reverse("program-edit", kwargs={"pk": program.pk}))
assert response.status_code == 200
@pytest.mark.django_db()
def test_group_change_episode(user, client, program, episode):
client.force_login(user)
response = client.get(reverse("episode-edit", kwargs={"pk": episode.pk}))
assert response.status_code == 403
user.groups.add(program.editors)
response = client.get(reverse("episode-edit", kwargs={"pk": episode.pk}))
assert response.status_code == 200

View File

@ -1,3 +1,5 @@
from itertools import chain
import json
import pytest
from django.urls import reverse
from django.core.files.uploadedfile import SimpleUploadedFile
@ -32,3 +34,34 @@ def test_add_cover(user, client, program, png_content):
assert r.status_code == 200
p = Program.objects.get(pk=program.pk)
assert "cover1.png" in p.cover.url
@pytest.mark.django_db()
def test_edit_tracklist(user, client, program, episode, tracks):
user.groups.add(program.editors)
client.force_login(user)
episode.status = 0x10 # published
episode.save()
r = client.get(reverse("program-detail", kwargs={"slug": episode.program.slug}))
assert r.status_code == 200
r = client.get(reverse("episode-detail", kwargs={"slug": episode.slug}))
assert r.status_code == 200
r2 = client.get(reverse("episode-edit", kwargs={"pk": episode.pk}))
assert r2.status_code == 200
tracklist = [t.id for t in episode.track_set.all().order_by("position")]
tracklist_details_reversed = [(t.id, t.artist, t.title) for t in episode.track_set.all().order_by("-position")]
tracklist_details_reversed = list(chain(*tracklist_details_reversed))
data = """{{"website": [""], "content": ["foobar"], "new_podcast": [""], "form-TOTAL_FORMS": ["3"],
"form-INITIAL_FORMS": ["3"], "form-MIN_NUM_FORMS": ["0"], "form-MAX_NUM_FORMS": ["1000"], "form-0-position": ["0"],
"form-0-id": ["{}"], "form-0-": ["", "", "", "", "", ""], "form-0-artist": ["{}"], "form-0-title": ["{}"],
"form-0-tags": [""], "form-0-album": [""], "form-0-year": [""], "form-1-position": ["1"], "form-1-id": ["{}"],
"form-1-": ["", "", "", "", "", ""], "form-1-artist": ["{}"], "form-1-title": ["{}"], "form-1-tags": [""],
"form-1-album": [""], "form-1-year": [""], "form-2-position": ["2"], "form-2-id": ["{}"], "form-2-": ["", "", "",
"", "", ""], "form-2-artist": ["{}"], "form-2-title": ["{}"], "form-2-tags": [""], "form-2-album": [""],
"form-2-year": [""]}}""".format(
*tracklist_details_reversed
)
r = client.post(reverse("episode-edit", kwargs={"pk": episode.pk}), json.loads(data), follow=True)
assert r.status_code == 200
assert set(episode.track_set.all().values_list("id", flat=True)) == set(tracklist)

View File

@ -1,10 +1,10 @@
from django.contrib.auth.mixins import UserPassesTestMixin
from django.forms.models import modelformset_factory
from django.urls import reverse
from aircox.forms import EpisodeForm
from aircox.models import Episode, Program, StaticPage, Track
from ..filters import EpisodeFilters
from ..models import Episode, Program, StaticPage
from .page import PageListView
from .program import ProgramPageDetailView, BaseProgramMixin
from .page import PageUpdateView
@ -50,6 +50,7 @@ class PodcastListView(EpisodeListView):
class EpisodeUpdateView(UserPassesTestMixin, BaseProgramMixin, PageUpdateView):
model = Episode
form_class = EpisodeForm
template_name = "aircox/episode_form.html"
def get_sidebar_queryset(self):
return super().get_sidebar_queryset().filter(parent=self.program)
@ -60,3 +61,27 @@ class EpisodeUpdateView(UserPassesTestMixin, BaseProgramMixin, PageUpdateView):
def get_success_url(self):
return reverse("episode-detail", kwargs={"slug": self.get_object().slug})
def get_object(self, queryset=None):
obj = Episode.objects.get(pk=self.kwargs["pk"])
return obj
def get_formset(self, *args, **kwargs):
fields = ("position", "artist", "title", "tags", "album", "year", "info")
TrackFormSet = modelformset_factory(Track, fields=fields, extra=0)
return TrackFormSet(*args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["fields"] = ("position", "artist", "title", "tags", "album", "year", "info")
context["formset"] = self.get_formset(queryset=Track.objects.filter(episode=self.object))
return context
def post(self, request, *args, **kwargs):
super().post(request, *args, **kwargs)
formset = self.get_formset(request.POST)
if formset.is_valid():
formset.save()
return super().form_valid(formset)
else:
return super().form_valid(formset) # form_invalid(formset)

View File

@ -63,7 +63,7 @@ class ParentMixin:
def get_context_data(self, **kwargs):
parent = kwargs.setdefault("parent", self.parent)
if parent is not None:
if parent is not None and parent.cover:
kwargs.setdefault("cover", parent.cover.url)
return super().get_context_data(**kwargs)