Compare commits

..

19 Commits

Author SHA1 Message Date
07f3881dde docs: update user manual with simplified program management for animators 2024-02-28 16:57:04 +01:00
6469913f8a db: create program editors groups 2024-02-28 16:57:04 +01:00
75f1973d41 db: add missing migration on schedule timezone 2024-02-28 16:57:04 +01:00
b96cf59780 episode-form: add tracks inline formset 2024-02-28 16:57:04 +01:00
63c7fadd3f templatetags: display edit-links for admins 2024-02-28 16:57:04 +01:00
c9ddd38b9b templates: update after merging branch 118-design 2024-02-28 16:57:04 +01:00
72cd24083a templates: add in-context edition links 2024-02-28 16:57:04 +01:00
fd3411ed38 db: migrations merge 2024-02-28 16:57:04 +01:00
fddaaee169 templates: update container block names 2024-02-28 16:57:04 +01:00
42ae35a6ae signals: disable schedule_pre_save when using loaddata 2024-02-28 16:57:04 +01:00
1bfbdb304f templates: set document type to html, prevent quicks mode 2024-02-28 16:57:00 +01:00
27b0bec870 context_processors: prevent a null station error when no default station is defined 2024-02-28 16:55:14 +01:00
5986d86da3 views/program: allow changing program cover 2024-02-28 16:52:03 +01:00
b4539481e6 misc: add a profile view for authenticated users 2024-02-28 16:43:37 +01:00
25f6d91903 misc: use the django authentication system 2024-02-28 16:23:25 +01:00
8b3d3a2483 misc: move station and audio_streams to context_processors (in order to have them available in accounts views) 2024-02-28 16:23:25 +01:00
c8b0d1c5fb misc: edit programs in site 2024-02-28 16:23:19 +01:00
1674266890 templatetags: parametrize has_perm() in order to enable aircox namespace permissions 2024-02-28 15:17:38 +01:00
dd71f984ed models/program: link to editor groups 2024-02-28 15:17:31 +01:00
12 changed files with 81 additions and 87 deletions

View File

@ -1,7 +1,12 @@
from django import forms
from django.forms import ModelForm
from django.forms import ModelForm, ImageField, FileField
from .models import Comment
from ckeditor.fields import RichTextField
from filer.models.imagemodels import Image
from filer.models.filemodels import File
from aircox.models import Comment, Episode, Program
from aircox.controllers.sound_file import SoundFile
class CommentForm(ModelForm):
@ -16,3 +21,38 @@ class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ["nickname", "email", "content"]
class ProgramForm(ModelForm):
content = RichTextField()
new_cover = ImageField(required=False)
class Meta:
model = Program
fields = ["content"]
def save(self, commit=True):
file_obj = self.cleaned_data["new_cover"]
if file_obj:
obj, _ = Image.objects.get_or_create(original_filename=file_obj.name, file=file_obj)
self.instance.cover = obj
super().save(commit=commit)
class EpisodeForm(ModelForm):
content = RichTextField()
new_podcast = FileField(required=False)
class Meta:
model = Episode
fields = ["content"]
def save(self, commit=True):
file_obj = self.cleaned_data["new_podcast"]
if file_obj:
obj, _ = File.objects.get_or_create(original_filename=file_obj.name, file=file_obj)
sound_file = SoundFile(obj.path)
sound_file.sync(
program=self.instance.program, episode=self.instance, type=0, is_public=True, is_downloadable=True
)
super().save(commit=commit)

View File

