documentation
This commit is contained in:
parent
5109392db7
commit
49c4939708
|
@ -312,10 +312,10 @@ class Dealer (Source):
|
||||||
source.skip()
|
source.skip()
|
||||||
self.controller.log(
|
self.controller.log(
|
||||||
source = self.id,
|
source = self.id,
|
||||||
diffusion = diff,
|
|
||||||
date = now,
|
date = now,
|
||||||
comment = 'trigger the scheduled diffusion to liquidsoap; '
|
comment = 'trigger the scheduled diffusion to liquidsoap; '
|
||||||
'skip all other streams',
|
'skip all other streams',
|
||||||
|
related_object = diff,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -401,9 +401,9 @@ class Controller:
|
||||||
|
|
||||||
self.log(
|
self.log(
|
||||||
source = source.id,
|
source = source.id,
|
||||||
sound = models.Sound.objects.get(path = on_air),
|
|
||||||
date = tz.make_aware(tz.datetime.now()),
|
date = tz.make_aware(tz.datetime.now()),
|
||||||
comment = 'sound has changed'
|
comment = 'sound has changed',
|
||||||
|
related_object = models.Sound.objects.get(path = on_air),
|
||||||
)
|
)
|
||||||
|
|
||||||
def monitor (self):
|
def monitor (self):
|
||||||
|
|
|
@ -1,22 +1,30 @@
|
||||||
This application defines all base models and basic control of them. We have:
|
This application defines all base models and basic control of them. We have:
|
||||||
* **Nameable**: generic class used in any class needing to be named. Includes some utility functions;
|
* **Nameable**: generic class used in any class needing to be named. Includes some utility functions;
|
||||||
* **Program**: the program itself;
|
* **Program**: the program itself;
|
||||||
* **Episode**: occurence of a program;
|
* **Diffusion**: occurrence of a program planified in the timetable. For rerun, informations are bound to the initial diffusion;
|
||||||
* **Diffusion**: diffusion of an episode in the timetable, linked to an episode (an episode can have multiple diffusions);
|
|
||||||
* **Schedule**: describes diffusions frequencies for each program;
|
* **Schedule**: describes diffusions frequencies for each program;
|
||||||
* **Track**: track informations in a playlist of an episode;
|
* **Track**: track informations in a playlist of a diffusion;
|
||||||
* **Sound**: information about a sound that can be used for podcast or rerun;
|
* **Sound**: information about a sound that can be used for podcast or rerun;
|
||||||
|
* **Log**: logs
|
||||||
|
|
||||||
# Program
|
|
||||||
Each program has a directory in **AIRCOX_PROGRAMS_DATA**; For each, subdir:
|
# Architecture
|
||||||
|
A Station is basically an object that represent a radio station. On each station, we use the Program object, that is declined in two different type:
|
||||||
|
* **Scheduled**: the diffusion is based on a timetable and planified through one Schedule or more; Diffusion object represent the occurrence of these programs;
|
||||||
|
* **Streamed**: the diffusion is based on random playlist, used to fill gaps between the programs;
|
||||||
|
|
||||||
|
Each program has a directory in **AIRCOX_PROGRAMS_DIR**; For each, subdir:
|
||||||
* **archives**: complete episode record, can be used for diffusions or as a podcast
|
* **archives**: complete episode record, can be used for diffusions or as a podcast
|
||||||
* **excerpts**: excerpt of an episode, or other elements, can be used as a podcast
|
* **excerpts**: excerpt of an episode, or other elements, can be used as a podcast
|
||||||
|
|
||||||
Each program has a schedule, defined through multiple schedule elements. This schedule can calculate the next dates of diffusion, if is a rerun (of wich diffusion), etc.
|
|
||||||
|
|
||||||
Basically, for each program created, we can define some options, a directory in **AIRCOX_PROGRAMS_DATA**, where subfolders defines some informations about a file.
|
# manage.py's commands
|
||||||
|
* **diffusions_monitor**: update/create, check and clean diffusions; When a diffusion is created, its type is unconfirmed, and requires a manual approval to be on the timetable.
|
||||||
|
* **sound_monitor**: check for existing and missing sounds files in programs directories and synchronize the database. Can also check for the quality of file and synchronize the database according to them.
|
||||||
|
* **sound_quality_check**: check for the quality of the file (don't update database)
|
||||||
|
|
||||||
|
|
||||||
# Notes
|
# External Requirements
|
||||||
We don't give any view on what should be now, because it is up to the stream generator to give info about what is running.
|
* Sox (and soxi): sound file monitor and quality check
|
||||||
|
* Requirements.txt for python's dependecies
|
||||||
|
|
||||||
|
|
2
aircox_programs/Requirements.txt
Normal file
2
aircox_programs/Requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
django-taggit >= 0.17.1
|
||||||
|
|
|
@ -4,7 +4,6 @@ from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from suit.admin import SortableTabularInline, SortableModelAdmin
|
|
||||||
|
|
||||||
from aircox_programs.forms import *
|
from aircox_programs.forms import *
|
||||||
from aircox_programs.models import *
|
from aircox_programs.models import *
|
||||||
|
@ -13,7 +12,6 @@ from aircox_programs.models import *
|
||||||
#
|
#
|
||||||
# Inlines
|
# Inlines
|
||||||
#
|
#
|
||||||
# TODO: inherits from the corresponding admin view
|
|
||||||
class SoundInline (admin.TabularInline):
|
class SoundInline (admin.TabularInline):
|
||||||
model = Sound
|
model = Sound
|
||||||
|
|
||||||
|
@ -28,12 +26,13 @@ class StreamInline (admin.TabularInline):
|
||||||
extra = 1
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
class TrackInline (SortableTabularInline):
|
# from suit.admin import SortableTabularInline, SortableModelAdmin
|
||||||
fields = ['artist', 'name', 'tags', 'position']
|
#class TrackInline (SortableTabularInline):
|
||||||
form = TrackForm
|
# fields = ['artist', 'name', 'tags', 'position']
|
||||||
model = Track
|
# form = TrackForm
|
||||||
sortable = 'position'
|
# model = Track
|
||||||
extra = 10
|
# sortable = 'position'
|
||||||
|
# extra = 10
|
||||||
|
|
||||||
|
|
||||||
class NameableAdmin (admin.ModelAdmin):
|
class NameableAdmin (admin.ModelAdmin):
|
||||||
|
@ -86,22 +85,20 @@ class DiffusionAdmin (admin.ModelAdmin):
|
||||||
sounds = [ str(s) for s in obj.get_archives()]
|
sounds = [ str(s) for s in obj.get_archives()]
|
||||||
return ', '.join(sounds) if sounds else ''
|
return ', '.join(sounds) if sounds else ''
|
||||||
|
|
||||||
list_display = ('id', 'type', 'date', 'archives', 'program', 'initial')
|
list_display = ('id', 'type', 'date', 'program', 'initial', 'archives')
|
||||||
list_filter = ('type', 'date', 'program')
|
list_filter = ('type', 'date', 'program')
|
||||||
list_editable = ('type', 'date')
|
list_editable = ('type', 'date')
|
||||||
|
|
||||||
fields = ['type', 'date', 'initial', 'sounds', 'program']
|
fields = ['type', 'date', 'initial', 'program', 'sounds']
|
||||||
readonly_fields = ('duration',)
|
|
||||||
|
|
||||||
def get_form(self, request, obj=None, **kwargs):
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
if obj:
|
if request.user.has_perm('aircox_program.programming'):
|
||||||
if obj.date < tz.make_aware(tz.datetime.now()):
|
self.readonly_fields = []
|
||||||
self.readonly_fields = list(self.fields)
|
else:
|
||||||
self.readonly_fields.remove('type')
|
self.readonly_fields = ['program', 'date', 'duration']
|
||||||
elif obj.initial:
|
|
||||||
self.readonly_fields = ['program', 'sounds']
|
if obj.initial:
|
||||||
else:
|
self.readonly_fields += ['program', 'sounds']
|
||||||
self.readonly_fields = []
|
|
||||||
return super().get_form(request, obj, **kwargs)
|
return super().get_form(request, obj, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
|
@ -113,7 +110,11 @@ class DiffusionAdmin (admin.ModelAdmin):
|
||||||
return qs.exclude(type = Diffusion.Type['unconfirmed'])
|
return qs.exclude(type = Diffusion.Type['unconfirmed'])
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Log)
|
@admin.register(Log)
|
||||||
|
class LogAdmin (admin.ModelAdmin):
|
||||||
|
list_display = ['id', 'date', 'source', 'comment', 'related_object']
|
||||||
|
list_filter = ['date', 'related_type']
|
||||||
|
|
||||||
admin.site.register(Track)
|
admin.site.register(Track)
|
||||||
admin.site.register(Schedule)
|
admin.site.register(Schedule)
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ from django.template.defaultfilters import slugify
|
||||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
|
@ -33,9 +35,9 @@ class Nameable (models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def slug (self):
|
def slug (self):
|
||||||
return self.get_slug_name()
|
"""
|
||||||
|
Slug based on the name. We replace '-' by '_'
|
||||||
def get_slug_name (self):
|
"""
|
||||||
return slugify(self.name).replace('-', '_')
|
return slugify(self.name).replace('-', '_')
|
||||||
|
|
||||||
def __str__ (self):
|
def __str__ (self):
|
||||||
|
@ -450,8 +452,11 @@ class Program (Nameable):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path (self):
|
def path (self):
|
||||||
|
"""
|
||||||
|
Return the path to the programs directory
|
||||||
|
"""
|
||||||
return os.path.join(settings.AIRCOX_PROGRAMS_DIR,
|
return os.path.join(settings.AIRCOX_PROGRAMS_DIR,
|
||||||
self.get_slug_name() + '_' + str(self.id) )
|
self.slug + '_' + str(self.id) )
|
||||||
|
|
||||||
def ensure_dir (self, subdir = None):
|
def ensure_dir (self, subdir = None):
|
||||||
"""
|
"""
|
||||||
|
@ -498,10 +503,9 @@ class Diffusion (models.Model):
|
||||||
- stop: the diffusion has been manually stopped
|
- stop: the diffusion has been manually stopped
|
||||||
"""
|
"""
|
||||||
Type = {
|
Type = {
|
||||||
'default': 0x00, # confirmed diffusion case FIXME
|
'default': 0x00, # diffusion is planified
|
||||||
'unconfirmed': 0x01, # scheduled by the generator but not confirmed for diffusion
|
'unconfirmed': 0x01, # scheduled by the generator but not confirmed for diffusion
|
||||||
'cancel': 0x02, # cancellation happened; used to inform users
|
'cancel': 0x02, # diffusion canceled
|
||||||
# 'restart': 0x03, # manual restart; used to remix/give up antenna
|
|
||||||
}
|
}
|
||||||
for key, value in Type.items():
|
for key, value in Type.items():
|
||||||
ugettext_lazy(key)
|
ugettext_lazy(key)
|
||||||
|
@ -592,6 +596,10 @@ class Diffusion (models.Model):
|
||||||
verbose_name = _('Diffusion')
|
verbose_name = _('Diffusion')
|
||||||
verbose_name_plural = _('Diffusions')
|
verbose_name_plural = _('Diffusions')
|
||||||
|
|
||||||
|
permissions = (
|
||||||
|
('programming', _('edit the diffusion\'s planification')),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Log (models.Model):
|
class Log (models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -603,11 +611,6 @@ class Log (models.Model):
|
||||||
help_text = 'source information',
|
help_text = 'source information',
|
||||||
blank = True, null = True,
|
blank = True, null = True,
|
||||||
)
|
)
|
||||||
diffusion = models.ForeignKey(
|
|
||||||
'Diffusion',
|
|
||||||
help_text = _('related diffusion'),
|
|
||||||
blank = True, null = True,
|
|
||||||
)
|
|
||||||
sound = models.ForeignKey(
|
sound = models.ForeignKey(
|
||||||
'Sound',
|
'Sound',
|
||||||
help_text = _('played sound'),
|
help_text = _('played sound'),
|
||||||
|
@ -620,6 +623,16 @@ class Log (models.Model):
|
||||||
max_length = 512,
|
max_length = 512,
|
||||||
blank = True, null = True,
|
blank = True, null = True,
|
||||||
)
|
)
|
||||||
|
related_type = models.ForeignKey(
|
||||||
|
ContentType,
|
||||||
|
blank = True, null = True,
|
||||||
|
)
|
||||||
|
related_id = models.PositiveIntegerField(
|
||||||
|
blank = True, null = True,
|
||||||
|
)
|
||||||
|
related_object = GenericForeignKey(
|
||||||
|
'related_type', 'related_id',
|
||||||
|
)
|
||||||
|
|
||||||
def print (self):
|
def print (self):
|
||||||
print(str(self), ':', self.comment or '')
|
print(str(self), ':', self.comment or '')
|
||||||
|
|
|
@ -33,5 +33,3 @@ ensure('AIRCOX_SOUND_FILE_EXT',
|
||||||
# Stream for the scheduled diffusions
|
# Stream for the scheduled diffusions
|
||||||
ensure('AIRCOX_SCHEDULED_STREAM', 0)
|
ensure('AIRCOX_SCHEDULED_STREAM', 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,6 @@ class Program (RelatedPost):
|
||||||
model = programs.Program
|
model = programs.Program
|
||||||
bind_mapping = True
|
bind_mapping = True
|
||||||
mapping = {
|
mapping = {
|
||||||
'title': 'name',
|
|
||||||
'content': 'description',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Episode (RelatedPost):
|
class Episode (RelatedPost):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user