diff --git a/aircox_liquidsoap/utils.py b/aircox_liquidsoap/utils.py index 6d4f19f..cf059a3 100644 --- a/aircox_liquidsoap/utils.py +++ b/aircox_liquidsoap/utils.py @@ -312,10 +312,10 @@ class Dealer (Source): source.skip() self.controller.log( source = self.id, - diffusion = diff, date = now, comment = 'trigger the scheduled diffusion to liquidsoap; ' 'skip all other streams', + related_object = diff, ) @@ -401,9 +401,9 @@ class Controller: self.log( source = source.id, - sound = models.Sound.objects.get(path = on_air), 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): diff --git a/aircox_programs/README.md b/aircox_programs/README.md index 9bdccf8..c4f3043 100644 --- a/aircox_programs/README.md +++ b/aircox_programs/README.md @@ -1,22 +1,30 @@ 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; * **Program**: the program itself; -* **Episode**: occurence of a program; -* **Diffusion**: diffusion of an episode in the timetable, linked to an episode (an episode can have multiple diffusions); +* **Diffusion**: occurrence of a program planified in the timetable. For rerun, informations are bound to the initial diffusion; * **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; +* **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 * **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 -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. +# External Requirements +* Sox (and soxi): sound file monitor and quality check +* Requirements.txt for python's dependecies diff --git a/aircox_programs/Requirements.txt b/aircox_programs/Requirements.txt new file mode 100644 index 0000000..02a0ab3 --- /dev/null +++ b/aircox_programs/Requirements.txt @@ -0,0 +1,2 @@ +django-taggit >= 0.17.1 + diff --git a/aircox_programs/admin.py b/aircox_programs/admin.py index 2462788..44fab03 100755 --- a/aircox_programs/admin.py +++ b/aircox_programs/admin.py @@ -4,7 +4,6 @@ from django import forms from django.contrib import admin from django.db import models -from suit.admin import SortableTabularInline, SortableModelAdmin from aircox_programs.forms import * from aircox_programs.models import * @@ -13,7 +12,6 @@ from aircox_programs.models import * # # Inlines # -# TODO: inherits from the corresponding admin view class SoundInline (admin.TabularInline): model = Sound @@ -28,12 +26,13 @@ class StreamInline (admin.TabularInline): extra = 1 -class TrackInline (SortableTabularInline): - fields = ['artist', 'name', 'tags', 'position'] - form = TrackForm - model = Track - sortable = 'position' - extra = 10 +# from suit.admin import SortableTabularInline, SortableModelAdmin +#class TrackInline (SortableTabularInline): +# fields = ['artist', 'name', 'tags', 'position'] +# form = TrackForm +# model = Track +# sortable = 'position' +# extra = 10 class NameableAdmin (admin.ModelAdmin): @@ -86,22 +85,20 @@ class DiffusionAdmin (admin.ModelAdmin): sounds = [ str(s) for s in obj.get_archives()] 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_editable = ('type', 'date') - fields = ['type', 'date', 'initial', 'sounds', 'program'] - readonly_fields = ('duration',) + fields = ['type', 'date', 'initial', 'program', 'sounds'] def get_form(self, request, obj=None, **kwargs): - if obj: - if obj.date < tz.make_aware(tz.datetime.now()): - self.readonly_fields = list(self.fields) - self.readonly_fields.remove('type') - elif obj.initial: - self.readonly_fields = ['program', 'sounds'] - else: - self.readonly_fields = [] + if request.user.has_perm('aircox_program.programming'): + self.readonly_fields = [] + else: + self.readonly_fields = ['program', 'date', 'duration'] + + if obj.initial: + self.readonly_fields += ['program', 'sounds'] return super().get_form(request, obj, **kwargs) def get_queryset(self, request): @@ -113,7 +110,11 @@ class DiffusionAdmin (admin.ModelAdmin): 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(Schedule) diff --git a/aircox_programs/models.py b/aircox_programs/models.py index c87244f..137c23c 100755 --- a/aircox_programs/models.py +++ b/aircox_programs/models.py @@ -5,6 +5,8 @@ from django.template.defaultfilters import slugify from django.utils.translation import ugettext as _, ugettext_lazy from django.utils import timezone as tz 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 @@ -33,9 +35,9 @@ class Nameable (models.Model): @property def slug (self): - return self.get_slug_name() - - def get_slug_name (self): + """ + Slug based on the name. We replace '-' by '_' + """ return slugify(self.name).replace('-', '_') def __str__ (self): @@ -450,8 +452,11 @@ class Program (Nameable): @property def path (self): + """ + Return the path to the programs directory + """ 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): """ @@ -498,10 +503,9 @@ class Diffusion (models.Model): - stop: the diffusion has been manually stopped """ Type = { - 'default': 0x00, # confirmed diffusion case FIXME + 'default': 0x00, # diffusion is planified 'unconfirmed': 0x01, # scheduled by the generator but not confirmed for diffusion - 'cancel': 0x02, # cancellation happened; used to inform users - # 'restart': 0x03, # manual restart; used to remix/give up antenna + 'cancel': 0x02, # diffusion canceled } for key, value in Type.items(): ugettext_lazy(key) @@ -592,6 +596,10 @@ class Diffusion (models.Model): verbose_name = _('Diffusion') verbose_name_plural = _('Diffusions') + permissions = ( + ('programming', _('edit the diffusion\'s planification')), + ) + class Log (models.Model): """ @@ -603,11 +611,6 @@ class Log (models.Model): help_text = 'source information', blank = True, null = True, ) - diffusion = models.ForeignKey( - 'Diffusion', - help_text = _('related diffusion'), - blank = True, null = True, - ) sound = models.ForeignKey( 'Sound', help_text = _('played sound'), @@ -620,6 +623,16 @@ class Log (models.Model): max_length = 512, 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): print(str(self), ':', self.comment or '') diff --git a/aircox_programs/settings.py b/aircox_programs/settings.py index 8d4ac4d..36abc2a 100755 --- a/aircox_programs/settings.py +++ b/aircox_programs/settings.py @@ -33,5 +33,3 @@ ensure('AIRCOX_SOUND_FILE_EXT', # Stream for the scheduled diffusions ensure('AIRCOX_SCHEDULED_STREAM', 0) - - diff --git a/website/models.py b/website/models.py index 732e310..95605a2 100644 --- a/website/models.py +++ b/website/models.py @@ -8,8 +8,6 @@ class Program (RelatedPost): model = programs.Program bind_mapping = True mapping = { - 'title': 'name', - 'content': 'description', } class Episode (RelatedPost):