This commit is contained in:
bkfox 2024-03-15 20:54:52 +01:00
commit c79f040fa1
19 changed files with 1589 additions and 161 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)

File diff suppressed because it is too large Load Diff

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

@ -81,6 +81,9 @@ class Station(models.Model):
max_length=64,
default=_("Music stream"),
)
legal_label = models.CharField(
_("Legal label"), max_length=64, blank=True, default="", help_text=_("Displayed at the bottom of pages.")
)
objects = StationQuerySet.as_manager()

View File

@ -51,7 +51,7 @@ Usefull context:
<div class="navs">
{% block nav %}
<nav class="nav primary" role="navigation" aria-label="main navigation">
{% block nav-primary %}
{% block primary-nav %}
<a class="nav-brand" href="{% url "home" %}">
<img src="{{ station.logo.url }}">
</a>
@ -60,7 +60,7 @@ Usefull context:
aria-label="{% translate "Main menu" %}">
</a-switch>
<div class="nav-menu">
{% block nav-primary-menu %}
{% block primary-nav-menu %}
{% nav_items "top" css_class="nav-item" active_class="active" as items %}
{% for item, render in items %}
{{ render }}
@ -71,11 +71,9 @@ Usefull context:
</a>
{% endif %}
{% if user.is_authenticated %}
<a class="nav-item" href="{% url "profile" %}" target="new">
{% translate "Profile" %}
</a>
<a class="nav-item" href="{% url 'logout' %}">
<i title="{% translate 'disconnect' %}" class="fa fa-power-off"></i>
<a class="nav-item" href="{% url "logout" %}" title="{% translate "Disconnect" %}"
aria-label="{% translate "Disconnect" %}">
<i class="fa fa-power-off"></i>
</a>
{% endif %}
{% endblock %}
@ -98,8 +96,6 @@ Usefull context:
{% endblock %}
{% endspaceless %}
{% block header-container %}
{% if page or cover or title %}
<header class="container header preview preview-header {% if cover %}has-cover{% endif %}">
@ -146,6 +142,24 @@ Usefull context:
{% endblock %}
</main>
{% endblock %}
{% block footer-container %}
<footer class="page-footer">
{% block footer %}
{% if request.station and request.station.legal_label %}
{{ request.station.legal_label }} &mdash;
{% endif %}
<a class="nav-item" href="{% url "profile" %}" target="new"
title="{% translate "Profile" %}">
<span class="small icon">
<i class="fa fa-account">
</span>
</a>
{% endblock %}
</footer>
{% endblock %}
</div>
{% block player-container %}
<div id="player">{% include "aircox/widgets/player.html" %}</div>

View File

@ -4,15 +4,19 @@
{% if user.is_authenticated and can_edit %}
{% with request.resolver_match.view_name as view_name %}
&nbsp;
{% if view_name in 'program-edit,bla' %}
<!--
<a href="{% url 'program-detail' page.slug %}" target="_self">
<span title="{% translate 'View' %} {{ page }}">{% translate 'View' %} 👁 </span>
{% if view_name in 'page-edit,program-edit,episode-edit' %}
<a href="{% url view_name|detail_view page.slug %}" target="_self">
<small>
<span title="{% translate 'View' %} {{ page }}">{% translate 'View' %} </span>
<i class="fa-regular fa-eye"></i>
</small>
</a>
-->
{% else %}
<a href="{% url view_name|edit_view page.pk %}" target="_self">
<span title="{% translate 'Edit' %} {{ page }}">{% translate 'Edit' %} 🖉 </span>
<small>
<span title="{% translate 'Edit' %} {{ page }}">{% translate 'Edit' %} </span>
<i class="fa-solid fa-pencil"></i>
</small>
</a>
{% endif %}
{% endwith %}

View File

@ -23,10 +23,10 @@
<table>
{{ form.as_table }}
{% render_honeypot_field "website" %}
</table>
<br/>
<input type="submit" value="Update" class="button is-success">
<hr>
{% include "aircox/playlist_inline.html" %}

View File

@ -16,12 +16,23 @@
<section class="container">
<div>
<form method="post" enctype="multipart/form-data">{% csrf_token %}
<table>
{{ form.as_table }}
{% render_honeypot_field "website" %}
</table>
<br/>
<input type="submit" value="Update" class="button is-success">
{% csrf_token %}
{% for field in form %}
<div class="field is-horizontal">
<label class="label">{{ field.label }}</label>
<div class="control">{{ field }}</div>
</div>
{% if field.errors %}
<p class="help is-danger">{{ field.errors }}</p>
{% endif %}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
{% endfor %}
<div class="has-text-right">
<button type="submit" class="button">{% translate "Update" %}</button>
</div>
</form>
</div>
</section>

View File

@ -143,3 +143,8 @@ def do_verbose_name(obj, plural=False):
@register.filter(name="edit_view")
def do_edit_view(obj):
return "%s-edit" % obj.split("-")[0]
@register.filter(name="detail_view")
def do_detail_view(obj):
return "%s-detail" % obj.split("-")[0]

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(_("manage/"), 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