diff --git a/aircox/forms.py b/aircox/forms.py
index 469dea7..9843847 100644
--- a/aircox/forms.py
+++ b/aircox/forms.py
@@ -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)
diff --git a/aircox/static/aircox/js/ckeditor-init.js b/aircox/static/aircox/js/ckeditor-init.js
new file mode 100644
index 0000000..01fa94e
--- /dev/null
+++ b/aircox/static/aircox/js/ckeditor-init.js
@@ -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)
+ }
+ }
+//})()
diff --git a/aircox/templates/aircox/episode_form.html b/aircox/templates/aircox/episode_form.html
index 7a5191c..9eac5b9 100644
--- a/aircox/templates/aircox/episode_form.html
+++ b/aircox/templates/aircox/episode_form.html
@@ -1,12 +1,17 @@
{% extends "aircox/basepage_detail.html" %}
{% load static i18n humanize honeypot aircox %}
-
{% block head_extra %}
- {{ form.media }}
+
+
+
+
{% endblock %}
{% block init-scripts %}
+ aircox.init(null, {hotReload:false, initPlayer:false, initApp:true})
+ initialiseCKEditor()
+ initialiseCKEditorInInlinedForms()
{% endblock %}
{% block comments %}
@@ -18,9 +23,15 @@
{{ form.as_table }}
{% render_honeypot_field "website" %}
+
+
+ {% include "aircox/playlist_inline.html" %}
+
+
+
{% endblock %}
diff --git a/aircox/templates/aircox/playlist_inline.html b/aircox/templates/aircox/playlist_inline.html
new file mode 100644
index 0000000..83ad7cc
--- /dev/null
+++ b/aircox/templates/aircox/playlist_inline.html
@@ -0,0 +1,70 @@
+{% comment %}Inline block to edit playlists{% endcomment %}
+{% load aircox aircox_admin static i18n %}
+
+
+ {{ formset.non_form_errors }}
+
+
+
+
+ {% trans "Playlist" %}
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+ [[ row+1 ]]
+
+
+
+ {% for field in fields %}
+ {% if field != 'position' %}
+
+ {% endif %}
+ {% endfor %}
+ |
+
+ {% for field in fields %}
+
+
+
+ {% endfor %}
+
+
diff --git a/aircox/tests/test_permissions.py b/aircox/tests/test_permissions.py
index a561f33..4a3c954 100644
--- a/aircox/tests/test_permissions.py
+++ b/aircox/tests/test_permissions.py
@@ -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
diff --git a/aircox/tests/test_program.py b/aircox/tests/test_program.py
index 0f74d44..123d90f 100644
--- a/aircox/tests/test_program.py
+++ b/aircox/tests/test_program.py
@@ -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)
diff --git a/aircox/views/episode.py b/aircox/views/episode.py
index 36b2c83..cd6d44a 100644
--- a/aircox/views/episode.py
+++ b/aircox/views/episode.py
@@ -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)
diff --git a/aircox/views/mixins.py b/aircox/views/mixins.py
index 8018212..0abd48f 100644
--- a/aircox/views/mixins.py
+++ b/aircox/views/mixins.py
@@ -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)