forked from rc/aircox
work on website + page becomes concrete
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,11 +1,17 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .page import Page
|
||||
from .page import Page, PageQuerySet
|
||||
from .program import Program, InProgramQuerySet
|
||||
|
||||
|
||||
class ArticleQuerySet(InProgramQuerySet, PageQuerySet):
|
||||
pass
|
||||
|
||||
|
||||
class Article(Page):
|
||||
detail_url_name = 'article-detail'
|
||||
|
||||
program = models.ForeignKey(
|
||||
Program, models.SET_NULL,
|
||||
verbose_name=_('program'), blank=True, null=True,
|
||||
@ -17,7 +23,7 @@ class Article(Page):
|
||||
'instead of a blog article'),
|
||||
)
|
||||
|
||||
objects = InProgramQuerySet.as_manager()
|
||||
objects = ArticleQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Article')
|
||||
|
@ -1,9 +1,7 @@
|
||||
import datetime
|
||||
from enum import IntEnum
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import F, Q
|
||||
from django.db.models.functions import Concat, Substr
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone as tz
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.functional import cached_property
|
||||
@ -64,7 +62,7 @@ class DiffusionQuerySet(BaseRerunQuerySet):
|
||||
|
||||
def on_air(self):
|
||||
""" On air diffusions """
|
||||
return self.filter(type=Diffusion.Type.on_air)
|
||||
return self.filter(type=Diffusion.TYPE_ON_AIR)
|
||||
|
||||
def now(self, now=None, order=True):
|
||||
""" Diffusions occuring now """
|
||||
@ -132,20 +130,20 @@ class Diffusion(BaseRerun):
|
||||
"""
|
||||
objects = DiffusionQuerySet.as_manager()
|
||||
|
||||
class Type(IntEnum):
|
||||
on_air = 0x00
|
||||
unconfirmed = 0x01
|
||||
cancel = 0x02
|
||||
TYPE_ON_AIR = 0x00
|
||||
TYPE_UNCONFIRMED = 0x01
|
||||
TYPE_CANCEL = 0x02
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_ON_AIR, _('on air')),
|
||||
(TYPE_UNCONFIRMED, _('not confirmed')),
|
||||
(TYPE_CANCEL, _('cancelled')),
|
||||
)
|
||||
|
||||
episode = models.ForeignKey(
|
||||
Episode, models.CASCADE,
|
||||
verbose_name=_('episode'),
|
||||
Episode, models.CASCADE, verbose_name=_('episode'),
|
||||
)
|
||||
type = models.SmallIntegerField(
|
||||
verbose_name=_('type'),
|
||||
default=Type.on_air,
|
||||
choices=[(int(y), _(x.replace('_', ' ')))
|
||||
for x, y in Type.__members__.items()],
|
||||
verbose_name=_('type'), default=TYPE_ON_AIR, choices=TYPE_CHOICES,
|
||||
)
|
||||
start = models.DateTimeField(_('start'))
|
||||
end = models.DateTimeField(_('end'))
|
||||
@ -222,7 +220,7 @@ class Diffusion(BaseRerun):
|
||||
# TODO: property?
|
||||
def is_live(self):
|
||||
""" True if Diffusion is live (False if there are sounds files). """
|
||||
return self.type == self.Type.on_air and \
|
||||
return self.type == self.TYPE_ON_AIR and \
|
||||
not self.episode.sound_set.archive().count()
|
||||
|
||||
def get_playlist(self, **types):
|
||||
@ -232,7 +230,7 @@ class Diffusion(BaseRerun):
|
||||
"""
|
||||
from .sound import Sound
|
||||
return list(self.get_sounds(**types)
|
||||
.filter(path__isnull=False, type=Sound.Type.archive)
|
||||
.filter(path__isnull=False, type=Sound.TYPE_ARCHIVE)
|
||||
.values_list('path', flat=True))
|
||||
|
||||
def get_sounds(self, **types):
|
||||
|
@ -1,6 +1,4 @@
|
||||
from collections import deque
|
||||
import datetime
|
||||
from enum import IntEnum
|
||||
import logging
|
||||
import os
|
||||
|
||||
@ -9,7 +7,7 @@ from django.utils import timezone as tz
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
from aircox import settings, utils
|
||||
from aircox import settings
|
||||
from .episode import Diffusion
|
||||
from .sound import Sound, Track
|
||||
from .station import Station
|
||||
@ -35,10 +33,10 @@ class LogQuerySet(models.QuerySet):
|
||||
self.filter(date__date__gte=date)
|
||||
|
||||
def on_air(self):
|
||||
return self.filter(type=Log.Type.on_air)
|
||||
return self.filter(type=Log.TYPE_ON_AIR)
|
||||
|
||||
def start(self):
|
||||
return self.filter(type=Log.Type.start)
|
||||
return self.filter(type=Log.TYPE_START)
|
||||
|
||||
def with_diff(self, with_it=True):
|
||||
return self.filter(diffusion__isnull=not with_it)
|
||||
@ -163,43 +161,33 @@ class Log(models.Model):
|
||||
This only remember what has been played on the outputs, not on each
|
||||
source; Source designate here which source is responsible of that.
|
||||
"""
|
||||
class Type(IntEnum):
|
||||
stop = 0x00
|
||||
"""
|
||||
Source has been stopped, e.g. manually
|
||||
"""
|
||||
# Rule: \/ diffusion != null \/ sound != null
|
||||
start = 0x01
|
||||
""" Diffusion or sound has been request to be played. """
|
||||
cancel = 0x02
|
||||
""" Diffusion has been canceled. """
|
||||
# Rule: \/ sound != null /\ track == null
|
||||
# \/ sound == null /\ track != null
|
||||
# \/ sound == null /\ track == null /\ comment = sound_path
|
||||
on_air = 0x03
|
||||
"""
|
||||
The sound or diffusion has been detected occurring on air. Can
|
||||
also designate live diffusion, although Liquidsoap did not play
|
||||
them since they don't have an attached sound archive.
|
||||
"""
|
||||
other = 0x04
|
||||
""" Other log """
|
||||
|
||||
TYPE_STOP = 0x00
|
||||
""" Source has been stopped, e.g. manually """
|
||||
# Rule: \/ diffusion != null \/ sound != null
|
||||
TYPE_START = 0x01
|
||||
""" Diffusion or sound has been request to be played. """
|
||||
TYPE_CANCEL = 0x02
|
||||
""" Diffusion has been canceled. """
|
||||
# Rule: \/ sound != null /\ track == null
|
||||
# \/ sound == null /\ track != null
|
||||
# \/ sound == null /\ track == null /\ comment = sound_path
|
||||
TYPE_ON_AIR = 0x03
|
||||
""" Sound or diffusion occured on air """
|
||||
TYPE_OTHER = 0x04
|
||||
""" Other log """
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_STOP, _('stop')), (TYPE_START, _('start')),
|
||||
(TYPE_CANCEL, _('cancelled')), (TYPE_ON_AIR, _('on air')),
|
||||
(TYPE_OTHER, _('other'))
|
||||
)
|
||||
|
||||
station = models.ForeignKey(
|
||||
Station, models.CASCADE,
|
||||
verbose_name=_('station'),
|
||||
help_text=_('related station'),
|
||||
)
|
||||
type = models.SmallIntegerField(
|
||||
choices=[(int(y), _(x.replace('_', ' ')))
|
||||
for x, y in Type.__members__.items()],
|
||||
blank=True, null=True,
|
||||
verbose_name=_('type'),
|
||||
)
|
||||
date = models.DateTimeField(
|
||||
default=tz.now, db_index=True,
|
||||
verbose_name=_('date'),
|
||||
verbose_name=_('station'), help_text=_('related station'),
|
||||
)
|
||||
type = models.SmallIntegerField(_('type'), choices=TYPE_CHOICES)
|
||||
date = models.DateTimeField(_('date'), default=tz.now, db_index=True)
|
||||
source = models.CharField(
|
||||
# we use a CharField to avoid loosing logs information if the
|
||||
# source is removed
|
||||
|
@ -38,28 +38,30 @@ class Category(models.Model):
|
||||
|
||||
class PageQuerySet(InheritanceQuerySet):
|
||||
def draft(self):
|
||||
return self.filter(status=Page.STATUS.draft)
|
||||
return self.filter(status=Page.STATUS_DRAFT)
|
||||
|
||||
def published(self):
|
||||
return self.filter(status=Page.STATUS.published)
|
||||
return self.filter(status=Page.STATUS_PUBLISHED)
|
||||
|
||||
def trash(self):
|
||||
return self.filter(status=Page.STATUS.trash)
|
||||
return self.filter(status=Page.STATUS_TRASH)
|
||||
|
||||
|
||||
class Page(models.Model):
|
||||
""" Base class for publishable content """
|
||||
class STATUS(IntEnum):
|
||||
draft = 0x00
|
||||
published = 0x10
|
||||
trash = 0x20
|
||||
STATUS_DRAFT = 0x00
|
||||
STATUS_PUBLISHED = 0x10
|
||||
STATUS_TRASH = 0x20
|
||||
STATUS_CHOICES = (
|
||||
(STATUS_DRAFT, _('draft')),
|
||||
(STATUS_PUBLISHED, _('published')),
|
||||
(STATUS_TRASH, _('trash')),
|
||||
)
|
||||
|
||||
title = models.CharField(max_length=128)
|
||||
slug = models.SlugField(_('slug'), blank=True, unique=True)
|
||||
status = models.PositiveSmallIntegerField(
|
||||
_('status'),
|
||||
default=STATUS.draft,
|
||||
choices=[(int(y), _(x)) for x, y in STATUS.__members__.items()],
|
||||
_('status'), default=STATUS_DRAFT, choices=STATUS_CHOICES,
|
||||
)
|
||||
category = models.ForeignKey(
|
||||
Category, models.SET_NULL,
|
||||
@ -84,8 +86,6 @@ class Page(models.Model):
|
||||
|
||||
detail_url_name = None
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __str__(self):
|
||||
return '{}: {}'.format(self._meta.verbose_name,
|
||||
@ -104,15 +104,15 @@ class Page(models.Model):
|
||||
|
||||
@property
|
||||
def is_draft(self):
|
||||
return self.status == self.STATUS.draft
|
||||
return self.status == self.STATUS_DRAFT
|
||||
|
||||
@property
|
||||
def is_published(self):
|
||||
return self.status == self.STATUS.published
|
||||
return self.status == self.STATUS_PUBLISHED
|
||||
|
||||
@property
|
||||
def is_trash(self):
|
||||
return self.status == self.STATUS.trash
|
||||
return self.status == self.STATUS_TRASH
|
||||
|
||||
@cached_property
|
||||
def headline(self):
|
||||
@ -132,6 +132,16 @@ class Page(models.Model):
|
||||
return cls(**cls.get_init_kwargs_from(page, **kwargs))
|
||||
|
||||
|
||||
class Comment(models.Model):
|
||||
page = models.ForeignKey(
|
||||
Page, models.CASCADE, verbose_name=_('related page'),
|
||||
)
|
||||
nickname = models.CharField(_('nickname'), max_length=32)
|
||||
email = models.EmailField(_('email'), max_length=32)
|
||||
date = models.DateTimeField(auto_now_add=True)
|
||||
content = models.TextField(_('content'), max_length=1024)
|
||||
|
||||
|
||||
class NavItem(models.Model):
|
||||
""" Navigation menu items """
|
||||
station = models.ForeignKey(
|
||||
|
@ -45,6 +45,11 @@ class Program(Page):
|
||||
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
|
||||
page = models.OneToOneField(
|
||||
Page, models.CASCADE,
|
||||
parent_link=True, related_name='program_page'
|
||||
)
|
||||
station = models.ForeignKey(
|
||||
Station,
|
||||
verbose_name=_('station'),
|
||||
@ -478,7 +483,7 @@ class Schedule(BaseRerun):
|
||||
initial = diffusions[initial]
|
||||
|
||||
diffusions[date] = Diffusion(
|
||||
episode=episode, type=Diffusion.Type.on_air,
|
||||
episode=episode, type=Diffusion.TYPE_ON_AIR,
|
||||
initial=initial, start=date, end=date+duration
|
||||
)
|
||||
return episodes.values(), diffusions.values()
|
||||
|
@ -36,7 +36,7 @@ class SoundQuerySet(models.QuerySet):
|
||||
|
||||
def archive(self):
|
||||
""" Return sounds that are archives """
|
||||
return self.filter(type=Sound.Type.archive)
|
||||
return self.filter(type=Sound.TYPE_ARCHIVE)
|
||||
|
||||
def paths(self, archive=True, order_by=True):
|
||||
"""
|
||||
@ -55,11 +55,14 @@ class Sound(models.Model):
|
||||
A Sound is the representation of a sound file that can be either an excerpt
|
||||
or a complete archive of the related diffusion.
|
||||
"""
|
||||
class Type(IntEnum):
|
||||
other = 0x00,
|
||||
archive = 0x01,
|
||||
excerpt = 0x02,
|
||||
removed = 0x03,
|
||||
TYPE_OTHER = 0x00
|
||||
TYPE_ARCHIVE = 0x01
|
||||
TYPE_EXCERPT = 0x02
|
||||
TYPE_REMOVED = 0x03
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_OTHER, _('other')), (TYPE_ARCHIVE, _('archive')),
|
||||
(TYPE_EXCERPT, _('excerpt')), (TYPE_REMOVED, _('removed'))
|
||||
)
|
||||
|
||||
name = models.CharField(_('name'), max_length=64)
|
||||
program = models.ForeignKey(
|
||||
@ -72,11 +75,7 @@ class Sound(models.Model):
|
||||
Episode, models.SET_NULL, blank=True, null=True,
|
||||
verbose_name=_('episode'),
|
||||
)
|
||||
type = models.SmallIntegerField(
|
||||
verbose_name=_('type'),
|
||||
choices=[(int(y), _(x)) for x, y in Type.__members__.items()],
|
||||
blank=True, null=True
|
||||
)
|
||||
type = models.SmallIntegerField(_('type'), choices=TYPE_CHOICES)
|
||||
# FIXME: url() does not use the same directory than here
|
||||
# should we use FileField for more reliability?
|
||||
path = models.FilePathField(
|
||||
@ -196,21 +195,21 @@ class Sound(models.Model):
|
||||
"""
|
||||
|
||||
if not self.file_exists():
|
||||
if self.type == self.Type.removed:
|
||||
if self.type == self.TYPE_REMOVED:
|
||||
return
|
||||
logger.info('sound %s: has been removed', self.path)
|
||||
self.type = self.Type.removed
|
||||
self.type = self.TYPE_REMOVED
|
||||
|
||||
return True
|
||||
|
||||
# not anymore removed
|
||||
changed = False
|
||||
|
||||
if self.type == self.Type.removed and self.program:
|
||||
if self.type == self.TYPE_REMOVED and self.program:
|
||||
changed = True
|
||||
self.type = self.Type.archive \
|
||||
self.type = self.TYPE_ARCHIVE \
|
||||
if self.path.startswith(self.program.archives_path) else \
|
||||
self.Type.excerpt
|
||||
self.TYPE_EXCERPT
|
||||
|
||||
# check mtime -> reset quality if changed (assume file changed)
|
||||
mtime = self.get_mtime()
|
||||
|
@ -1,4 +1,3 @@
|
||||
from enum import IntEnum
|
||||
import os
|
||||
|
||||
from django.db import models
|
||||
@ -91,36 +90,32 @@ class Port(models.Model):
|
||||
Some port types may be not available depending on the
|
||||
direction of the port.
|
||||
"""
|
||||
class Direction(IntEnum):
|
||||
input = 0x00
|
||||
output = 0x01
|
||||
DIRECTION_INPUT = 0x00
|
||||
DIRECTION_OUTPUT = 0x01
|
||||
DIRECTION_CHOICES = ((DIRECTION_INPUT, _('input')),
|
||||
(DIRECTION_OUTPUT, _('output')))
|
||||
|
||||
class Type(IntEnum):
|
||||
jack = 0x00
|
||||
alsa = 0x01
|
||||
pulseaudio = 0x02
|
||||
icecast = 0x03
|
||||
http = 0x04
|
||||
https = 0x05
|
||||
file = 0x06
|
||||
TYPE_JACK = 0x00
|
||||
TYPE_ALSA = 0x01
|
||||
TYPE_PULSEAUDIO = 0x02
|
||||
TYPE_ICECAST = 0x03
|
||||
TYPE_HTTP = 0x04
|
||||
TYPE_HTTPS = 0x05
|
||||
TYPE_FILE = 0x06
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_JACK, 'jack'), (TYPE_ALSA, 'alsa'),
|
||||
(TYPE_PULSEAUDIO, 'pulseaudio'), (TYPE_ICECAST, 'icecast'),
|
||||
(TYPE_HTTP, 'http'), (TYPE_HTTPS, 'https'),
|
||||
(TYPE_FILE, _('file'))
|
||||
)
|
||||
|
||||
station = models.ForeignKey(
|
||||
Station,
|
||||
verbose_name=_('station'),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
Station, models.CASCADE, verbose_name=_('station'))
|
||||
direction = models.SmallIntegerField(
|
||||
_('direction'),
|
||||
choices=[(int(y), _(x)) for x, y in Direction.__members__.items()],
|
||||
)
|
||||
type = models.SmallIntegerField(
|
||||
_('type'),
|
||||
# we don't translate the names since it is project names.
|
||||
choices=[(int(y), x) for x, y in Type.__members__.items()],
|
||||
)
|
||||
_('direction'), choices=DIRECTION_CHOICES)
|
||||
type = models.SmallIntegerField(_('type'), choices=TYPE_CHOICES)
|
||||
active = models.BooleanField(
|
||||
_('active'),
|
||||
default=True,
|
||||
_('active'), default=True,
|
||||
help_text=_('this port is active')
|
||||
)
|
||||
settings = models.TextField(
|
||||
@ -136,13 +131,13 @@ class Port(models.Model):
|
||||
Return True if the type is available for the given direction.
|
||||
"""
|
||||
|
||||
if self.direction == self.Direction.input:
|
||||
if self.direction == self.DIRECTION_INPUT:
|
||||
return self.type not in (
|
||||
self.Type.icecast, self.Type.file
|
||||
self.TYPE_ICECAST, self.TYPE_FILE
|
||||
)
|
||||
|
||||
return self.type not in (
|
||||
self.Type.http, self.Type.https
|
||||
self.TYPE_HTTP, self.TYPE_HTTPS
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
Reference in New Issue
Block a user