add website app, move articles to it, fix programs.models

This commit is contained in:
bkfox 2015-09-18 12:46:04 +02:00
parent dae9545e27
commit ef4c098d2e
11 changed files with 227 additions and 210 deletions

49
programs/README.md Normal file
View File

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

View File

@ -1,13 +1,15 @@
import copy import copy
from django.contrib import admin from django import forms
from django.db import models from django.contrib import admin
from django.db import models
from suit.admin import SortableTabularInline, SortableModelAdmin from suit.admin import SortableTabularInline, SortableModelAdmin
from autocomplete_light.contrib.taggit_field import TaggitWidget, TaggitField from autocomplete_light.contrib.taggit_field import TaggitWidget, TaggitField
from programs.forms import * from programs.forms import *
from programs.models import * from programs.models import *
# #
# Inlines # Inlines
@ -43,7 +45,7 @@ class MetadataAdmin (admin.ModelAdmin):
'fields': [ 'title', 'tags' ] 'fields': [ 'title', 'tags' ]
}), }),
( None, { ( None, {
'fields': [ 'date', 'public', 'enumerable' ], 'fields': [ 'date', 'public' ],
}), }),
] ]
@ -57,9 +59,9 @@ class MetadataAdmin (admin.ModelAdmin):
class PublicationAdmin (MetadataAdmin): class PublicationAdmin (MetadataAdmin):
fieldsets = copy.deepcopy(MetadataAdmin.fieldsets) 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_filter = ['date', 'public', 'parent', 'author']
list_editable = ('public', 'enumerable') list_editable = ('public',)
search_fields = ['title', 'content'] search_fields = ['title', 'content']
fieldsets[0][1]['fields'].insert(1, 'subtitle') fieldsets[0][1]['fields'].insert(1, 'subtitle')
@ -77,22 +79,15 @@ class SoundAdmin (MetadataAdmin):
@admin.register(Stream) @admin.register(Stream)
class StreamAdmin (SortableModelAdmin): class StreamAdmin (SortableModelAdmin):
list_display = ('id', 'name', 'type', 'public', 'enumerable', 'priority') list_display = ('id', 'title', 'type', 'public', 'priority')
list_editable = ('public', 'enumerable') list_editable = ('public',)
sortable = "priority" sortable = "priority"
@admin.register(Article)
class ArticleAdmin (PublicationAdmin):
fieldsets = copy.deepcopy(PublicationAdmin.fieldsets)
fieldsets[1][1]['fields'] += ['static_page']
@admin.register(Program) @admin.register(Program)
class ProgramAdmin (PublicationAdmin): class ProgramAdmin (PublicationAdmin):
fieldsets = copy.deepcopy(PublicationAdmin.fieldsets) fieldsets = copy.deepcopy(PublicationAdmin.fieldsets)
inlines = [ ScheduleInline ] inlines = [ ScheduleInline ]
fieldsets[1][1]['fields'] += ['email', 'url'] fieldsets[1][1]['fields'] += ['email', 'url']
@ -100,7 +95,7 @@ class ProgramAdmin (PublicationAdmin):
@admin.register(Episode) @admin.register(Episode)
class EpisodeAdmin (PublicationAdmin): class EpisodeAdmin (PublicationAdmin):
fieldsets = copy.deepcopy(PublicationAdmin.fieldsets) fieldsets = copy.deepcopy(PublicationAdmin.fieldsets)
list_filter = ['parent'] + PublicationAdmin.list_filter list_filter = ['parent'] + PublicationAdmin.list_filter
fieldsets[0][1]['fields'] += ['sounds'] fieldsets[0][1]['fields'] += ['sounds']

View File

