This commit is contained in:
bkfox
2015-10-03 14:58:44 +02:00
parent f9d2d47ee6
commit 1a5dcc3eb9
45 changed files with 380 additions and 321 deletions

View File

View File

@ -0,0 +1,108 @@
"""
Manage diffusions using schedules, to update, clean up or check diffusions.
A diffusion generated using this utility is considered has type "unconfirmed",
and is not considered as ready for diffusion; To do so, users must confirm the
diffusion case by changing it's type to "default".
Different actions are available:
- "update" is the process that is used to generated them using programs
schedules for the (given) month.
- "clean" will remove all diffusions that are still unconfirmed and have been
planified before the (given) month.
- "check" will remove all diffusions that are unconfirmed and have been planified
from the (given) month and later.
"""
from argparse import RawTextHelpFormatter
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone as tz
from aircox_programs.models import *
class Actions:
@staticmethod
def update (date):
items = []
for schedule in Schedule.objects.filter(program__active = True):
items += schedule.diffusions_of_month(date, exclude_saved = True)
print('> {} new diffusions for schedule #{} ({})'.format(
len(items), schedule.id, str(schedule)
))
print('total of {} diffusions will be created. To be used, they need '
'manual approval.'.format(len(items)))
print(Diffusion.objects.bulk_create(items))
@staticmethod
def clean (date):
qs = Diffusion.objects.filter(type = Diffusion.Type['unconfirmed'],
date__lt = date)
print('{} diffusions will be removed'.format(qs.count()))
qs.delete()
@staticmethod
def check (date):
qs = Diffusion.objects.filter(type = Diffusion.Type['unconfirmed'],
date__gt = date)
items = []
for diffusion in qs:
schedules = Schedule.objects.filter(program = diffusion.program)
for schedule in schedules:
if schedule.match(diffusion.date):
break
else:
print('> #{}: {}'.format(diffusion.date, str(diffusion)))
items.append(diffusion.id)
print('{} diffusions will be removed'.format(len(items)))
if len(items):
Diffusion.objects.filter(id__in = items).delete()
class Command (BaseCommand):
help= __doc__
def add_arguments (self, parser):
parser.formatter_class=RawTextHelpFormatter
now = tz.datetime.today()
group = parser.add_argument_group('action')
group.add_argument(
'--update', action='store_true',
help = 'generate (unconfirmed) diffusions for the given month. '
'These diffusions must be confirmed manually by changing '
'their type to "normal"')
group.add_argument(
'--clean', action='store_true',
help = 'remove unconfirmed diffusions older than the given month')
group.add_argument(
'--check', action='store_true',
help = 'check future unconfirmed diffusions from the given date '
'agains\'t schedules and remove it if that do not match any '
'schedule')
group = parser.add_argument_group(
'date')
group.add_argument('--year', type=int, default=now.year,
help='used by update, default is today\'s year')
group.add_argument('--month', type=int, default=now.month,
help='used by update, default is today\'s month')
def handle (self, *args, **options):
date = tz.datetime(year = options.get('year'),
month = options.get('month'),
day = 1)
date = tz.make_aware(date)
if options.get('update'):
Actions.update(date)
elif options.get('clean'):
Actions.clean(date)
elif options.get('check'):
Actions.check(date)
else:
raise CommandError('no action has been given')

View File

