From 71f4d2473e696bfc5df6cf08d9efbeb75806b457 Mon Sep 17 00:00:00 2001 From: Christophe Siraut Date: Mon, 5 Feb 2024 09:49:17 +0100 Subject: [PATCH] episode-form: add tracks inline formset --- aircox/static/aircox/js/ckeditor-init.js | 29 ++++++++ aircox/templates/aircox/episode_form.html | 15 ++++- aircox/templates/aircox/playlist_inline.html | 70 ++++++++++++++++++++ aircox/tests/test_permissions.py | 10 +++ aircox/tests/test_program.py | 33 +++++++++ aircox/views/episode.py | 28 ++++++++ 6 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 aircox/static/aircox/js/ckeditor-init.js create mode 100644 aircox/templates/aircox/playlist_inline.html 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 }} + + + + + + + + {% 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 a434bb6..8cce944 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 @@ -38,3 +40,34 @@ def test_add_cover(user, client, program): 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": ["%s"], "form-0-": ["", "", "", "", "", ""], "form-0-artist": ["%s"], "form-0-title": ["%s"], + "form-0-tags": [""], "form-0-album": [""], "form-0-year": [""], "form-1-position": ["1"], "form-1-id": ["%s"], + "form-1-": ["", "", "", "", "", ""], "form-1-artist": ["%s"], "form-1-title": ["%s"], "form-1-tags": [""], + "form-1-album": [""], "form-1-year": [""], "form-2-position": ["2"], "form-2-id": ["%s"], "form-2-": ["", "", "", + "", "", ""], "form-2-artist": ["%s"], "form-2-title": ["%s"], "form-2-tags": [""], "form-2-album": [""], + "form-2-year": [""]}""" % tuple( + tracklist_details_reversed + ) + r = client.post(reverse("episode-edit", kwargs={"pk": episode.pk}), json.loads(data), follow=True) + assert r.status_code == 200 + assert [t.id for t in episode.track_set.all().order_by("position")] == list(reversed(tracklist)) diff --git a/aircox/views/episode.py b/aircox/views/episode.py index dd166b2..7e7107f 100644 --- a/aircox/views/episode.py +++ b/aircox/views/episode.py @@ -1,11 +1,13 @@ from django.contrib.auth.mixins import UserPassesTestMixin from django.forms import ModelForm, FileField +from django.forms.models import modelformset_factory from django.urls import reverse from ckeditor.fields import RichTextField from filer.models.filemodels import File from aircox.controllers.sound_file import SoundFile +from aircox.models import Track from ..filters import EpisodeFilters from ..models import Episode, Program, StaticPage @@ -67,11 +69,13 @@ 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) 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) @@ -82,3 +86,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)