work on website + page becomes concrete

This commit is contained in:
bkfox
2019-09-05 14:12:12 +02:00
parent 595af5a69d
commit c46f006379
88 changed files with 476 additions and 9823 deletions

View File

@ -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')

View File

@ -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):

View File

@ -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

View File

@ -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(

View File

@ -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()

View File

@ -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()

View File

@ -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):