@ -0,0 +1,271 @@
import argparse
import json
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
import aircox_programs.models as models
class Model:
# dict: key is the argument name, value is the constructor
required = {}
optional = {}
model = None
def __init__ (self, model, required = {}, optional = {}, post = None):
self.model = model
self.required = required
self.optional = optional
self.post = post
def to_string (self):
return '\n'.join(
[ ' - required: {}'.format(', '.join(self.required))
, ' - optional: {}'.format(', '.join(self.optional))
, (self.post is AddTags and ' - tags available\n') or
'\n'
])
def check_or_raise (self, options):
for req in self.required:
if req not in options:
raise CommandError('required argument ' + req + ' is missing')
def get_kargs (self, options):
kargs = {}
for i in self.required:
if options.get(i):
fn = self.required[i]
kargs[i] = fn(options[i])
for i in self.optional:
if options.get(i):
fn = self.optional[i]
kargs[i] = fn(options[i])
return kargs
def get_by_id (self, options):
id_list = options.get('id')
items = self.model.objects.filter( id__in = id_list )
if len(items) is not len(id_list):
for key, id in enumerate(id_list):
if id in items:
del id_list[key]
raise CommandError(
'the following ids has not been found: {} (no change done)'
, ', '.join(id_list)
)
return items
def make (self, options):
self.check_or_raise(options)
kargs = self.get_kargs(options)
item = self.model(**kargs)
item.save()
if self.post:
self.post(item, options)
print('{} #{} created'.format(self.model.name()
, item.id))
def update (self, options):
items = self.get_by_id(options)
for key, item in enumerate(items):
kargs = self.get_kargs(options)
item.__dict__.update(options)
item.save()
print('{} #{} updated'.format(self.model.name()
, item.id))
del items[key]
def delete (self, options):
items = self.get_by_id(options)
items.delete()
print('{} #{} deleted'.format(self.model.name()
, ', '.join(options.get('id'))
))
def dump (self, options):
qs = self.model.objects.all()
fields = ['id'] + [ f.name for f in self.model._meta.fields
if f.name is not 'id']
items = []
for item in qs:
r = []
for f in fields:
v = getattr(item, f)
if hasattr(v, 'id'):
v = v.id
r.append(v)
items.append(r)
if options.get('head'):
items = items[0:options.get('head')]
elif options.get('tail'):
items = items[-options.get('tail'):]
if options.get('fields'):
print(json.dumps(fields))
print(json.dumps(items, default = lambda x: str(x)))
return
def DateTime (string):
dt = timezone.datetime.strptime(string, '%Y-%m-%d %H:%M:%S')
return timezone.make_aware(dt, timezone.get_current_timezone())
def Time (string):
dt = timezone.datetime.strptime(string, '%H:%M')
return timezone.datetime.time(dt)
def AddTags (instance, options):
if options.get('tags'):
instance.tags.add(*options['tags'])
models = {
'program': Model( models.Program
, { 'title': str }
, { 'subtitle': str, 'can_comment': bool, 'date': DateTime
, 'parent_id': int, 'public': bool
, 'url': str, 'email': str, 'non_stop': bool
}
, AddTags
)
, 'article': Model( models.Article
, { 'title': str }
, { 'subtitle': str, 'can_comment': bool, 'date': DateTime
, 'parent_id': int, 'public': bool
, 'static_page': bool, 'focus': bool
}
, AddTags
)
, 'episode': Model( models.Episode
, { 'title': str }
, { 'subtitle': str, 'can_comment': bool, 'date': DateTime
, 'parent_id': int, 'public': bool
}
, AddTags
)
, 'schedule': Model( models.Schedule
, { 'parent_id': int, 'date': DateTime, 'duration': Time
, 'frequency': int }
, { 'rerun': int } # FIXME: redo
)
, 'sound': Model( models.Sound
, { 'parent_id': int, 'date': DateTime, 'file': str
, 'duration': Time}
, { 'fragment': bool, 'embed': str, 'removed': bool }
, AddTags
)
}
class Command (BaseCommand):
help='Create, update, delete or dump an element of the given model.' \
' If no action is given, dump it'
def add_arguments (self, parser):
parser.add_argument( 'model', type=str
, metavar="MODEL"
, help='model to add. It must be in {}'\
.format(', '.join(models.keys()))
)
group = parser.add_argument_group('actions')
group.add_argument('--dump', action='store_true')
group.add_argument('--add', action='store_true'
, help='create or update (if id is given) object')
group.add_argument('--delete', action='store_true')
group.add_argument('--json', action='store_true'
, help='dump using json')
group = parser.add_argument_group('selector')
group.add_argument('--id', type=str, nargs='+'
, metavar="ID"
, help='select existing object by id'
)
group.add_argument('--head', type=int
, help='dump the HEAD first objects only'
)
group.add_argument('--tail', type=int
, help='dump the TAIL last objects only'
)
group.add_argument('--fields', action='store_true'
, help='print fields before dumping'
)
# publication/generic
group = parser.add_argument_group('fields'
, 'depends on the given model')
group.add_argument('--parent_id', type=str)
group.add_argument('--title', type=str)
group.add_argument('--subtitle', type=str)
group.add_argument('--can_comment',action='store_true')
group.add_argument('--public', action='store_true')
group.add_argument( '--date', type=str
, help='a valid date time (Y/m/d H:m:s')
group.add_argument('--tags', type=str, nargs='+')
# program
group.add_argument('--url', type=str)
group.add_argument('--email', type=str)
group.add_argument('--non_stop', type=int)
# article
group.add_argument('--static_page',action='store_true')
group.add_argument('--focus', action='store_true')
# schedule
group.add_argument('--duration', type=str)
group.add_argument('--frequency', type=int)
group.add_argument('--rerun', type=int)
# fields
parser.formatter_class=argparse.RawDescriptionHelpFormatter
parser.epilog = 'available fields per model:'
for name, model in models.items():
parser.epilog += '\n ' + model.model.type() + ': \n' \
+ model.to_string()
def handle (self, *args, **options):
model = options.get('model')
if not model:
raise CommandError('no model has been given')
model = model.lower()
if model not in models:
raise CommandError('model {} is not supported'.format(str(model)))
if options.get('add'):
if options.get('id'):
models[model].update(options)
else:
models[model].make(options)
elif options.get('delete'):
models[model].delete(options)
else: # --dump --json
models[model].dump(options)

