documentation
This commit is contained in:
		@ -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):
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user