aircox-radiocampus/aircox/models/program.py
Thomas Kairos f7a61fe6c0 Feat: packaging (#127)
- Add configuration files for packaging
- Precommit now uses ruff

Co-authored-by: bkfox <thomas bkfox net>
Reviewed-on: rc/aircox#127
2023-10-11 10:58:34 +02:00

182 lines
5.3 KiB
Python

import logging
import os
import shutil
from django.conf import settings as conf
from django.db import models
from django.db.models import F
from django.db.models.functions import Concat, Substr
from django.utils.translation import gettext_lazy as _
from aircox.conf import settings
from .page import Page, PageQuerySet
from .station import Station
logger = logging.getLogger("aircox")
__all__ = (
"Program",
"ProgramChildQuerySet",
"ProgramQuerySet",
"Stream",
)
class ProgramQuerySet(PageQuerySet):
def station(self, station):
# FIXME: reverse-lookup
return self.filter(station=station)
def active(self):
return self.filter(active=True)
class Program(Page):
"""A Program can either be a Streamed or a Scheduled program.
A Streamed program is used to generate non-stop random playlists when there
is not scheduled diffusion. In such a case, a Stream is used to describe
diffusion informations.
A Scheduled program has a schedule and is the one with a normal use case.
Renaming a Program rename the corresponding directory to matches the new
name if it does not exists.
"""
# explicit foreign key in order to avoid related name clashes
station = models.ForeignKey(Station, models.CASCADE, verbose_name=_("station"))
active = models.BooleanField(
_("active"),
default=True,
help_text=_("if not checked this program is no longer active"),
)
sync = models.BooleanField(
_("syncronise"),
default=True,
help_text=_("update later diffusions according to schedule changes"),
)
objects = ProgramQuerySet.as_manager()
detail_url_name = "program-detail"
@property
def path(self):
"""Return program's directory path."""
return os.path.join(settings.PROGRAMS_DIR, self.slug.replace("-", "_"))
@property
def abspath(self):
"""Return absolute path to program's dir."""
return os.path.join(conf.MEDIA_ROOT, self.path)
@property
def archives_path(self):
return os.path.join(self.path, settings.SOUND_ARCHIVES_SUBDIR)
@property
def excerpts_path(self):
return os.path.join(self.path, settings.SOUND_ARCHIVES_SUBDIR)
def __init__(self, *kargs, **kwargs):
super().__init__(*kargs, **kwargs)
if self.slug:
self.__initial_path = self.path
self.__initial_cover = self.cover
@classmethod
def get_from_path(cl, path):
"""Return a Program from the given path.
We assume the path has been given in a previous time by this
model (Program.path getter).
"""
if path.startswith(settings.PROGRAMS_DIR_ABS):
path = path.replace(settings.PROGRAMS_DIR_ABS, "")
while path[0] == "/":
path = path[1:]
path = path[: path.index("/")]
return cl.objects.filter(slug=path.replace("_", "-")).first()
def ensure_dir(self, subdir=None):
"""Make sur the program's dir exists (and optionally subdir).
Return True if the dir (or subdir) exists.
"""
path = os.path.join(self.abspath, subdir) if subdir else self.abspath
os.makedirs(path, exist_ok=True)
return os.path.exists(path)
class Meta:
verbose_name = _("Program")
verbose_name_plural = _("Programs")
def __str__(self):
return self.title
def save(self, *kargs, **kwargs):
from .sound import Sound
super().save(*kargs, **kwargs)
# TODO: move in signals
path_ = getattr(self, "__initial_path", None)
abspath = path_ and os.path.join(conf.MEDIA_ROOT, path_)
if path_ is not None and path_ != self.path and os.path.exists(abspath) and not os.path.exists(self.abspath):
logger.info(
"program #%s's dir changed to %s - update it.",
self.id,
self.title,
)
shutil.move(abspath, self.abspath)
Sound.objects.filter(path__startswith=path_).update(file=Concat("file", Substr(F("file"), len(path_))))
class ProgramChildQuerySet(PageQuerySet):
def station(self, station=None, id=None):
return (
self.filter(parent__program__station=station)
if id is None
else self.filter(parent__program__station__id=id)
)
def program(self, program=None, id=None):
return self.parent(program, id)
class Stream(models.Model):
"""When there are no program scheduled, it is possible to play sounds in
order to avoid blanks. A Stream is a Program that plays this role, and
whose linked to a Stream.
All sounds that are marked as good and that are under the related
program's archive dir are elligible for the sound's selection.
"""
program = models.ForeignKey(
Program,
models.CASCADE,
verbose_name=_("related program"),
)
delay = models.TimeField(
_("delay"),
blank=True,
null=True,
help_text=_("minimal delay between two sound plays"),
)
begin = models.TimeField(
_("begin"),
blank=True,
null=True,
help_text=_("used to define a time range this stream is " "played"),
)
end = models.TimeField(
_("end"),
blank=True,
null=True,
help_text=_("used to define a time range this stream is " "played"),
)