diff --git a/programs/README.md b/programs/README.md new file mode 100644 index 0000000..b0240df --- /dev/null +++ b/programs/README.md @@ -0,0 +1,49 @@ +This application defines all base classes for the aircox platform. This includes: +* **Metadata**: generic class that contains metadata +* **Publication**: generic class for models that can be publicated +* **Track**: informations on a track in a playlist +* **SoundFile**: informations on a sound (podcast) +* **Schedule**: schedule informations for programs +* **Article**: simple article +* **Program**: radio program +* **Episode**: occurence of a radio program +* **Event**: log info on what has been or what should be played + + +# Program +Each program has a directory in **AIRCOX_PROGRAMS_DATA**; For each, subdir: +* **public**: public sound files and data (accessible from the website) +* **private**: private sound files and data +* **podcasts**: podcasts that can be upload to external plateforms + + +# Event +Event have a double purpose: +- log played sounds +- plannify diffusions + + +# manage.py schedule +Return the next songs to be played and the schedule and the programmed emissions + +# manage.py monitor +The manage.py has a command **monitor** that: +* check for new sound files +* stat the sound files +* match sound files against episodes and eventually program them +* upload public podcasts to mixcloud if required + +The command will try to match file name against a planified episode by detecting +a date (ISO 8601 date notation YYYY-MM-DD or YYYYMMDD) as name prefix + +Tags set: +* **incorrect**: the sound is not correct for diffusion (TODO: parameters) + + + + + + + + + diff --git a/programs/admin.py b/programs/admin.py index 902e564..d7f3b6c 100755 --- a/programs/admin.py +++ b/programs/admin.py @@ -1,13 +1,15 @@ import copy -from django.contrib import admin -from django.db import models +from django import forms +from django.contrib import admin +from django.db import models from suit.admin import SortableTabularInline, SortableModelAdmin from autocomplete_light.contrib.taggit_field import TaggitWidget, TaggitField -from programs.forms import * -from programs.models import * +from programs.forms import * +from programs.models import * + # # Inlines @@ -43,7 +45,7 @@ class MetadataAdmin (admin.ModelAdmin): 'fields': [ 'title', 'tags' ] }), ( None, { - 'fields': [ 'date', 'public', 'enumerable' ], + 'fields': [ 'date', 'public' ], }), ] @@ -57,9 +59,9 @@ class MetadataAdmin (admin.ModelAdmin): class PublicationAdmin (MetadataAdmin): fieldsets = copy.deepcopy(MetadataAdmin.fieldsets) - list_display = ('id', 'title', 'date', 'public', 'enumerable', 'parent') + list_display = ('id', 'title', 'date', 'public', 'parent') list_filter = ['date', 'public', 'parent', 'author'] - list_editable = ('public', 'enumerable') + list_editable = ('public',) search_fields = ['title', 'content'] fieldsets[0][1]['fields'].insert(1, 'subtitle') @@ -77,22 +79,15 @@ class SoundAdmin (MetadataAdmin): @admin.register(Stream) class StreamAdmin (SortableModelAdmin): - list_display = ('id', 'name', 'type', 'public', 'enumerable', 'priority') - list_editable = ('public', 'enumerable') + list_display = ('id', 'title', 'type', 'public', 'priority') + list_editable = ('public',) sortable = "priority" -@admin.register(Article) -class ArticleAdmin (PublicationAdmin): - fieldsets = copy.deepcopy(PublicationAdmin.fieldsets) - - fieldsets[1][1]['fields'] += ['static_page'] - - @admin.register(Program) class ProgramAdmin (PublicationAdmin): - fieldsets = copy.deepcopy(PublicationAdmin.fieldsets) - inlines = [ ScheduleInline ] + fieldsets = copy.deepcopy(PublicationAdmin.fieldsets) + inlines = [ ScheduleInline ] fieldsets[1][1]['fields'] += ['email', 'url'] @@ -100,7 +95,7 @@ class ProgramAdmin (PublicationAdmin): @admin.register(Episode) class EpisodeAdmin (PublicationAdmin): fieldsets = copy.deepcopy(PublicationAdmin.fieldsets) - list_filter = ['parent'] + PublicationAdmin.list_filter + list_filter = ['parent'] + PublicationAdmin.list_filter fieldsets[0][1]['fields'] += ['sounds'] diff --git a/programs/management/commands/programs.py b/programs/management/commands/programs.py index 0abedb8..0d190a1 100644 --- a/programs/management/commands/programs.py +++ b/programs/management/commands/programs.py @@ -119,16 +119,10 @@ class Model: elif options.get('tail'): items = items[-options.get('tail'):] - if options.get('json'): - if options.get('fields'): - print(json.dumps(fields)) - print(json.dumps(items, default = lambda x: str(x))) - return - if options.get('fields'): - print(' || '.join(fields)) - for item in items: - print(' || '.join(item)) + print(json.dumps(fields)) + print(json.dumps(items, default = lambda x: str(x))) + return def DateTime (string): diff --git a/programs/models.py b/programs/models.py index fad47cd..9a50878 100755 --- a/programs/models.py +++ b/programs/models.py @@ -1,19 +1,17 @@ import os -# django -from django.db import models -from django.contrib.auth.models import User -from django.template.defaultfilters import slugify -from django.contrib.contenttypes.fields import GenericForeignKey -from django.contrib.contenttypes.models import ContentType -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.db import models +from django.contrib.auth.models import User +from django.template.defaultfilters import slugify +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType +from django.utils.translation import ugettext as _, ugettext_lazy +from django.utils import timezone as tz +from django.utils.html import strip_tags -# extensions -from taggit.managers import TaggableManager +from taggit.managers import TaggableManager -import programs.settings as settings +import programs.settings as settings def date_or_default (date, date_only = False): @@ -29,28 +27,6 @@ def date_or_default (date, date_only = False): return date -#class Model (models.Model): -# @classmethod -# def type (cl): -# """ -# Return a string with the type of the model (class name lowered) -# """ -# name = cl.__name__.lower() -# return name - -# @classmethod -# def name (cl, plural = False): -# """ -# Return the name of the model using meta.verbose_name -# """ -# if plural: -# return cl._meta.verbose_name_plural.title() -# return cl._meta.verbose_name.title() -# -# class Meta: -# abstract = True - - class Metadata (models.Model): """ meta is used to extend a model for future needs @@ -73,16 +49,14 @@ class Metadata (models.Model): default = True, help_text = _('publication is public'), ) - enumerable = models.BooleanField( - _('enumerable'), - default = True, - help_text = _('publication is listable'), - ) tags = TaggableManager( _('tags'), blank = True, ) + def get_slug_name (self): + return slugify(self.title) + class Meta: abstract = True @@ -108,28 +82,6 @@ class Publication (Metadata): help_text = _('comments are enabled on this publication'), ) - def get_slug_name (self): - return slugify(self.title) - - def get_parents (self, order_by = "desc", include_fields = None): - """ - Return an array of the parents of the item. - If include_fields is an array of files to include. - """ - # TODO: fields included - # FIXME: parameter name + container - parents = [ self ] - while parents[-1].parent: - parent = parents[-1].parent - if parent not in parents: - # avoid cycles - parents.append(parent) - parents = parents[1:] - - if order_by == 'desc': - return reversed(parents) - return parents - @staticmethod def _exclude_args (allow_unpublished = False, prefix = ''): if allow_unpublished: @@ -181,7 +133,7 @@ class Track (models.Model): ) tags = TaggableManager( blank = True ) # position can be used to specify a position in seconds for non-stop - # programs + # programs or a position in the playlist position = models.SmallIntegerField( default = 0, help_text=_('position in the playlist'), @@ -389,11 +341,11 @@ class Schedule (models.Model): # others for date in dates: - ep_date = date + first_date = date if self.rerun: - ep_date -= self.date - self.rerun.date + first_date -= self.date - self.rerun.date - episode = Episode.objects.filter(date = date, + episode = Episode.objects.filter(date = first_date, parent = self.parent) episode = episode[0] if episode.count() else None @@ -418,7 +370,7 @@ class Schedule (models.Model): class Diffusion (models.Model): Type = { - 'normal': 0x00, # simple diffusion (done/planed) + 'default': 0x00, # simple diffusion (done/planed) '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 @@ -472,9 +424,8 @@ class Stream (models.Model): for key, value in Type.items(): ugettext_lazy(key) - # FIXME: id as integer? - name = models.CharField( - _('name'), + title = models.CharField( + _('title'), max_length = 32, blank = True, null = True, @@ -483,8 +434,6 @@ class Stream (models.Model): verbose_name = _('type'), choices = [ (y, x) for x,y in Type.items() ], ) - # FIXME unique value / suit's orderable - # priority = models.SmallIntegerField( _('priority'), default = 0, @@ -493,12 +442,7 @@ class Stream (models.Model): public = models.BooleanField( _('public'), default = True, - help_text = _('content is public'), - ) - enumerable = models.BooleanField( - _('enumerable'), - default = True, - help_text = _('publication is listable'), + help_text = _('program list is public'), ) # get info for: @@ -509,39 +453,11 @@ class Stream (models.Model): # - stream/pgm def __str__ (self): - return self.name + ' / ' + str(self.priority) - - -class Article (Publication): - # FIXME: move to website? - parent = models.ForeignKey( - 'self', - verbose_name = _('parent'), - blank = True, null = True, - help_text = _('parent article'), - ) - static_page = models.BooleanField( - _('static page'), - default = False, - ) - focus = models.BooleanField( - _('article is focus'), - default = False, - ) - - class Meta: - verbose_name = _('Article') - verbose_name_plural = _('Articles') + return '#{} {}'.format(self.priority, self.title) class Program (Publication): parent = models.ForeignKey( - Article, - verbose_name = _('parent'), - blank = True, null = True, - help_text = _('parent article'), - ) - stream = models.ForeignKey( Stream, verbose_name = _('stream'), ) diff --git a/programs/views.py b/programs/views.py deleted file mode 100755 index 60dca72..0000000 --- a/programs/views.py +++ /dev/null @@ -1,76 +0,0 @@ -from django.shortcuts import render -from django.core.serializers.json import DjangoJSONEncoder -from django.utils import timezone, dateformat - -import programs.models as models -import programs.settings - - - -class DiffusionList: - type = None - next = None - prev = None - at = None - count = None - - - def __init__ (self, **kwargs): - self.__dict__ = kwargs - if kwargs: - self.get_queryset() - - - def get_queryset (self): - diffusions = models.Diffusion.objects; - - if self.next: diffusions = diffusions.filter( date_end__ge = timezone.now() ) - elif self.prev: diffusions = diffusions.filter( date_end__le = timezone.now() ) - else: diffusions = diffusions.all() - - diffusions = diffusions.extra(order_by = ['date']) - if self.at: diffusions = diffusions[self.at:] - if self.count: diffusions = diffusions[:self.count] - - self.diffusions = diffusions - - - def raw_string(): - """ - Return a string with diffusions rendered as raw - """ - res = [] - for diffusion in diffusions: - r = [ dateformat.format(diffusion.date, "Y/m/d H:i:s") - , str(diffusion.type) - , diffusion.parent.file.path - , diffusion.parent.file.url - ] - - res.push(' '.join(r)) - - return '\n'.join(res) - - - def json_string(): - import json - - res = [] - for diffusion in diffusions: - r = { - 'date': dateformat.format(diffusion.date, "Y/m/d H:i:s") - , 'date_end': dateformat.format(diffusion.date_end, "Y/m/d H:i:s") - , 'type': str(diffusion.type) - , 'file_path': diffusion.parent.file.path - , 'file_url': diffusion.parent.file.url - } - - res.push(json.dumps(r)) - - return '\n'.join(res) - - - - - - diff --git a/website/__init__.py b/website/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/website/admin.py b/website/admin.py new file mode 100644 index 0000000..6a94c6e --- /dev/null +++ b/website/admin.py @@ -0,0 +1,15 @@ +import copy + +from django.contrib import admin + +from programs.admin import PublicationAdmin +from website.models import * + +@admin.register(Article) +class ArticleAdmin (PublicationAdmin): + fieldsets = copy.deepcopy(PublicationAdmin.fieldsets) + + fieldsets[1][1]['fields'] += ['static_page'] + + + diff --git a/website/models.py b/website/models.py new file mode 100644 index 0000000..22cc276 --- /dev/null +++ b/website/models.py @@ -0,0 +1,35 @@ +from django.db import models +from django.utils.translation import ugettext as _, ugettext_lazy + +from programs.models import Publication + + +class Article (Publication): + parent = models.ForeignKey( + 'self', + verbose_name = _('parent'), + blank = True, null = True, + help_text = _('parent article'), + ) + static_page = models.BooleanField( + _('static page'), + default = False, + ) + focus = models.BooleanField( + _('article is focus'), + default = False, + ) + referring_tag = models.CharField( + _('referring tag'), + max_length = 32, + blank = True, null = True, + help_text = _('tag used by other to refers to this article'), + ) + + class Meta: + verbose_name = _('Article') + verbose_name_plural = _('Articles') + + + + diff --git a/website/tests.py b/website/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/website/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/website/utils.py b/website/utils.py new file mode 100644 index 0000000..ea77a5b --- /dev/null +++ b/website/utils.py @@ -0,0 +1,83 @@ +from django.db import models +from django.utils import timezone, dateformat +from programs.models import * + + +class ListQueries: + @staticmethod + def search (qs, q): + qs = qs.filter(tags__slug__in = re.compile(r'(\s|\+)+').split(q)) | \ + qs.filter(title__icontains = q) | \ + qs.filter(subtitle__icontains = q) | \ + qs.filter(content__icontains = q) + qs.distinct() + return qs + + @staticmethod + def thread (qs, q): + return qs.filter(parent = q) + + @staticmethod + def next (qs, q): + qs = qs.filter(date__gte = timezone.now()) + if q: + qs = qs.filter(parent = q) + return qs + + @staticmethod + def prev (qs, q): + qs = qs.filter(date__lte = timezone.now()) + if q: + qs = qs.filter(parent = q) + return qs + + @staticmethod + def date (qs, q): + if not q: + q = timezone.datetime.today() + if type(q) is str: + q = timezone.datetime.strptime(q, '%Y/%m/%d').date() + + return qs.filter(date__startswith = q) + + class Diffusion: + @staticmethod + def episode (qs, q): + return qs.filter(episode = q) + + @staticmethod + def program (qs, q): + return qs.filter(program = q) + +class ListQuery: + model = None + qs = None + + def __init__ (self, model, *kwargs): + self.model = model + self.__dict__.update(kwargs) + + def get_queryset (self, by, q): + qs = model.objects.all() + if model._meta.get_field_by_name('public'): + qs = qs.filter(public = True) + + # run query set + queries = Queries.__dict__.get(self.model) or Queries + filter = queries.__dict__.get(by) + if filter: + qs = filter(qs, q) + + # order + if self.sort == 'asc': + qs = qs.order_by('date', 'id') + else: + qs = qs.order_by('-date', '-id') + + # exclude + qs = qs.exclude(id = exclude) + + self.qs = qs + return qs + + diff --git a/website/views.py b/website/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/website/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.