Compare commits

..

24 Commits

Author SHA1 Message Date
1aababe2ae docs: update user manual with simplified program management for animators 2024-02-06 09:57:21 +01:00
0dd961e0bb db: create program editors groups 2024-02-06 09:57:20 +01:00
f9da318a38 db: add missing migration on schedule timezone 2024-02-06 09:57:20 +01:00
26fa426416 views: avoid failing on missing parent cover 2024-02-06 09:40:45 +01:00
71f4d2473e episode-form: add tracks inline formset 2024-02-06 09:40:45 +01:00
2e9ebaded2 templatetags: display edit-links for admins 2024-02-06 09:40:45 +01:00
c6a4196319 templates: update after merging branch 118-design 2024-02-06 09:40:37 +01:00
be224d0efb templatetags: return on none type object 2024-02-05 10:29:58 +01:00
89f80ad103 templates: add in-context edition links 2024-02-05 10:29:58 +01:00
6d556fcd5d db: migrations merge 2024-02-05 10:29:55 +01:00
4201d50f4b templates: update container block names 2024-02-05 10:24:48 +01:00
6c942f36fa templatetags: avoid failing on nav_items when no station is defined 2024-02-05 10:24:48 +01:00
d51b9ee58b signals: disable schedule_pre_save when using loaddata 2024-02-05 10:24:48 +01:00
1a27ae2a76 misc: add in-site episode management for animators 2024-02-05 10:24:46 +01:00
e5862ee59b templates: set document type to html, prevent quicks mode 2024-02-05 10:22:16 +01:00
8f88b15536 ProgramUpdateView: use ckeditor RichTextField 2024-02-05 10:22:16 +01:00
10dfe3811b context_processors: prevent a null station error when no default station is defined 2024-02-05 10:22:16 +01:00
f71c201020 views/program: allow changing program cover 2024-02-05 10:22:16 +01:00
0812f3a0a1 misc: add a profile view for authenticated users 2024-02-05 10:22:14 +01:00
ad2ed17c34 misc: use the django authentication system 2024-02-05 10:19:05 +01:00
9db69580e0 misc: move station and audio_streams to context_processors (in order to have them available in accounts views) 2024-02-05 10:19:05 +01:00
4ead6b154b misc: edit programs in site 2024-02-05 10:19:05 +01:00
811cc97e07 templatetags: parametrize has_perm() in order to enable aircox namespace permissions 2024-02-05 10:19:05 +01:00
b794e24d0c models/program: link to editor groups 2024-02-05 10:19:05 +01:00
12 changed files with 87 additions and 81 deletions

View File

@ -1,12 +1,7 @@
from django import forms
from django.forms import ModelForm, ImageField, FileField
from django.forms import ModelForm
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
from .models import Comment
class CommentForm(ModelForm):
@ -21,38 +16,3 @@ 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,7 +125,6 @@ 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,
@ -160,6 +159,7 @@ 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,6 +59,8 @@ 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:
@ -102,6 +104,8 @@ 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,7 +1,6 @@
from datetime import time, timedelta
import itertools
import logging
import os
from django.conf import settings
from django.contrib.auth.models import User
@ -163,10 +162,3 @@ 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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

View File

@ -7,6 +7,12 @@ 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)
@ -23,7 +29,7 @@ def test_edit_program(user, client, program):
@pytest.mark.django_db()
def test_add_cover(user, client, program, png_content):
def test_add_cover(user, client, program):
assert program.cover is None
user.groups.add(program.editors)
client.force_login(user)
@ -52,16 +58,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": ["{}"], "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
"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 set(episode.track_set.all().values_list("id", flat=True)) == set(tracklist)
assert [t.id for t in episode.track_set.all().order_by("position")] == list(reversed(tracklist))

View File

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

View File

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

View File

@ -1,10 +1,16 @@
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 aircox.forms import EpisodeForm
from aircox.models import Episode, Program, StaticPage, Track
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
from .page import PageListView
from .program import ProgramPageDetailView, BaseProgramMixin
from .page import PageUpdateView
@ -47,6 +53,25 @@ 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,7 +16,6 @@ __all__ = [
"BasePageDetailView",
"PageDetailView",
"PageListView",
"PageUpdateView",
]

View File

@ -1,15 +1,15 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.base import TemplateView
from django.contrib.auth.decorators import login_required
from django.template.response import TemplateResponse
from aircox.models import Program
from aircox.views import BaseView
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)
@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)

View File

@ -1,10 +1,14 @@
import random
from django.contrib.auth.mixins import UserPassesTestMixin
from django.forms import ModelForm, ImageField
from django.urls import reverse
from aircox.forms import ProgramForm
from aircox.models import Article, Episode, Page, Program, StaticPage
from ckeditor.fields import RichTextField
from filer.models.imagemodels import Image
from ..models import Article, Episode, Page, Program, StaticPage
from .mixins import ParentMixin
from .page import PageDetailView, PageListView, PageUpdateView
@ -52,6 +56,22 @@ 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