@ -125,6 +125,7 @@ class Program(Page):
editors, created = Group.objects.get_or_create(name=self.editors_group_name)
if created:
self.editors = editors
super().save()
permission, _ = Permission.objects.get_or_create(
name=f"change program {self.title}",
codename=self.change_permission_codename,
@ -159,7 +160,6 @@ class Program(Page):
Sound.objects.filter(path__startswith=path_).update(file=Concat("file", Substr(F("file"), len(path_))))
self.set_group_ownership()
super().save(*kargs, **kwargs)
class ProgramChildQuerySet(PageQuerySet):

View File

@ -59,8 +59,6 @@ def do_get_tracks(obj):
@register.simple_tag(name="has_perm", takes_context=True)
def do_has_perm(context, obj, perm, user=None, simple=False):
"""Return True if ``user.has_perm('[APP].[perm]_[MODEL]')``"""
if not obj:
return
if user is None:
user = context["request"].user
if simple:
@ -104,8 +102,6 @@ def do_player_live_attr(context):
@register.simple_tag(name="nav_items", takes_context=True)
def do_nav_items(context, menu, **kwargs):
"""Render navigation items for the provided menu name."""
if not getattr(context["request"], "station"):
return []
station, request = context["station"], context["request"]
return [(item, item.render(request, **kwargs)) for item in station.navitem_set.filter(menu=menu)]

View File

@ -1,6 +1,7 @@
from datetime import time, timedelta
import itertools
import logging
import os
from django.conf import settings
from django.contrib.auth.models import User
@ -162,3 +163,10 @@ def tracks(episode, sound):
@pytest.fixture
def user():
return User.objects.create_user(username="user1", password="bar")
@pytest.fixture
def png_content():
image_file = "{}/image.png".format(os.path.dirname(__file__))
with open(image_file, "rb") as fh:
return fh.read()

BIN
aircox/tests/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@ -7,12 +7,6 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from aircox.models import Program
png_content = (
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde"
+ b"\x00\x00\x00\x0cIDATx\x9cc`\xf8\xcf\x00\x00\x02\x02\x01\x00{\t\x81x\x00\x00\x00\x00IEND\xaeB`\x82"
)
@pytest.mark.django_db()
def test_edit_program(user, client, program):
client.force_login(user)
@ -29,7 +23,7 @@ def test_edit_program(user, client, program):
@pytest.mark.django_db()
def test_add_cover(user, client, program):
def test_add_cover(user, client, program, png_content):
assert program.cover is None
user.groups.add(program.editors)
client.force_login(user)
@ -58,16 +52,16 @@ def test_edit_tracklist(user, client, program, episode, tracks):
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"],
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
"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 [t.id for t in episode.track_set.all().order_by("position")] == list(reversed(tracklist))
assert set(episode.track_set.all().values_list("id", flat=True)) == set(tracklist)

View File

@ -132,6 +132,6 @@ urls = [
views.errors.NoStationErrorView.as_view(),
name="errors-no-station",
),
path("gestion/", views.profile, name="profile"),
path("accounts/profile/", views.profile, name="profile"),
path("gestion/", views.ProfileView.as_view(), name="profile"),
path("accounts/profile/", views.ProfileView.as_view(), name="profile"),
]

View File

@ -11,7 +11,7 @@ from .page import (
PageDetailView,
PageListView,
)
from .profile import profile
from .profile import ProfileView
from .program import (
ProgramDetailView,
ProgramListView,
@ -40,7 +40,7 @@ __all__ = (
"BasePageListView",
"PageDetailView",
"PageListView",
"profile",
"ProfileView",
"ProgramDetailView",
"ProgramListView",
"ProgramPageDetailView",

View File

@ -1,16 +1,10 @@
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 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
@ -53,25 +47,6 @@ class PodcastListView(EpisodeListView):
queryset = Episode.objects.published().with_podcasts().order_by("-pub_date")
class EpisodeForm(ModelForm):
content = RichTextField()
new_podcast = FileField(required=False)
class Meta:
model = Episode
fields = ["content"]
def save(self, commit=True):
file_obj = self.cleaned_data["new_podcast"]
if file_obj:
obj, _ = File.objects.get_or_create(original_filename=file_obj.name, file=file_obj)
sound_file = SoundFile(obj.path)
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

View File

@ -16,6 +16,7 @@ __all__ = [
"BasePageDetailView",
"PageDetailView",
"PageListView",
"PageUpdateView",
]

View File

@ -1,15 +1,15 @@
from django.contrib.auth.decorators import login_required
from django.template.response import TemplateResponse
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.base import TemplateView
from aircox.models import Program
from aircox.views import BaseView
@login_required
def profile(request):
programs = []
ugroups = request.user.groups.all()
for p in Program.objects.all():
if p.editors in ugroups:
programs.append(p)
context = {"user": request.user, "programs": programs}
return TemplateResponse(request, "accounts/profile.html", context)
class ProfileView(LoginRequiredMixin, BaseView, TemplateView):
template_name = "accounts/profile.html"
def get_context_data(self, **kwargs):
groups = self.request.user.groups.all()
programs = Program.objects.filter(editors__in=groups)
kwargs.update({"user": self.request.user, "programs": programs})
return super().get_context_data(**kwargs)

View File

@ -1,14 +1,10 @@
import random
from django.contrib.auth.mixins import UserPassesTestMixin
from django.forms import ModelForm, ImageField
from django.urls import reverse
from ckeditor.fields import RichTextField
from filer.models.imagemodels import Image
from ..models import Article, Episode, Page, Program, StaticPage
from aircox.forms import ProgramForm
from aircox.models import Article, Episode, Page, Program, StaticPage
from .mixins import ParentMixin
from .page import PageDetailView, PageListView, PageUpdateView
@ -56,22 +52,6 @@ class ProgramDetailView(BaseProgramMixin, PageDetailView):
return super().get_template_names() + ["aircox/program_detail.html"]
class ProgramForm(ModelForm):
content = RichTextField()
new_cover = ImageField(required=False)
class Meta:
model = Program
fields = ["content"]
def save(self, commit=True):
file_obj = self.cleaned_data["new_cover"]
if file_obj:
obj, _ = Image.objects.get_or_create(original_filename=file_obj.name, file=file_obj)
self.instance.cover = obj
super().save(commit=commit)
class ProgramUpdateView(UserPassesTestMixin, BaseProgramMixin, PageUpdateView):
model = Program
form_class = ProgramForm