View File

@ -0,0 +1,136 @@
"""
Check over programs' sound files, scan them, and add them to the
database if they are not there yet.
It tries to parse the file name to get the date of the diffusion of an
episode and associate the file with it; We use the following format:
yyyymmdd[_n][_][title]
Where:
'yyyy' is the year of the episode's diffusion;
'mm' is the month of the episode's diffusion;
'dd' is the day of the episode's diffusion;
'n' is the number of the episode (if multiple episodes);
'title' the title of the sound;
"""
import os
import re
from argparse import RawTextHelpFormatter
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from aircox_programs.models import *
import aircox_programs.settings as settings
class Command (BaseCommand):
help= __doc__
def report (self, program = None, component = None, *content):
if not component:
print('{}: '.format(program), *content)
else:
print('{}, {}: '.format(program, component), *content)
def add_arguments (self, parser):
parser.formatter_class=RawTextHelpFormatter
def handle (self, *args, **options):
programs = Program.objects.filter()
for program in programs:
self.check(program, program.path + '/public', public = True)
self.check(program, program.path + '/podcasts', embed = True)
self.check(program, program.path + '/private')
def get_sound_info (self, path):
"""
Parse file name to get info on the assumption it has the correct
format (given in Command.help)
"""
r = re.search('^(?P<year>[0-9]{4})'
'(?P<month>[0-9]{2})'
'(?P<day>[0-9]{2})'
'(_(?P<n>[0-9]+))?'
'_?(?P<name>.*)\.\w+$',
os.path.basename(path))
if not (r and r.groupdict()):
self.report(program, path, "file path is not correct, use defaults")
r = {
'name': os.path.splitext(path)
}
r['path'] = path
return r
def ensure_sound (self, sound_info):
"""
Return the Sound for the given sound_info; If not found, create it
without saving it.
"""
sound = Sound.objects.filter(path = path)
if sound:
sound = sound[0]
else:
sound = Sound(path = path, title = sound_info['name'])
def find_episode (self, program, sound_info):
"""
For a given program, and sound path check if there is an episode to
associate to, using the diffusion's date.
If there is no matching episode, return None.
"""
# check on episodes
diffusion = Diffusion.objects.filter(
program = program,
date__year = int(sound_info['year']),
date__month = int(sound_info['month']),
date__day = int(sound_info['day'])
)
if not diffusion.count():
self.report(program, path, 'no diffusion found for the given date')
return
diffusion = diffusion[0]
return diffusion.episode or None
def check (self, program, dir_path, public = False, embed = False):
"""
Scan a given directory that is associated to the given program, and
update sounds information
Return a list of scanned sounds
"""
if not os.path.exists(dir_path):
return
paths = []
for path in os.listdir(dir_path):
path = dir_path + '/' + path
if not path.endswith(settings.AIRCOX_SOUNDFILE_EXT):
continue
paths.append(path)
sound_info = self.get_sound_info(path)
sound = self.ensure_sound(sound_info)
sound.public = public
# episode and relation
if 'year' in sound_info:
episode = self.find_episode(program, sound_info)
if episode:
for sound_ in episode.sounds.get_queryset():
if sound_.path == sound.path:
break
else:
self.report(program, path, 'associate sound to episode ',
episode.id)
episode.sounds.add(sound)
return paths