rm file
This commit is contained in:
parent
1d321a0de6
commit
07d72d799d
|
@ -233,12 +233,12 @@ class SoundMonitor:
|
|||
if not program.ensure_dir(subdir):
|
||||
return
|
||||
|
||||
subdir = os.path.join(program.abspath, subdir)
|
||||
abs_subdir = os.path.join(program.abspath, subdir)
|
||||
sounds = []
|
||||
|
||||
# sounds in directory
|
||||
for path in os.listdir(subdir):
|
||||
path = os.path.join(subdir, path)
|
||||
for path in os.listdir(abs_subdir):
|
||||
path = os.path.join(abs_subdir, path)
|
||||
if not path.endswith(settings.SOUND_FILE_EXT):
|
||||
continue
|
||||
|
||||
|
@ -247,7 +247,7 @@ class SoundMonitor:
|
|||
sounds.append(sound_file.sound.pk)
|
||||
|
||||
# sounds in db & unchecked
|
||||
sounds = Sound.objects.filter(file__startswith=subdir).exclude(pk__in=sounds)
|
||||
sounds = Sound.objects.filter(file__startswith=program.path).exclude(pk__in=sounds)
|
||||
self.check_sounds(sounds, program=program)
|
||||
|
||||
def check_sounds(self, qs, **sync_kwargs):
|
||||
|
|
102
aircox/forms.py
102
aircox/forms.py
|
@ -1,102 +0,0 @@
|
|||
from django import forms
|
||||
from django.forms.models import modelformset_factory
|
||||
|
||||
|
||||
from aircox import models
|
||||
|
||||
|
||||
__all__ = ("CommentForm", "PageForm", "ProgramForm", "EpisodeForm", "SoundForm", "EpisodeSoundFormSet", "TrackFormSet")
|
||||
|
||||
|
||||
class CommentForm(forms.ModelForm):
|
||||
nickname = forms.CharField()
|
||||
email = forms.EmailField(required=False)
|
||||
content = forms.CharField(widget=forms.Textarea())
|
||||
|
||||
nickname.widget.attrs.update({"class": "input"})
|
||||
email.widget.attrs.update({"class": "input"})
|
||||
content.widget.attrs.update({"class": "textarea"})
|
||||
|
||||
class Meta:
|
||||
model = models.Comment
|
||||
fields = ["nickname", "email", "content"]
|
||||
|
||||
|
||||
class ImageForm(forms.Form):
|
||||
file = forms.ImageField()
|
||||
|
||||
|
||||
class PageForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ("title", "category", "status", "cover", "content")
|
||||
model = models.Page
|
||||
|
||||
|
||||
class ChildPageForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ("title", "status", "cover", "content")
|
||||
model = models.Page
|
||||
|
||||
|
||||
class ProgramForm(PageForm):
|
||||
class Meta:
|
||||
fields = PageForm.Meta.fields
|
||||
model = models.Program
|
||||
|
||||
|
||||
class EpisodeForm(PageForm):
|
||||
class Meta:
|
||||
model = models.Episode
|
||||
fields = ChildPageForm.Meta.fields
|
||||
|
||||
|
||||
class SoundForm(forms.ModelForm):
|
||||
"""SoundForm used in EpisodeUpdateView."""
|
||||
|
||||
class Meta:
|
||||
model = models.Sound
|
||||
fields = ["name", "program", "file", "broadcast", "duration", "is_public", "is_downloadable"]
|
||||
|
||||
|
||||
class SoundCreateForm(forms.ModelForm):
|
||||
"""SoundForm used in EpisodeUpdateView."""
|
||||
|
||||
class Meta:
|
||||
model = models.Sound
|
||||
fields = ["name", "program", "file", "broadcast", "is_public", "is_downloadable"]
|
||||
widgets = {"program": forms.HiddenInput()}
|
||||
|
||||
|
||||
TrackFormSet = modelformset_factory(
|
||||
models.Track,
|
||||
fields=[
|
||||
"position",
|
||||
"episode",
|
||||
"artist",
|
||||
"title",
|
||||
"tags",
|
||||
],
|
||||
widgets={"episode": forms.HiddenInput(), "position": forms.HiddenInput()},
|
||||
can_delete=True,
|
||||
extra=0,
|
||||
)
|
||||
"""Track formset used in EpisodeUpdateView."""
|
||||
|
||||
|
||||
EpisodeSoundFormSet = modelformset_factory(
|
||||
models.EpisodeSound,
|
||||
fields=(
|
||||
"position",
|
||||
"episode",
|
||||
"sound",
|
||||
"broadcast",
|
||||
),
|
||||
widgets={
|
||||
"broadcast": forms.CheckboxInput(),
|
||||
"episode": forms.HiddenInput(),
|
||||
# "sound": forms.HiddenInput(),
|
||||
"position": forms.HiddenInput(),
|
||||
},
|
||||
can_delete=True,
|
||||
extra=0,
|
||||
)
|
23
aircox/forms/__init__.py
Normal file
23
aircox/forms/__init__.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from . import widgets
|
||||
|
||||
from .auth import UserGroupFormSet
|
||||
from .episode import EpisodeForm, EpisodeSoundFormSet
|
||||
from .program import ProgramForm
|
||||
from .page import CommentForm, ImageForm, PageForm, ChildPageForm
|
||||
from .sound import SoundForm, SoundCreateForm
|
||||
|
||||
|
||||
__all__ = (
|
||||
widgets,
|
||||
# ---- forms
|
||||
EpisodeForm,
|
||||
EpisodeSoundFormSet,
|
||||
ProgramForm,
|
||||
CommentForm,
|
||||
ImageForm,
|
||||
PageForm,
|
||||
ChildPageForm,
|
||||
SoundForm,
|
||||
SoundCreateForm,
|
||||
UserGroupFormSet,
|
||||
)
|
20
aircox/forms/auth.py
Normal file
20
aircox/forms/auth.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from django import forms
|
||||
from django.forms.models import modelformset_factory
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from aircox.forms import widgets
|
||||
|
||||
|
||||
__all__ = ("UserGroupFormSet",)
|
||||
|
||||
|
||||
UserGroupFormSet = modelformset_factory(
|
||||
User.groups.through,
|
||||
fields=("group", "user"),
|
||||
widgets={
|
||||
"group": forms.HiddenInput(),
|
||||
"user": widgets.VueAutoComplete("api:usergroup-autocomplete", lookup="username"),
|
||||
},
|
||||
extra=0,
|
||||
can_delete=True,
|
||||
)
|
34
aircox/forms/episode.py
Normal file
34
aircox/forms/episode.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from django import forms
|
||||
from django.forms.models import modelformset_factory
|
||||
|
||||
from aircox import models
|
||||
from .page import ChildPageForm
|
||||
|
||||
|
||||
__all__ = ("EpisodeForm", "EpisodeSoundFormSet")
|
||||
|
||||
|
||||
class EpisodeForm(ChildPageForm):
|
||||
class Meta:
|
||||
model = models.Episode
|
||||
fields = ChildPageForm.Meta.fields
|
||||
|
||||
|
||||
EpisodeSoundFormSet = modelformset_factory(
|
||||
models.EpisodeSound,
|
||||
fields=(
|
||||
"position",
|
||||
"episode",
|
||||
"sound",
|
||||
"broadcast",
|
||||
),
|
||||
widgets={
|
||||
"broadcast": forms.CheckboxInput(),
|
||||
"episode": forms.HiddenInput(),
|
||||
# "sound": forms.HiddenInput(),
|
||||
"position": forms.HiddenInput(),
|
||||
},
|
||||
can_delete=True,
|
||||
extra=0,
|
||||
)
|
||||
"""Formset used in EpisodeUpdateView."""
|
37
aircox/forms/page.py
Normal file
37
aircox/forms/page.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from django import forms
|
||||
|
||||
|
||||
from aircox import models
|
||||
|
||||
|
||||
__all__ = ("CommentForm", "ImageForm", "PageForm", "ChildPageForm")
|
||||
|
||||
|
||||
class CommentForm(forms.ModelForm):
|
||||
nickname = forms.CharField()
|
||||
email = forms.EmailField(required=False)
|
||||
content = forms.CharField(widget=forms.Textarea())
|
||||
|
||||
nickname.widget.attrs.update({"class": "input"})
|
||||
email.widget.attrs.update({"class": "input"})
|
||||
content.widget.attrs.update({"class": "textarea"})
|
||||
|
||||
class Meta:
|
||||
model = models.Comment
|
||||
fields = ["nickname", "email", "content"]
|
||||
|
||||
|
||||
class ImageForm(forms.Form):
|
||||
file = forms.ImageField()
|
||||
|
||||
|
||||
class PageForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ("title", "category", "status", "cover", "content")
|
||||
model = models.Page
|
||||
|
||||
|
||||
class ChildPageForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ("title", "status", "cover", "content")
|
||||
model = models.Page
|
11
aircox/forms/program.py
Normal file
11
aircox/forms/program.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from aircox import models
|
||||
from .page import PageForm
|
||||
|
||||
|
||||
__all__ = ("ProgramForm",)
|
||||
|
||||
|
||||
class ProgramForm(PageForm):
|
||||
class Meta:
|
||||
fields = PageForm.Meta.fields
|
||||
model = models.Program
|
26
aircox/forms/sound.py
Normal file
26
aircox/forms/sound.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from django import forms
|
||||
|
||||
from aircox import models
|
||||
|
||||
|
||||
__all__ = (
|
||||
"SoundForm",
|
||||
"SoundCreateForm",
|
||||
)
|
||||
|
||||
|
||||
class SoundForm(forms.ModelForm):
|
||||
"""SoundForm used in EpisodeUpdateView."""
|
||||
|
||||
class Meta:
|
||||
model = models.Sound
|
||||
fields = ["name", "program", "file", "broadcast", "duration", "is_public", "is_downloadable"]
|
||||
|
||||
|
||||
class SoundCreateForm(forms.ModelForm):
|
||||
"""SoundForm used in EpisodeUpdateView."""
|
||||
|
||||
class Meta:
|
||||
model = models.Sound
|
||||
fields = ["name", "program", "file", "broadcast", "is_public", "is_downloadable"]
|
||||
widgets = {"program": forms.HiddenInput()}
|
23
aircox/forms/track.py
Normal file
23
aircox/forms/track.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from django import forms
|
||||
from django.forms.models import modelformset_factory
|
||||
|
||||
from aircox import models
|
||||
|
||||
|
||||
__all__ = ("TrackFormSet",)
|
||||
|
||||
|
||||
TrackFormSet = modelformset_factory(
|
||||
models.Track,
|
||||
fields=[
|
||||
"position",
|
||||
"episode",
|
||||
"artist",
|
||||
"title",
|
||||
"tags",
|
||||
],
|
||||
widgets={"episode": forms.HiddenInput(), "position": forms.HiddenInput()},
|
||||
can_delete=True,
|
||||
extra=0,
|
||||
)
|
||||
"""Track formset used in EpisodeUpdateView."""
|
|
@ -4,14 +4,6 @@ from django.db import migrations, models
|
|||
import django.db.models.deletion
|
||||
|
||||
|
||||
def init_groups_and_permissions(app, schema_editor):
|
||||
from aircox.permissions import program_permissions
|
||||
|
||||
Program = app.get_model("aircox", "Program")
|
||||
for program in Program.objects.all():
|
||||
program_permissions.init(program)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
|
@ -25,10 +17,9 @@ class Migration(migrations.Migration):
|
|||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="auth.group",
|
||||
verbose_name="editors",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(init_groups_and_permissions),
|
||||
]
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.db import migrations, models
|
|||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("filer", "0017_image__transparent"),
|
||||
("aircox", "0021_alter_schedule_timezone"),
|
||||
("aircox", "0022_set_group_ownership"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -10,7 +10,7 @@ sounds_info = {}
|
|||
|
||||
def get_sounds_info(apps, schema_editor):
|
||||
Sound = apps.get_model("aircox", "Sound")
|
||||
objs = Sound.objects.filter(episode__isnull=False).values(
|
||||
objs = Sound.objects.filter().values(
|
||||
"pk",
|
||||
"episode_id",
|
||||
"position",
|
||||
|
@ -36,7 +36,7 @@ def restore_sounds_info(apps, schema_editor):
|
|||
sound.broadcast = info["type"] == TYPE_ARCHIVE
|
||||
sound.is_removed = info["type"] == TYPE_REMOVED
|
||||
sounds.append(sound)
|
||||
if not sound.is_removed:
|
||||
if not sound.is_removed and info["episode_id"]:
|
||||
obj = EpisodeSound(
|
||||
sound=sound,
|
||||
episode_id=info["episode_id"],
|
||||
|
|
|
@ -33,11 +33,6 @@ def _restore_for_objs(objs):
|
|||
return updated
|
||||
|
||||
|
||||
def set_group_ownership(*args):
|
||||
for program in Program.objects.all():
|
||||
program.set_group_ownership()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("aircox", "0026_alter_sound_options_remove_sound_episode_and_more"),
|
||||
|
|
|
@ -107,7 +107,10 @@ class File(models.Model):
|
|||
|
||||
def file_updated(self):
|
||||
"""Return True when file has been updated on filesystem."""
|
||||
return self.mtime != self.get_mtime() or self.is_removed != (not self.file_exists())
|
||||
exists = self.file_exists()
|
||||
if self.is_removed != (not exists):
|
||||
return True
|
||||
return exists and self.mtime != self.get_mtime()
|
||||
|
||||
def file_exists(self):
|
||||
"""Return true if the file still exists."""
|
||||
|
@ -130,7 +133,7 @@ class File(models.Model):
|
|||
name = name.replace("_", " ").strip()
|
||||
|
||||
is_removed = not self.file_exists()
|
||||
mtime = self.get_mtime()
|
||||
mtime = (not is_removed and self.get_mtime()) or None
|
||||
|
||||
changed = is_removed != self.is_removed or mtime != self.mtime or name != self.name
|
||||
self.name, self.is_removed, self.mtime = name, is_removed, mtime
|
||||
|
|
|
@ -63,7 +63,7 @@ class Program(Page):
|
|||
default=True,
|
||||
help_text=_("update later diffusions according to schedule changes"),
|
||||
)
|
||||
editors_group = models.ForeignKey(Group, models.CASCADE, blank=True, null=True, verbose_name=_("editors"))
|
||||
editors_group = models.ForeignKey(Group, models.CASCADE, verbose_name=_("editors"))
|
||||
|
||||
objects = ProgramQuerySet.as_manager()
|
||||
detail_url_name = "program-detail"
|
||||
|
|
|
@ -24,7 +24,7 @@ class SoundQuerySet(FileQuerySet):
|
|||
|
||||
def broadcast(self):
|
||||
"""Return sounds that are archives."""
|
||||
return self.filter(broadcast=True)
|
||||
return self.filter(broadcast=True, is_removed=False)
|
||||
|
||||
def playlist(self, order_by="file"):
|
||||
"""Return files absolute paths as a flat list (exclude sound without
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||
from .models import Program
|
||||
|
||||
|
||||
__all__ = ("PagePermissions", "program_permissions")
|
||||
__all__ = ("PagePermissions", "program")
|
||||
|
||||
|
||||
class PagePermissions:
|
||||
|
@ -30,7 +30,6 @@ class PagePermissions:
|
|||
"""Return True wether if user can edit Program or its children."""
|
||||
from .models.page import ChildPage
|
||||
|
||||
breakpoint()
|
||||
if isinstance(obj, ChildPage):
|
||||
obj = obj.parent_subclass
|
||||
|
||||
|
@ -43,20 +42,23 @@ class PagePermissions:
|
|||
perm = self.perms_codename_format.format(self=self, perm=perm)
|
||||
return user.has_perm(perm)
|
||||
|
||||
# TODO: bulk init
|
||||
def init(self, obj):
|
||||
def init(self, obj, model=None):
|
||||
"""Initialize permissions for the provided obj."""
|
||||
updated = False
|
||||
created_groups = []
|
||||
|
||||
# init groups
|
||||
for infos in self.groups:
|
||||
group = getattr(obj, infos["field"])
|
||||
if obj.pk == 12417:
|
||||
breakpoint()
|
||||
if not group:
|
||||
group, created = self.init_group(obj, infos)
|
||||
setattr(obj, infos["field"], group.pk)
|
||||
updated = True
|
||||
created and created_groups.append((group, infos))
|
||||
|
||||
if created_groups:
|
||||
if updated:
|
||||
obj.save()
|
||||
|
||||
# init perms
|
||||
|
@ -79,4 +81,4 @@ class PagePermissions:
|
|||
group.permissions.add(perm)
|
||||
|
||||
|
||||
program_permissions = PagePermissions(Program)
|
||||
program = PagePermissions(Program)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from . import auth
|
||||
from .admin import TrackSerializer, UserSettingsSerializer
|
||||
from .episode import EpisodeSoundSerializer, EpisodeSerializer
|
||||
from .log import LogInfo, LogInfoSerializer
|
||||
|
@ -5,6 +6,7 @@ from .page import CommentSerializer
|
|||
from .sound import SoundSerializer
|
||||
|
||||
__all__ = (
|
||||
"auth",
|
||||
"CommentSerializer",
|
||||
"LogInfo",
|
||||
"LogInfoSerializer",
|
||||
|
|
|
@ -10,6 +10,7 @@ Usefull context:
|
|||
{% endcomment %}
|
||||
<html>
|
||||
<head>
|
||||
{% block head %}
|
||||
<meta charset="utf-8" />
|
||||
<meta name="application-name" content="aircox" />
|
||||
<meta name="description" content="{{ site.description }}" />
|
||||
|
@ -27,17 +28,17 @@ Usefull context:
|
|||
{% endblock %}
|
||||
|
||||
<title>
|
||||
{% block head_title %}
|
||||
{% if page and page.title %}{{ page.title }} — {{ station.name }}
|
||||
{% else %}{{ station.name }}
|
||||
{% block head-title %}
|
||||
{% if page and page.title %}{{ page.title }} —
|
||||
{% endif %}
|
||||
{{ station.name }}
|
||||
{% endblock %}
|
||||
</title>
|
||||
|
||||
{% block head_extra %}{% endblock %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body {% if request.is_mobile %}class="mobile"{% endif %}>
|
||||
{% block body-head %}{% endblock %}
|
||||
{% block body %}
|
||||
<script id="init-script">
|
||||
window.addEventListener('load', function() {
|
||||
{% block init-scripts %}
|
||||
|
@ -65,7 +66,7 @@ Usefull context:
|
|||
{% endfor %}
|
||||
{% endblock %}
|
||||
{% if user.is_authenticated %}
|
||||
{% include "./dashboard/widgets/nav.html" %}
|
||||
{% include "./widgets/nav.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -115,7 +116,6 @@ Usefull context:
|
|||
{% endblock %}
|
||||
</span>
|
||||
{% endspaceless %}
|
||||
</span>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
@ -157,5 +157,7 @@ Usefull context:
|
|||
{% block player-container %}
|
||||
<div id="player">{% include "aircox/widgets/player.html" %}</div>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
{% extends "aircox/base.html" %}
|
||||
{% comment %}Display detail of a BasePage{% endcomment %}
|
||||
|
||||
{% block head_title %}
|
||||
{% block title %}{{ page.title }}{% endblock %}
|
||||
—
|
||||
{{ station.name }}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}{% if page %}{{ block.super }}{% endif %}{% endblock %}
|
|
@ -1,16 +1,8 @@
|
|||
{% extends "./base.html" %}
|
||||
{% extends "./public.html" %}
|
||||
|
||||
{% comment %}Display a list of BasePages{% endcomment %}
|
||||
{% load i18n aircox %}
|
||||
|
||||
{% block head_title %}
|
||||
{% block title %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
—
|
||||
{{ station.name }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ block.super }}
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block head-title %}
|
||||
{% block title %}{{ block.super }}{% endblock %}
|
||||
—
|
||||
{{ block.super }}
|
||||
{% block title %}
|
||||
{% if page and page.title %}{{ page.title }} —{% endif %}
|
||||
{% endblock %}
|
||||
{{ station.name }}
|
||||
{% endblock %}
|
||||
|
|
|
@ -29,11 +29,11 @@ sound-delete-url="{% url "api:sound-detail" pk=123 %}"
|
|||
{% for field in sound_form %}
|
||||
{% with field.name as name %}
|
||||
{% if name in "program" %}
|
||||
{% include "./form_field.html" with value=field.initial field=field.field hidden=True %}
|
||||
{% include "aircox/forms/form_field.html" with value=field.initial field=field.field hidden=True %}
|
||||
{% elif name != "file" %}
|
||||
<div class="field is-horizontal">
|
||||
<label class="label mr-3">{{ field.label }}</label>
|
||||
{% include "./form_field.html" with value=field.initial field=field.field %}
|
||||
{% include "aircox/forms/form_field.html" with value=field.initial field=field.field %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "./page_form.html" %}
|
||||
{% load static i18n humanize honeypot aircox %}
|
||||
|
||||
{% block page_form %}
|
||||
{% block page-form %}
|
||||
<a-episode :page="{title: "{{ object.title }}", podcasts: {{ object.sounds|json }}}">
|
||||
<template v-slot="{podcasts,page}">
|
||||
{{ block.super }}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
{% endblock %}
|
||||
|
||||
|
||||
{% block content-container %}
|
||||
{% block content %}
|
||||
<article class="message is-danger">
|
||||
<div class="message-header">
|
||||
<p>{% block error_title %}{% trans "An error occurred" %}{% endblock %}</p>
|
||||
|
|
25
aircox/templates/aircox/forms/form_field.html
Normal file
25
aircox/templates/aircox/forms/form_field.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% comment %}
|
||||
Render a form field instance as field (to be used when no model instance is provided). Value is binded as vue, class to Bulma
|
||||
|
||||
Context:
|
||||
- name: field name
|
||||
- field: form field
|
||||
- value: input ":value" attribute
|
||||
- vbind: if True, use ":value" instead of "value"
|
||||
- hidden: if True, hidden field
|
||||
{% endcomment %}
|
||||
{% load aircox %}
|
||||
|
||||
{% if field.widget.is_hidden or hidden %}
|
||||
<input type="hidden" name="{{ name }}" value="{{ value|default:"" }}">
|
||||
{% elif field|is_checkbox %}
|
||||
<input type="checkbox" class="checkbox" name="{{ name }}" {% if value %}checked{% endif %}>
|
||||
{% elif field|is_select %}
|
||||
<select name="{{ name }}" class="select" value="{{ value|default:"" }}">
|
||||
{% for value, label in field.widget.choices %}
|
||||
<option value="{{ value }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input type="text" class="input" name="{{ name }}" value="{{ value|default:"" }}">
|
||||
{% endif %}
|
54
aircox/templates/aircox/forms/formset.html
Normal file
54
aircox/templates/aircox/forms/formset.html
Normal file
|
@ -0,0 +1,54 @@
|
|||
{% comment %}
|
||||
Base template for list editor based on formsets (tracklist_editor, playlist_editor).
|
||||
|
||||
Context:
|
||||
- tag_id: id of parent component
|
||||
- tag: vue component tag (a-playlist-editor, etc.)
|
||||
- related_field: field name that target object
|
||||
- object: related object
|
||||
- formset: formset used to render the list editor
|
||||
- formset_data: formset data
|
||||
{% endcomment %}
|
||||
|
||||
{% load aircox aircox_admin static i18n %}
|
||||
|
||||
{% with formset.form.base_fields as fields %}
|
||||
{% block outer %}
|
||||
<div id="{{ tag_id }}">
|
||||
{{ formset.non_form_errors }}
|
||||
<!-- formset.management_form -->
|
||||
|
||||
<{{ tag|default:"a-form-set" }}
|
||||
{% block tag-attrs %}
|
||||
:form-data="{{ formset_data|json }}"
|
||||
:labels="window.aircox.labels"
|
||||
:init-data="{% formset_inline_data formset=formset %}"
|
||||
:columns="[{% for n, f in fields.items %}{% if not f.widget.is_hidden %}'{{ n }}',{% endif %}{% endfor %} ]"
|
||||
settings-url="{% url "api:user-settings" %}"
|
||||
data-prefix="{{ formset.prefix }}-"
|
||||
{% endblock %}>
|
||||
{% block inner %}
|
||||
<template #rows-header-head>
|
||||
{% block rows-header-head %}
|
||||
<th style="max-width:2em" title="{{ fields.position.help_text }}"
|
||||
aria-description="{{ fields.position.help_text }}">
|
||||
<span class="icon">
|
||||
<i class="fa fa-arrow-down-1-9"></i>
|
||||
</span>
|
||||
</th>
|
||||
{% endblock %}
|
||||
</template>
|
||||
{% for name, field in fields.items %}
|
||||
{% if not field.widget.is_hidden and not field.is_readonly %}
|
||||
<template v-slot:control-{{ name }}="{item,cell,value,attr,emit,inputName}">
|
||||
{% block row-control %}
|
||||
{% include "./v_form_field.html" with value="item.data."|add:name name="inputName" %}
|
||||
{% endblock %}
|
||||
</template>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
</{{ tag }}>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endwith %}
|
24
aircox/templates/aircox/forms/v_form_field.html
Normal file
24
aircox/templates/aircox/forms/v_form_field.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
{% comment %}
|
||||
Render a form field instance as field (to be used when no model instance is provided). Value is binded as vue, class to Bulma
|
||||
|
||||
Context:
|
||||
- name: field name
|
||||
- field: form field
|
||||
- value: input ":v-model" attribute
|
||||
- hidden: if True, hidden field
|
||||
{% endcomment %}
|
||||
{% load aircox %}
|
||||
|
||||
{% if field.widget.is_hidden or hidden %}
|
||||
<input type="hidden" :name="{{ name }}" :value="{{ value|default:"" }}">
|
||||
{% elif field|is_checkbox %}
|
||||
<input type="checkbox" class="checkbox" :name="{{ name }}" v-model="{{ value }}">
|
||||
{% elif field|is_select %}
|
||||
<select :name="{{ name }}" class="select" v-model="{{ value|default:"" }}">
|
||||
{% for value, label in field.widget.choices %}
|
||||
<option value="{{ value }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input type="text" class="input" :name="{{ name }}" v-model="{{ value|default:"" }}">
|
||||
{% endif %}
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "aircox/base.html" %}
|
||||
{% extends "./public.html" %}
|
||||
{% load i18n aircox %}
|
||||
|
||||
{% block head_title %}{{ station.name }}{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "aircox/basepage_detail.html" %}
|
||||
{% extends "aircox/public.html" %}
|
||||
{% load static i18n humanize honeypot aircox %}
|
||||
{% comment %}
|
||||
Base template used to display a Page
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "./page_detail.html" %}
|
||||
{% extends "./dashboard/base.html" %}
|
||||
{% load static aircox_admin i18n %}
|
||||
|
||||
{% block assets %}
|
||||
|
@ -20,6 +20,7 @@ aircox.labels = {% inline_labels %}
|
|||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content-container %}
|
||||
<a-select-file ref="cover-select"
|
||||
:labels="window.aircox.labels"
|
||||
|
@ -54,7 +55,7 @@ aircox.labels = {% inline_labels %}
|
|||
|
||||
<section class="container">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% block page_form %}
|
||||
{% block page-form %}
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% if field.name == "cover" %}
|
||||
|
@ -69,7 +70,7 @@ aircox.labels = {% inline_labels %}
|
|||
{% elif field.name == "content" %}
|
||||
<textarea name="{{ field.name }}" class="is-fullwidth height-25">{{ field.value|striptags|safe }}</textarea>
|
||||
{% else %}
|
||||
{% include "./dashboard/widgets/form_field.html" with field=field.field name=field.name value=field.initial %}
|
||||
{% include "aircox/forms/form_field.html" with field=field.field name=field.name value=field.initial %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="help">{{ field.help_text }}</p>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "aircox/page_detail.html" %}
|
||||
{% extends "./page_form.html" %}
|
||||
{% load static i18n humanize honeypot aircox %}
|
||||
|
||||
|
||||
|
@ -6,34 +6,13 @@
|
|||
{{ form.media }}
|
||||
{% endblock %}
|
||||
|
||||
{% block init-scripts %}
|
||||
{% endblock %}
|
||||
{% block page-form %}
|
||||
//////
|
||||
{{ block.super }}
|
||||
|
||||
{% block comments %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content-container %}
|
||||
<section class="container">
|
||||
<div>
|
||||
<form method="post" enctype="multipart/form-data">{% csrf_token %}
|
||||
{% 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>
|
||||
{% if editors_formset %}
|
||||
<hr/>
|
||||
<h2 class="title is-2">{% translate "Editors" %}</h2>
|
||||
{% include "./widgets/usergroup_formset.html" with formset=editors_formset formset_data=editors_formset_data tag_id="usergroup_formset" %}
|
||||
{% 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>
|
||||
{% endblock %}
|
||||
|
|
17
aircox/templates/aircox/public.html
Normal file
17
aircox/templates/aircox/public.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{% extends base_template|default:"./base.html" %}
|
||||
|
||||
|
||||
{% comment %}
|
||||
Override is a trick here: it allows to change title at two different different
|
||||
places inside the page: inside `<title>` tag, and inside the page
|
||||
content.
|
||||
{% endcomment %}
|
||||
|
||||
{% block head-title %}
|
||||
{% block title %}
|
||||
{% if page and page.title %}{{ page.title }} —{% endif %}
|
||||
{% endblock %}
|
||||
{{ station.name }}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}{% if page %}{{ block.super }}{% endif %}{% endblock %}
|
4
aircox/templates/aircox/widgets/autocomplete.html
Normal file
4
aircox/templates/aircox/widgets/autocomplete.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<a-autocomplete
|
||||
url="{{url}}"
|
||||
name="{{ widget.name }}"{% if widget.value != None %} model-value="{{ widget.value|stringformat:'s' }}"{% endif %}
|
||||
{% include "django/forms/widgets/attrs.html" %} />
|
|
@ -25,31 +25,12 @@
|
|||
{% endblock %}
|
||||
|
||||
|
||||
{% block actions %}
|
||||
{% if object.sound_set.public.count %}
|
||||
<button class="button" @click="player.playButtonClick($event)"
|
||||
data-sounds="{{ object.podcasts|json }}">
|
||||
<span class="icon is-small">
|
||||
<span class="fas fa-play"></span>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block actions %}
|
||||
{% has_perm page object.program.change_permission_codename simple=True as can_edit %}
|
||||
{% if can_edit %}
|
||||
<a class="button" href="{% url 'episode-edit' object.pk %}" target="_self">
|
||||
<span class="icon is-small"><i class="fas fa-pen" alt="{% trans 'edit' %}"></i></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if object.sound_set.public.count %}
|
||||
<button class="button" @click="player.playButtonClick($event)"
|
||||
data-sounds="{{ object.podcasts|json }}">
|
||||
<span class="icon is-small">
|
||||
<span class="fas fa-play"></span>
|
||||
</span>
|
||||
</button>
|
||||
{% block content %}
|
||||
{% if not object.content %}
|
||||
{% with object.parent.content as content %}
|
||||
{{ block.super }}
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
{{ block.super }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
10
aircox/templates/aircox/widgets/usergroup_formset.html
Normal file
10
aircox/templates/aircox/widgets/usergroup_formset.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% extends "aircox/forms/formset.html" %}
|
||||
{% load aircox %}
|
||||
|
||||
{% block row-control %}
|
||||
{% if name == 'user' %}
|
||||
{% form_field field name value %}
|
||||
{% else %}
|
||||
{{ block.super }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -14,6 +14,12 @@ random.seed()
|
|||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag(name="form_field")
|
||||
def form_field(field, name=None, value=None, **kwargs):
|
||||
name = name or field.name
|
||||
return field.widget.render(name=name, value=value, **kwargs)
|
||||
|
||||
|
||||
@register.filter(name="admin_url")
|
||||
def admin_url(obj, action):
|
||||
meta = obj._meta
|
||||
|
|
|
@ -25,6 +25,7 @@ router.register("images", viewsets.ImageViewSet, basename="image")
|
|||
router.register("sound", viewsets.SoundViewSet, basename="sound")
|
||||
router.register("track", viewsets.TrackROViewSet, basename="track")
|
||||
router.register("comment", viewsets.CommentViewSet, basename="comment")
|
||||
router.register("usergroup", viewsets.UserGroupViewSet, basename="usergroup")
|
||||
|
||||
|
||||
api = [
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.db.models import Q
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic.base import TemplateView
|
||||
|
||||
|
@ -12,9 +12,13 @@ from .log import LogListView
|
|||
__all__ = ("DashboardBaseView", "DashboardView", "StatisticsView")
|
||||
|
||||
|
||||
class DashboardBaseView(LoginRequiredMixin, BaseView):
|
||||
class DashboardBaseView(LoginRequiredMixin, UserPassesTestMixin, BaseView):
|
||||
title = _("Dashboard")
|
||||
|
||||
def test_func(self):
|
||||
user = self.request.user
|
||||
return user.is_staff or user.is_superuser
|
||||
|
||||
|
||||
class DashboardView(DashboardBaseView, TemplateView):
|
||||
template_name = "aircox/dashboard/dashboard.html"
|
||||
|
|
|
@ -52,7 +52,7 @@ class EpisodeUpdateView(UserPassesTestMixin, VueFormDataMixin, PageUpdateView):
|
|||
|
||||
def test_func(self):
|
||||
obj = self.get_object()
|
||||
return permissions.program_permissions.can(self.request.user, "update", obj)
|
||||
return permissions.program.can(self.request.user, "update", obj)
|
||||
|
||||
def get_tracklist_queryset(self, episode):
|
||||
return Track.objects.filter(episode=episode).order_by("position")
|
||||
|
|
|
@ -194,13 +194,9 @@ class PageDetailView(BasePageDetailView):
|
|||
|
||||
class PageUpdateView(BaseView, UpdateView):
|
||||
context_object_name = "page"
|
||||
template_name = "aircox/page_form.html"
|
||||
|
||||
def get_page(self):
|
||||
return self.object
|
||||
|
||||
def get_success_url(self):
|
||||
return self.request.path
|
||||
|
||||
def get_comment_form(self):
|
||||
return None
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import random
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
from django.urls import reverse
|
||||
|
||||
from aircox import models, forms, permissions
|
||||
from .mixins import VueFormDataMixin
|
||||
from .page import PageDetailView, PageListView, PageUpdateView
|
||||
|
||||
__all__ = (
|
||||
|
@ -51,13 +53,39 @@ class ProgramListView(PageListView):
|
|||
return super().get_queryset().order_by("title")
|
||||
|
||||
|
||||
class ProgramUpdateView(UserPassesTestMixin, PageUpdateView):
|
||||
class ProgramUpdateView(UserPassesTestMixin, VueFormDataMixin, PageUpdateView):
|
||||
model = models.Program
|
||||
form_class = forms.ProgramForm
|
||||
queryset = models.Program.objects.select_related("editors_group")
|
||||
|
||||
def test_func(self):
|
||||
obj = self.get_object()
|
||||
return permissions.program_permissions.can(self.request.user, "update", obj)
|
||||
return permissions.program.can(self.request.user, "update", obj)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("program-detail", kwargs={"slug": self.get_object().slug})
|
||||
def get_editors_queryset(self, program):
|
||||
# TODO: provide username in formset initials
|
||||
return User.groups.through.objects.filter(group_id=program.editors_group_id).order_by("user__username")
|
||||
|
||||
def get_editors_formset(self, program, **kwargs):
|
||||
return forms.UserGroupFormSet(
|
||||
**{
|
||||
**kwargs,
|
||||
"prefix": "editors",
|
||||
"queryset": self.get_editors_queryset(program),
|
||||
"initial": {
|
||||
"group": program.editors_group_id,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def get_context_data(self, editors_formset=None, **kwargs):
|
||||
# TODO: use group and permission system
|
||||
if self.request.user.is_superuser:
|
||||
if editors_formset is None:
|
||||
editors_formset = self.get_editors_formset(self.object)
|
||||
kwargs["editors_formset_data"] = self.get_formset_data(
|
||||
editors_formset, {"group": self.object.editors_group_id}
|
||||
)
|
||||
|
||||
context = super().get_context_data(editors_formset=editors_formset, **kwargs)
|
||||
return context
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django_filters import rest_framework as drf_filters
|
||||
from rest_framework import status, viewsets, parsers, permissions
|
||||
from rest_framework.decorators import action
|
||||
|
@ -8,14 +9,37 @@ from filer.models.imagemodels import Image
|
|||
from . import models, forms, filters, serializers
|
||||
from .views import BaseAPIView
|
||||
|
||||
|
||||
__all__ = (
|
||||
"ImageViewSet",
|
||||
"SoundViewSet",
|
||||
"TrackROViewSet",
|
||||
"UserGroupViewSet",
|
||||
"UserSettingsViewSet",
|
||||
)
|
||||
|
||||
|
||||
class AutocompleteMixin:
|
||||
"""Based on provided filterset and serializer, add an "autocomplete" action
|
||||
to the viewset.
|
||||
|
||||
Url ``GET`` parameters:
|
||||
- `field` (many): if provided, only return provided field names
|
||||
- filterset's lookups.
|
||||
|
||||
Return a list of values if ``field`` is provided, result of `list()` otherwise.
|
||||
"""
|
||||
|
||||
@action(name="autocomplete", detail=False)
|
||||
def autocomplete(self, request):
|
||||
field = request.GET.get("field", None)
|
||||
if field:
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
values = queryset.values_list(field, flat=True).distinct()
|
||||
return Response(values[:10])
|
||||
return self.list(request)
|
||||
|
||||
|
||||
class ImageViewSet(viewsets.ModelViewSet):
|
||||
parsers = (parsers.MultiPartParser,)
|
||||
permissions = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
|
@ -55,7 +79,7 @@ class SoundViewSet(BaseAPIView, viewsets.ModelViewSet):
|
|||
return query
|
||||
|
||||
|
||||
class TrackROViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
class TrackROViewSet(AutocompleteMixin, viewsets.ReadOnlyModelViewSet):
|
||||
"""Track viewset used for auto completion."""
|
||||
|
||||
serializer_class = serializers.admin.TrackSerializer
|
||||
|
@ -64,15 +88,6 @@ class TrackROViewSet(viewsets.ReadOnlyModelViewSet):
|
|||
filterset_class = filters.TrackFilterSet
|
||||
queryset = models.Track.objects.all()
|
||||
|
||||
@action(name="autocomplete", detail=False)
|
||||
def autocomplete(self, request):
|
||||
field = request.GET.get("field", None)
|
||||
if field:
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
values = queryset.values_list(field, flat=True).distinct()
|
||||
return Response(values[:10])
|
||||
return self.list(request)
|
||||
|
||||
|
||||
class CommentViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = serializers.CommentSerializer
|
||||
|
@ -81,13 +96,19 @@ class CommentViewSet(viewsets.ModelViewSet):
|
|||
|
||||
|
||||
# --- admin
|
||||
class UserGroupViewSet(AutocompleteMixin, viewsets.ModelViewSet):
|
||||
serializer_class = serializers.auth.UserGroupSerializer
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
queryset = User.groups.through.objects.all().distinct().order_by("user__username")
|
||||
|
||||
|
||||
class UserSettingsViewSet(viewsets.ViewSet):
|
||||
"""User's settings specific to aircox.
|
||||
|
||||
Allow only to create and edit user's own settings.
|
||||
"""
|
||||
|
||||
serializer_class = serializers.admin.UserSettingsSerializer
|
||||
serializer_class = serializers.UserSettingsSerializer
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
||||
def get_serializer(self, instance=None, **kwargs):
|
||||
|
|
|
@ -3,6 +3,7 @@ import tzlocal
|
|||
|
||||
from aircox.utils import to_seconds
|
||||
|
||||
from ..conf import settings
|
||||
from .metadata import Metadata, Request
|
||||
|
||||
|
||||
|
@ -76,7 +77,7 @@ class PlaylistSource(Source):
|
|||
self.program = program
|
||||
|
||||
super().__init__(controller, id=id, **kwargs)
|
||||
self.path = os.path.join(self.station.path, f"{self.id}.m3u")
|
||||
self.path = settings.get_dir(self.station, f"{self.id}.m3u")
|
||||
|
||||
def get_sound_queryset(self):
|
||||
"""Get playlist's sounds queryset."""
|
||||
|
@ -88,6 +89,7 @@ class PlaylistSource(Source):
|
|||
|
||||
def write_playlist(self, playlist=[]):
|
||||
"""Write playlist to file."""
|
||||
playlist = playlist or self.get_playlist()
|
||||
os.makedirs(os.path.dirname(self.path), exist_ok=True)
|
||||
with open(self.path, "w") as file:
|
||||
file.write("\n".join(playlist or []))
|
||||
|
|
|
@ -95,6 +95,8 @@ class Streamer:
|
|||
data = render_to_string(
|
||||
self.template_name,
|
||||
{
|
||||
"dir": settings.get_dir(self.station),
|
||||
"log_file": settings.get_dir(self.station, "liquidsoap.log"),
|
||||
"station": self.station,
|
||||
"streamer": self,
|
||||
},
|
||||
|
|
|
@ -53,13 +53,13 @@ class SourceSerializer(MetadataSerializer):
|
|||
class PlaylistSerializer(SourceSerializer):
|
||||
program = serializers.CharField(source="program.id")
|
||||
|
||||
url_name = "admin:api:streamer-playlist-detail"
|
||||
url_name = "streamer:api:streamer-playlist-detail"
|
||||
|
||||
|
||||
class QueueSourceSerializer(SourceSerializer):
|
||||
queue = serializers.ListField(child=RequestSerializer(), source="requests")
|
||||
|
||||
url_name = "admin:api:streamer-queue-detail"
|
||||
url_name = "streamer:api:streamer-queue-detail"
|
||||
|
||||
|
||||
class StreamerSerializer(BaseSerializer):
|
||||
|
@ -69,7 +69,7 @@ class StreamerSerializer(BaseSerializer):
|
|||
playlists = serializers.ListField(child=PlaylistSerializer())
|
||||
queues = serializers.ListField(child=QueueSourceSerializer())
|
||||
|
||||
url_name = "admin:api:streamer-detail"
|
||||
url_name = "streamer:api:streamer-detail"
|
||||
|
||||
def get_url(self, obj, **kwargs):
|
||||
kwargs["pk"] = obj.station.pk
|
||||
|
|
|
@ -80,7 +80,7 @@ end
|
|||
{% block config %}
|
||||
set("server.socket", true)
|
||||
set("server.socket.path", "{{ streamer.socket_path }}")
|
||||
set("log.file.path", "{{ station.path }}/liquidsoap.log")
|
||||
set("log.file.path", "{{ log_file }}")
|
||||
{% endblock %}
|
||||
|
||||
{% block config_extras %}
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
{% extends "admin/base_site.html" %}
|
||||
{% comment %}Admin tools used to manage the streamer.{% endcomment %}
|
||||
{% extends "aircox/dashboard/base.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block init-scripts %}
|
||||
aircox.init({apiUrl: "{% url "admin:api:streamer-list" %}"},
|
||||
{config: window.StreamerApp})
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block title %}{% translate "Streamer monitor" %}{% endblock %}
|
||||
|
||||
{% block content-container %}
|
||||
{{ block.super }}
|
||||
<div id="app">
|
||||
<a-streamer api-url="{% url "admin:api:streamer-list" %}">
|
||||
<div class="container">
|
||||
<a-streamer api-url="{% url "streamer:api:streamer-list" %}">
|
||||
<template v-slot="{streamer,streamers,sources,fetchStreamers,Sound}">
|
||||
<div class="navbar toolbar">
|
||||
<div class="navbar-start">
|
||||
|
|
|
@ -1,33 +1,25 @@
|
|||
from django.contrib import admin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.urls import include, path
|
||||
|
||||
from aircox.viewsets import SoundViewSet
|
||||
|
||||
from . import viewsets
|
||||
from .views import StreamerAdminView
|
||||
from . import views, viewsets
|
||||
|
||||
admin.site.route_view(
|
||||
"tools/streamer",
|
||||
StreamerAdminView.as_view(),
|
||||
"tools-streamer",
|
||||
label=_("Streamer Monitor"),
|
||||
)
|
||||
|
||||
streamer_prefix = "streamer/(?P<station_pk>[0-9]+)/"
|
||||
__all__ = ("api", "urls")
|
||||
|
||||
|
||||
prefix = "(?P<station_pk>[0-9]+)/"
|
||||
|
||||
|
||||
router = admin.site.router
|
||||
router.register(
|
||||
streamer_prefix + "playlist",
|
||||
viewsets.PlaylistSourceViewSet,
|
||||
basename="streamer-playlist",
|
||||
)
|
||||
router.register(
|
||||
streamer_prefix + "queue",
|
||||
viewsets.QueueSourceViewSet,
|
||||
basename="streamer-queue",
|
||||
)
|
||||
router.register(prefix + "playlist", viewsets.PlaylistSourceViewSet, basename="streamer-playlist")
|
||||
router.register(prefix + "queue", viewsets.QueueSourceViewSet, basename="streamer-queue")
|
||||
router.register("streamer", viewsets.StreamerViewSet, basename="streamer")
|
||||
router.register("sound", SoundViewSet, basename="sound")
|
||||
|
||||
urls = []
|
||||
api = router.urls
|
||||
urls = [
|
||||
path("api/", include((api, "aircox_streamer"), namespace="api")),
|
||||
path("", views.StreamerView.as_view(), name="dashboard-streamer"),
|
||||
]
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from aircox.views.admin import AdminMixin
|
||||
from aircox.views.dashboard import DashboardBaseView
|
||||
from .controllers import streamers
|
||||
|
||||
|
||||
class StreamerAdminView(AdminMixin, TemplateView):
|
||||
class StreamerView(DashboardBaseView, TemplateView):
|
||||
template_name = "aircox_streamer/streamer.html"
|
||||
title = _("Streamer Monitor")
|
||||
title = _("Streamer")
|
||||
streamers = streamers
|
||||
|
||||
def dispatch(self, *args, **kwargs):
|
||||
|
|
|
@ -43,6 +43,8 @@ class ControllerViewSet(viewsets.ViewSet):
|
|||
if station_pk is None:
|
||||
station_pk = self.request.station.pk
|
||||
self.streamers.fetch()
|
||||
if station_pk is None:
|
||||
return None
|
||||
if station_pk not in self.streamers:
|
||||
raise Http404("station not found")
|
||||
return self.streamers[station_pk]
|
||||
|
@ -78,7 +80,7 @@ class StreamerViewSet(ControllerViewSet):
|
|||
def dispatch(self, request, *args, pk=None, **kwargs):
|
||||
if pk is not None:
|
||||
kwargs.setdefault("station_pk", pk)
|
||||
self.streamer = self.get_streamer(request, **kwargs)
|
||||
self.streamer = self.get_streamer(**kwargs)
|
||||
self.object = self.streamer
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"eslint-plugin-vue": "^8.0.3",
|
||||
"sass": "^1.49.9",
|
||||
"sass-loader": "^12.6.0",
|
||||
"vue-cli": "^2.9.6"
|
||||
"vue-cli": "^2.9.6",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
|
|
@ -232,6 +232,8 @@ export default {
|
|||
: fetch(url, Model.getOptions()).then(d => d.json())
|
||||
|
||||
promise = promise.then(items => {
|
||||
if(items.results)
|
||||
items = items.results
|
||||
this.items = items.filter((i) => i) || []
|
||||
this.promise = null;
|
||||
this.move(0)
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -184,9 +184,9 @@ THUMBNAIL_PROCESSORS = (
|
|||
# Enabled applications
|
||||
INSTALLED_APPS = (
|
||||
"radiocampus",
|
||||
"aircox_streamer.apps.AircoxStreamerConfig",
|
||||
"aircox.apps.AircoxConfig",
|
||||
"aircox.apps.AircoxAdminConfig",
|
||||
"aircox_streamer.apps.AircoxStreamerConfig",
|
||||
# Aircox dependencies
|
||||
"rest_framework",
|
||||
"django_filters",
|
||||
|
|
|
@ -22,16 +22,14 @@ from django.urls import include, path
|
|||
import aircox.urls
|
||||
import aircox_streamer.urls
|
||||
|
||||
urlpatterns = (
|
||||
aircox.urls.urls
|
||||
+ aircox_streamer.urls.urls
|
||||
+ [
|
||||
urlpatterns = [
|
||||
*aircox.urls.urls,
|
||||
path("streamer/", include((aircox_streamer.urls.urls, "aircox_streamer"), namespace="streamer")),
|
||||
path("admin/", admin.site.urls),
|
||||
path("accounts/", include("django.contrib.auth.urls")),
|
||||
path("ckeditor/", include("ckeditor_uploader.urls")),
|
||||
path("filer/", include("filer.urls")),
|
||||
]
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "aircox/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block head_extra %}
|
||||
{% block assets %}
|
||||
{{ block.super }}
|
||||
<style>
|
||||
:root {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
Django~=4.1
|
||||
djangorestframework~=3.13
|
||||
django-model-utils>=4.2
|
||||
Django~=5.0
|
||||
djangorestframework~=3.14
|
||||
django-model-utils>=4.3
|
||||
django-filter~=22.1
|
||||
|
||||
django-content-editor~=6.3
|
||||
django-filer~=2.2
|
||||
django-filer~=3.1
|
||||
django-honeypot~=1.0
|
||||
django-taggit~=3.0
|
||||
django-admin-sortable2~=2.1
|
||||
|
|
Loading…
Reference in New Issue
Block a user