@ -119,16 +119,10 @@ class Model:
elif options.get('tail'): elif options.get('tail'):
items = items[-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'): if options.get('fields'):
print(' || '.join(fields)) print(json.dumps(fields))
for item in items: print(json.dumps(items, default = lambda x: str(x)))
print(' || '.join(item)) return
def DateTime (string): def DateTime (string):

View File

@ -1,19 +1,17 @@
import os import os
# django from django.db import models
from django.db import models from django.contrib.auth.models import User
from django.contrib.auth.models import User from django.template.defaultfilters import slugify
from django.template.defaultfilters import slugify from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.models import ContentType 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
# 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): def date_or_default (date, date_only = False):
@ -29,28 +27,6 @@ def date_or_default (date, date_only = False):
return date 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): class Metadata (models.Model):
""" """
meta is used to extend a model for future needs meta is used to extend a model for future needs
@ -73,16 +49,14 @@ class Metadata (models.Model):
default = True, default = True,
help_text = _('publication is public'), help_text = _('publication is public'),
) )
enumerable = models.BooleanField(
_('enumerable'),
default = True,
help_text = _('publication is listable'),
)
tags = TaggableManager( tags = TaggableManager(
_('tags'), _('tags'),
blank = True, blank = True,
) )
def get_slug_name (self):
return slugify(self.title)
class Meta: class Meta:
abstract = True abstract = True
@ -108,28 +82,6 @@ class Publication (Metadata):
help_text = _('comments are enabled on this publication'), 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 @staticmethod
def _exclude_args (allow_unpublished = False, prefix = ''): def _exclude_args (allow_unpublished = False, prefix = ''):
if allow_unpublished: if allow_unpublished:
@ -181,7 +133,7 @@ class Track (models.Model):
) )
tags = TaggableManager( blank = True ) tags = TaggableManager( blank = True )
# position can be used to specify a position in seconds for non-stop # position can be used to specify a position in seconds for non-stop
# programs # programs or a position in the playlist
position = models.SmallIntegerField( position = models.SmallIntegerField(
default = 0, default = 0,
help_text=_('position in the playlist'), help_text=_('position in the playlist'),
@ -389,11 +341,11 @@ class Schedule (models.Model):
# others # others
for date in dates: for date in dates:
ep_date = date first_date = date
if self.rerun: 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) parent = self.parent)
episode = episode[0] if episode.count() else None episode = episode[0] if episode.count() else None
@ -418,7 +370,7 @@ class Schedule (models.Model):
class Diffusion (models.Model): class Diffusion (models.Model):
Type = { Type = {
'normal': 0x00, # simple diffusion (done/planed) 'default': 0x00, # simple diffusion (done/planed)
'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, # cancellation happened; used to inform users
'restart': 0x03, # manual restart; used to remix/give up antenna 'restart': 0x03, # manual restart; used to remix/give up antenna
@ -472,9 +424,8 @@ class Stream (models.Model):
for key, value in Type.items(): for key, value in Type.items():
ugettext_lazy(key) ugettext_lazy(key)
# FIXME: id as integer? title = models.CharField(
name = models.CharField( _('title'),
_('name'),
max_length = 32, max_length = 32,
blank = True, blank = True,
null = True, null = True,
@ -483,8 +434,6 @@ class Stream (models.Model):
verbose_name = _('type'), verbose_name = _('type'),
choices = [ (y, x) for x,y in Type.items() ], choices = [ (y, x) for x,y in Type.items() ],
) )
# FIXME unique value / suit's orderable
#
priority = models.SmallIntegerField( priority = models.SmallIntegerField(
_('priority'), _('priority'),
default = 0, default = 0,
@ -493,12 +442,7 @@ class Stream (models.Model):
public = models.BooleanField( public = models.BooleanField(
_('public'), _('public'),
default = True, default = True,
help_text = _('content is public'), help_text = _('program list is public'),
)
enumerable = models.BooleanField(
_('enumerable'),
default = True,
help_text = _('publication is listable'),
) )
# get info for: # get info for:
@ -509,39 +453,11 @@ class Stream (models.Model):
# - stream/pgm # - stream/pgm
def __str__ (self): def __str__ (self):
return self.name + ' / ' + str(self.priority) return '#{} {}'.format(self.priority, self.title)
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')
class Program (Publication): class Program (Publication):
parent = models.ForeignKey( parent = models.ForeignKey(
Article,
verbose_name = _('parent'),
blank = True, null = True,
help_text = _('parent article'),
)
stream = models.ForeignKey(
Stream, Stream,
verbose_name = _('stream'), verbose_name = _('stream'),
) )

View File

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

0
website/__init__.py Normal file
View File

15
website/admin.py Normal file
View File

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

35
website/models.py Normal file
View File

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

3
website/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

83
website/utils.py Normal file
View File

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

3
website/views.py Normal file
View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.