schedules algorithms
This commit is contained in:
		@ -81,7 +81,7 @@ class SoundFileAdmin (MetadataAdmin):
 | 
				
			|||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #inlines = [ EpisodeInline ]
 | 
					    #inlines = [ EpisodeInline ]
 | 
				
			||||||
    inlines = [ EventInline ]
 | 
					    #inlines = [ EventInline ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import argparse
 | 
					import argparse
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.core.management.base    import BaseCommand, CommandError
 | 
					from django.core.management.base    import BaseCommand, CommandError
 | 
				
			||||||
from django.utils                   import timezone
 | 
					from django.utils                   import timezone
 | 
				
			||||||
@ -110,7 +111,7 @@ class Model:
 | 
				
			|||||||
                v = getattr(item, f)
 | 
					                v = getattr(item, f)
 | 
				
			||||||
                if hasattr(v, 'id'):
 | 
					                if hasattr(v, 'id'):
 | 
				
			||||||
                    v = v.id
 | 
					                    v = v.id
 | 
				
			||||||
                r.append(str(v).ljust(len(f)))
 | 
					                r.append(v)
 | 
				
			||||||
            items.append(r)
 | 
					            items.append(r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if options.get('head'):
 | 
					        if options.get('head'):
 | 
				
			||||||
@ -118,7 +119,14 @@ class Model:
 | 
				
			|||||||
        elif options.get('tail'):
 | 
					        elif options.get('tail'):
 | 
				
			||||||
            items = items[-options.get('tail'):]
 | 
					            items = items[-options.get('tail'):]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        print(' || '.join(fields))
 | 
					        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:
 | 
					        for item in items:
 | 
				
			||||||
            print(' || '.join(item))
 | 
					            print(' || '.join(item))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -165,7 +173,7 @@ models = {
 | 
				
			|||||||
  , 'schedule': Model( models.Schedule
 | 
					  , 'schedule': Model( models.Schedule
 | 
				
			||||||
                    , { 'parent_id': int, 'date': DateTime, 'duration': Time
 | 
					                    , { 'parent_id': int, 'date': DateTime, 'duration': Time
 | 
				
			||||||
                      , 'frequency': int }
 | 
					                      , 'frequency': int }
 | 
				
			||||||
                    , { 'rerun': bool }
 | 
					                    , { 'rerun': int } # FIXME: redo
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
  , 'soundfile': Model( models.SoundFile
 | 
					  , 'soundfile': Model( models.SoundFile
 | 
				
			||||||
                    , { 'parent_id': int, 'date': DateTime, 'file': str
 | 
					                    , { 'parent_id': int, 'date': DateTime, 'file': str
 | 
				
			||||||
@ -194,6 +202,8 @@ class Command (BaseCommand):
 | 
				
			|||||||
        group.add_argument('--add', action='store_true'
 | 
					        group.add_argument('--add', action='store_true'
 | 
				
			||||||
                          , help='create or update (if id is given) object')
 | 
					                          , help='create or update (if id is given) object')
 | 
				
			||||||
        group.add_argument('--delete', action='store_true')
 | 
					        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 = parser.add_argument_group('selector')
 | 
				
			||||||
@ -207,6 +217,10 @@ class Command (BaseCommand):
 | 
				
			|||||||
        group.add_argument('--tail', type=int
 | 
					        group.add_argument('--tail', type=int
 | 
				
			||||||
                          , help='dump the TAIL last objects only'
 | 
					                          , help='dump the TAIL last objects only'
 | 
				
			||||||
                          )
 | 
					                          )
 | 
				
			||||||
 | 
					        group.add_argument('--fields', action='store_true'
 | 
				
			||||||
 | 
					                          , help='print fields before dumping'
 | 
				
			||||||
 | 
					                          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # publication/generic
 | 
					        # publication/generic
 | 
				
			||||||
        group = parser.add_argument_group('fields'
 | 
					        group = parser.add_argument_group('fields'
 | 
				
			||||||
@ -232,7 +246,7 @@ class Command (BaseCommand):
 | 
				
			|||||||
        # schedule
 | 
					        # schedule
 | 
				
			||||||
        group.add_argument('--duration',   type=str)
 | 
					        group.add_argument('--duration',   type=str)
 | 
				
			||||||
        group.add_argument('--frequency',  type=int)
 | 
					        group.add_argument('--frequency',  type=int)
 | 
				
			||||||
        group.add_argument('--rerun',      action='store_true')
 | 
					        group.add_argument('--rerun',      type=int)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # fields
 | 
					        # fields
 | 
				
			||||||
        parser.formatter_class=argparse.RawDescriptionHelpFormatter
 | 
					        parser.formatter_class=argparse.RawDescriptionHelpFormatter
 | 
				
			||||||
@ -258,7 +272,7 @@ class Command (BaseCommand):
 | 
				
			|||||||
                models[model].make(options)
 | 
					                models[model].make(options)
 | 
				
			||||||
        elif options.get('delete'):
 | 
					        elif options.get('delete'):
 | 
				
			||||||
            models[model].delete(options)
 | 
					            models[model].delete(options)
 | 
				
			||||||
        else:
 | 
					        else: # --dump --json
 | 
				
			||||||
            models[model].dump(options)
 | 
					            models[model].dump(options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -17,41 +17,42 @@ import programs.settings                    as settings
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Frequency for schedules. Basically, it is a mask of bits where each bit is a
 | 
				
			||||||
 | 
					# week. Bits > rank 5 are used for special schedules.
 | 
				
			||||||
 | 
					# Important: the first week is always the first week where the weekday of the
 | 
				
			||||||
 | 
					# schedule is present.
 | 
				
			||||||
Frequency = {
 | 
					Frequency = {
 | 
				
			||||||
    'ponctual':          0b000000
 | 
					    'ponctual':         0b000000
 | 
				
			||||||
  , 'every week':        0b001111
 | 
					  , 'first':            0b000001
 | 
				
			||||||
  , 'first week':        0b000001
 | 
					  , 'second':           0b000010
 | 
				
			||||||
  , 'second week':       0b000010
 | 
					  , 'third':            0b000100
 | 
				
			||||||
  , 'third week':        0b000100
 | 
					  , 'fourth':           0b001000
 | 
				
			||||||
  , 'fourth week':       0b001000
 | 
					  , 'last':             0b010000
 | 
				
			||||||
  , 'first and third':   0b000101
 | 
					  , 'first and third':  0b000101
 | 
				
			||||||
  , 'second and fourth': 0b001010
 | 
					  , 'second and fourth': 0b001010
 | 
				
			||||||
  , 'one week on two':   0b010010
 | 
					  , 'every':            0b011111
 | 
				
			||||||
    #'uneven week':         0b100000
 | 
					  , 'one on two':       0b100000
 | 
				
			||||||
    # TODO 'every day':     0b110000
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Translators: html safe values
 | 
					# Translators: html safe values
 | 
				
			||||||
ugettext_lazy('ponctual')
 | 
					ugettext_lazy('ponctual')
 | 
				
			||||||
ugettext_lazy('every week')
 | 
					ugettext_lazy('every')
 | 
				
			||||||
ugettext_lazy('first week')
 | 
					ugettext_lazy('first')
 | 
				
			||||||
ugettext_lazy('second week')
 | 
					ugettext_lazy('second')
 | 
				
			||||||
ugettext_lazy('third week')
 | 
					ugettext_lazy('third')
 | 
				
			||||||
ugettext_lazy('fourth week')
 | 
					ugettext_lazy('fourth')
 | 
				
			||||||
ugettext_lazy('first and third')
 | 
					ugettext_lazy('first and third')
 | 
				
			||||||
ugettext_lazy('second and fourth')
 | 
					ugettext_lazy('second and fourth')
 | 
				
			||||||
ugettext_lazy('one week on two')
 | 
					ugettext_lazy('one on two')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EventType = {
 | 
					EventType = {
 | 
				
			||||||
    'play':     0x02    # the sound is playing / planified to play
 | 
					    'diffuse':  0x01   # the diffusion is planified or done
 | 
				
			||||||
  , 'cancel':   0x03    # the sound has been canceled from grid; useful to give
 | 
					  , 'cancel':   0x03   # the diffusion has been canceled from grid; useful to give
 | 
				
			||||||
                        # the info to the users
 | 
					                        # the info to the users
 | 
				
			||||||
  , 'stop':     0x04    # the sound has been arbitrary stopped (non-stop or not)
 | 
					  , 'stop':     0x04   # the diffusion been arbitrary stopped (non-stop or not)
 | 
				
			||||||
  , 'non-stop': 0x05    # the sound has been played as non-stop
 | 
					 | 
				
			||||||
#, 'streaming'
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -109,8 +110,9 @@ class Metadata (Model):
 | 
				
			|||||||
    public      = models.BooleanField(
 | 
					    public      = models.BooleanField(
 | 
				
			||||||
                      _('public')
 | 
					                      _('public')
 | 
				
			||||||
                    , default = False
 | 
					                    , default = False
 | 
				
			||||||
                    , help_text = _('publication is accessible to the public')
 | 
					                    , help_text = _('publication is public')
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
 | 
					    # FIXME: add a field to specify if the element should be listed or not
 | 
				
			||||||
    meta        = models.TextField(
 | 
					    meta        = models.TextField(
 | 
				
			||||||
                      _('meta')
 | 
					                      _('meta')
 | 
				
			||||||
                    , blank = True
 | 
					                    , blank = True
 | 
				
			||||||
@ -317,41 +319,52 @@ class Schedule (Model):
 | 
				
			|||||||
    parent      = models.ForeignKey( 'Program', blank = True, null = True )
 | 
					    parent      = models.ForeignKey( 'Program', blank = True, null = True )
 | 
				
			||||||
    date        = models.DateTimeField(_('start'))
 | 
					    date        = models.DateTimeField(_('start'))
 | 
				
			||||||
    duration    = models.TimeField(
 | 
					    duration    = models.TimeField(
 | 
				
			||||||
                      _('duration')
 | 
					                    _('duration')
 | 
				
			||||||
                    , blank = True
 | 
					                  , blank = True
 | 
				
			||||||
                    , null = True
 | 
					                  , null = True
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
    frequency   = models.SmallIntegerField(
 | 
					    frequency   = models.SmallIntegerField(
 | 
				
			||||||
                      _('frequency')
 | 
					                    _('frequency')
 | 
				
			||||||
                    , choices = [ (y, x) for x,y in Frequency.items() ]
 | 
					                  , choices = [ (y, x) for x,y in Frequency.items() ]
 | 
				
			||||||
 | 
					                  )
 | 
				
			||||||
 | 
					    rerun       = models.ForeignKey(
 | 
				
			||||||
 | 
					                    'self'
 | 
				
			||||||
 | 
					                  , blank = True
 | 
				
			||||||
 | 
					                  , null = True
 | 
				
			||||||
 | 
					                  , help_text = "Schedule of a rerun"
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
    rerun       = models.BooleanField(_('rerun'), default = False)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def match_date (self, at = timezone.datetime.today(), check_time = False):
 | 
					    def match (self, date = None, check_time = False):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Return True if the given datetime matches the schedule
 | 
					        Return True if the given datetime matches the schedule
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if self.date.weekday() == at.weekday() and self.match_week(at):
 | 
					        if not date:
 | 
				
			||||||
            return (check_time and self.date.time() == at.date.time()) or True
 | 
					            date = timezone.datetime.today()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.date.weekday() == date.weekday() and self.match_week(date):
 | 
				
			||||||
 | 
					            return (check_time and self.date.time() == date.date.time()) or True
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def match_week (self, at = timezone.datetime.today()):
 | 
					    def match_week (self, date = None):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Return True if the given week number matches the schedule, False
 | 
					        Return True if the given week number matches the schedule, False
 | 
				
			||||||
        otherwise.
 | 
					        otherwise.
 | 
				
			||||||
        If the schedule is ponctual, return None.
 | 
					        If the schedule is ponctual, return None.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					        if not date:
 | 
				
			||||||
 | 
					            date = timezone.datetime.today()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.frequency == Frequency['ponctual']:
 | 
					        if self.frequency == Frequency['ponctual']:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.frequency == Frequency['one week on two']:
 | 
					        if self.frequency == Frequency['one on two']:
 | 
				
			||||||
            week = at.isocalendar()[1]
 | 
					            week = date.isocalendar()[1]
 | 
				
			||||||
            return (week % 2) == (self.date.isocalendar()[1] % 2)
 | 
					            return (week % 2) == (self.date.isocalendar()[1] % 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        first_of_month = timezone.datetime.date(at.year, at.month, 1)
 | 
					        first_of_month = timezone.datetime.date(date.year, date.month, 1)
 | 
				
			||||||
        week = at.isocalendar()[1] - first_of_month.isocalendar()[1]
 | 
					        week = date.isocalendar()[1] - first_of_month.isocalendar()[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # weeks of month
 | 
					        # weeks of month
 | 
				
			||||||
        if week == 4:
 | 
					        if week == 4:
 | 
				
			||||||
@ -360,52 +373,62 @@ class Schedule (Model):
 | 
				
			|||||||
        return (self.frequency & (0b0001 << week) > 0)
 | 
					        return (self.frequency & (0b0001 << week) > 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def next_date (self, at = timezone.datetime.today()):
 | 
					    def normalize (self, date):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Set the time of a datetime to the schedule's one
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return date.replace( hour = self.date.hour
 | 
				
			||||||
 | 
					                           , minute = self.date.minute )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def dates_of_month (self, date = None):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return a list with all matching dates of the month of the given
 | 
				
			||||||
 | 
					        date (= today).
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        if self.frequency == Frequency['ponctual']:
 | 
					        if self.frequency == Frequency['ponctual']:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # first day of the week
 | 
					        if not date:
 | 
				
			||||||
        date = at - timezone.timedelta( days = at.weekday() )
 | 
					            date = timezone.datetime.today()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # for the next five week, we look for a matching week.
 | 
					        date = timezone.datetime( year = date.year
 | 
				
			||||||
        # when found, add the number of day since de start of the
 | 
					                                 , month = date.month
 | 
				
			||||||
        # we need to test if the result is >= at
 | 
					                                 , day = 1 )
 | 
				
			||||||
        for i in range(0,5):
 | 
					        wday = self.date.weekday()
 | 
				
			||||||
            if self.match_week(date):
 | 
					        fwday = date.weekday()
 | 
				
			||||||
                date_ = date + timezone.timedelta( days = self.date.weekday() )
 | 
					 | 
				
			||||||
                if date_ >= at:
 | 
					 | 
				
			||||||
                    # we don't want past events
 | 
					 | 
				
			||||||
                    return timezone.datetime(date_.year, date_.month, date_.day,
 | 
					 | 
				
			||||||
                                                self.date.hour, self.date.minute)
 | 
					 | 
				
			||||||
            date += timezone.timedelta( days = 7 )
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # move date to the date weekday of the schedule
 | 
				
			||||||
 | 
					        # check on SO#3284452 for the formula
 | 
				
			||||||
 | 
					        date += timezone.timedelta(
 | 
				
			||||||
 | 
					                    days = (7 if fwday > wday else 0) - fwday + wday
 | 
				
			||||||
 | 
					                 )
 | 
				
			||||||
 | 
					        fwday = date.weekday()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def next_dates (self, at = timezone.datetime.today(), n = 52):
 | 
					        # special frequency case
 | 
				
			||||||
        # we could have optimized this function, but since it should not
 | 
					        weeks = self.frequency
 | 
				
			||||||
        # be use too often, we keep a more readable and easier to debug
 | 
					        if self.frequency == Frequency['last']:
 | 
				
			||||||
        # solution
 | 
					            date += timezone.timedelta(month = 1, days = -7)
 | 
				
			||||||
        if self.frequency == 0b000000:
 | 
					            return self.normalize([date])
 | 
				
			||||||
            return None
 | 
					        if weeks == Frequency['one on two']:
 | 
				
			||||||
 | 
					            # if both week are the same, then the date week of the month
 | 
				
			||||||
 | 
					            # matches. Note: wday % 2 + fwday % 2 => (wday + fwday) % 2
 | 
				
			||||||
 | 
					            fweek = date.isocalendar()[1]
 | 
				
			||||||
 | 
					            week = self.date.isocalendar()[1]
 | 
				
			||||||
 | 
					            weeks = 0b010101 if not (fweek + week) % 2 else 0b001010
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        res = []
 | 
					        dates = []
 | 
				
			||||||
        for i in range(n):
 | 
					        for week in range(0,5):
 | 
				
			||||||
            e = self.next_date(at)
 | 
					            # NB: there can be five weeks in a month
 | 
				
			||||||
            if not e:
 | 
					            if not weeks & (0b1 << week):
 | 
				
			||||||
                break
 | 
					                continue
 | 
				
			||||||
            res.append(e)
 | 
					 | 
				
			||||||
            at = res[-1] + timezone.timedelta(days = 1)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return res
 | 
					            wdate = date + timezone.timedelta(days = week * 7)
 | 
				
			||||||
 | 
					            if wdate.month == date.month:
 | 
				
			||||||
 | 
					                dates.append(self.normalize(wdate))
 | 
				
			||||||
 | 
					        return dates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #def to_string(self):
 | 
					 | 
				
			||||||
    #    s = ugettext_lazy( RFrequency[self.frequency] )
 | 
					 | 
				
			||||||
    #    if self.rerun:
 | 
					 | 
				
			||||||
    #        return s + ' (' + _('rerun') + ')'
 | 
					 | 
				
			||||||
    #    return s
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __str__ (self):
 | 
					    def __str__ (self):
 | 
				
			||||||
        frequency = [ x for x,y in Frequency.items() if y == self.frequency ]
 | 
					        frequency = [ x for x,y in Frequency.items() if y == self.frequency ]
 | 
				
			||||||
        return self.parent.title + ': ' + frequency[0]
 | 
					        return self.parent.title + ': ' + frequency[0]
 | 
				
			||||||
@ -419,19 +442,19 @@ class Schedule (Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class Article (Publication):
 | 
					class Article (Publication):
 | 
				
			||||||
    parent      = models.ForeignKey(
 | 
					    parent      = models.ForeignKey(
 | 
				
			||||||
                      'self'
 | 
					                    'self'
 | 
				
			||||||
                    , verbose_name = _('parent')
 | 
					                  , verbose_name = _('parent')
 | 
				
			||||||
                    , blank = True
 | 
					                  , blank = True
 | 
				
			||||||
                    , null = True
 | 
					                  , null = True
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
    static_page = models.BooleanField(
 | 
					    static_page = models.BooleanField(
 | 
				
			||||||
                      _('static page')
 | 
					                    _('static page')
 | 
				
			||||||
                    , default = False
 | 
					                  , default = False
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
    focus       = models.BooleanField(
 | 
					    focus       = models.BooleanField(
 | 
				
			||||||
                      _('article is focus')
 | 
					                    _('article is focus')
 | 
				
			||||||
                    , blank = True
 | 
					                  , blank = True
 | 
				
			||||||
                    , default = False
 | 
					                  , default = False
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -443,26 +466,26 @@ class Article (Publication):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class Program (Publication):
 | 
					class Program (Publication):
 | 
				
			||||||
    parent      = models.ForeignKey(
 | 
					    parent      = models.ForeignKey(
 | 
				
			||||||
                      Article
 | 
					                    Article
 | 
				
			||||||
                    , verbose_name = _('parent')
 | 
					                  , verbose_name = _('parent')
 | 
				
			||||||
                    , blank = True
 | 
					                  , blank = True
 | 
				
			||||||
                    , null = True
 | 
					                  , null = True
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
    email       = models.EmailField(
 | 
					    email       = models.EmailField(
 | 
				
			||||||
                      _('email')
 | 
					                    _('email')
 | 
				
			||||||
                    , max_length = 128
 | 
					                  , max_length = 128
 | 
				
			||||||
                    , null = True
 | 
					                  , null = True
 | 
				
			||||||
                    , blank = True
 | 
					                  , blank = True
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
    url         = models.URLField(
 | 
					    url         = models.URLField(
 | 
				
			||||||
                    _('website')
 | 
					                    _('website')
 | 
				
			||||||
                    , blank = True
 | 
					                  , blank = True
 | 
				
			||||||
                    , null = True
 | 
					                  , null = True
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
    non_stop    = models.SmallIntegerField(
 | 
					    non_stop    = models.SmallIntegerField(
 | 
				
			||||||
                      _('non-stop priority')
 | 
					                    _('non-stop priority')
 | 
				
			||||||
                    , help_text = _('this program can be used as non-stop')
 | 
					                  , help_text = _('this program can be used as non-stop')
 | 
				
			||||||
                    , default = -1
 | 
					                  , default = -1
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
@ -498,6 +521,7 @@ class Episode (Publication):
 | 
				
			|||||||
    #   minimum of values.
 | 
					    #   minimum of values.
 | 
				
			||||||
    #   Duration can be retrieved from the sound file if there is one.
 | 
					    #   Duration can be retrieved from the sound file if there is one.
 | 
				
			||||||
    #
 | 
					    #
 | 
				
			||||||
 | 
					    # FIXME: ponctual replays?
 | 
				
			||||||
    parent      = models.ForeignKey(
 | 
					    parent      = models.ForeignKey(
 | 
				
			||||||
                      Program
 | 
					                      Program
 | 
				
			||||||
                    , verbose_name = _('parent')
 | 
					                    , verbose_name = _('parent')
 | 
				
			||||||
@ -517,23 +541,35 @@ class Episode (Publication):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class Event (Model):
 | 
					class Event (Model):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Event logs and planification of a sound file
 | 
					    Event logs and planifications.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    An event is:
 | 
				
			||||||
 | 
					    - scheduled: when it has been generated following programs' Schedule
 | 
				
			||||||
 | 
					    - planified: when it has been generated manually/ponctually or scheduled
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    sound       = models.ForeignKey (
 | 
					    parent      = models.ForeignKey (
 | 
				
			||||||
                      SoundFile
 | 
					                      Episode
 | 
				
			||||||
                    , verbose_name = _('sound file')
 | 
					                    , blank = True
 | 
				
			||||||
 | 
					                    , null = True
 | 
				
			||||||
 | 
					                  )
 | 
				
			||||||
 | 
					    program     = models.ForeignKey (
 | 
				
			||||||
 | 
					                      Program
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
    type        = models.SmallIntegerField(
 | 
					    type        = models.SmallIntegerField(
 | 
				
			||||||
                      _('type')
 | 
					                      _('type')
 | 
				
			||||||
                    , choices = [ (y, x) for x,y in EventType.items() ]
 | 
					                    , choices = [ (y, x) for x,y in EventType.items() ]
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
    date        = models.DateTimeField( _('date of event start') )
 | 
					    date        = models.DateTimeField( _('date of event start') )
 | 
				
			||||||
    meta        = models.TextField (
 | 
					    stream      = models.SmallIntegerField(
 | 
				
			||||||
                      _('meta')
 | 
					                      _('stream')
 | 
				
			||||||
                    , blank = True
 | 
					                    , default = 0
 | 
				
			||||||
                    , null = True
 | 
					                    , help_text = 'stream id on which the event happens'
 | 
				
			||||||
 | 
					                  )
 | 
				
			||||||
 | 
					    scheduled   = models.BooleanField(
 | 
				
			||||||
 | 
					                      _('automated')
 | 
				
			||||||
 | 
					                    , default = False
 | 
				
			||||||
 | 
					                    , help_text = 'event generated automatically'
 | 
				
			||||||
                  )
 | 
					                  )
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        verbose_name = _('Event')
 | 
					        verbose_name = _('Event')
 | 
				
			||||||
 | 
				
			|||||||
@ -6,11 +6,19 @@ def ensure (key, default):
 | 
				
			|||||||
    globals()[key] = getattr(settings, key, default)
 | 
					    globals()[key] = getattr(settings, key, default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Directory for the programs data
 | 
				
			||||||
ensure('AIRCOX_PROGRAMS_DIR',
 | 
					ensure('AIRCOX_PROGRAMS_DIR',
 | 
				
			||||||
       os.path.join(settings.MEDIA_ROOT, 'programs'))
 | 
					       os.path.join(settings.MEDIA_ROOT, 'programs'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Default directory for the soundfiles
 | 
				
			||||||
ensure('AIRCOX_SOUNDFILE_DEFAULT_DIR',
 | 
					ensure('AIRCOX_SOUNDFILE_DEFAULT_DIR',
 | 
				
			||||||
       os.path.join(AIRCOX_PROGRAMS_DIR + 'default'))
 | 
					       os.path.join(AIRCOX_PROGRAMS_DIR + 'default'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Extension of sound files
 | 
				
			||||||
ensure('AIRCOX_SOUNDFILE_EXT',
 | 
					ensure('AIRCOX_SOUNDFILE_EXT',
 | 
				
			||||||
        ('ogg','flac','wav','mp3','opus'))
 | 
					        ('ogg','flac','wav','mp3','opus'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Stream for the scheduled events
 | 
				
			||||||
 | 
					ensure('AIRCOX_SCHEDULED_STREAM', 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,43 +1,52 @@
 | 
				
			|||||||
from django.utils                   import timezone
 | 
					from django.utils                   import timezone
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from programs.models                import Schedule, Event, Episode,\
 | 
					from programs.models                import Schedule, Event, Episode,\
 | 
				
			||||||
                                           SoundFile, Frequency
 | 
					                                           EventType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def update_scheduled_events (date):
 | 
					def scheduled_month_events (date = None, unsaved_only = False):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Update planified events from schedules
 | 
					    Return a list of scheduled events for the month of the given date. For the
 | 
				
			||||||
    TODO: notification in case of conflicts?
 | 
					    non existing events, a program attribute to the corresponding program is
 | 
				
			||||||
 | 
					    set.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    all_schedules = Schedule.objects().all()
 | 
					    if not date:
 | 
				
			||||||
    schedules = [ schedule
 | 
					        date = timezone.datetime.today()
 | 
				
			||||||
                    for schedule in models.Schedule.objects().all()
 | 
					 | 
				
			||||||
                    if schedule.match-date(date) ]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    schedules.sort(key = lambda e: e.date)
 | 
					    schedules = Schedule.objects.all()
 | 
				
			||||||
 | 
					    events = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for schedule in schedules:
 | 
					    for schedule in schedules:
 | 
				
			||||||
        if schedule.frequency == Frequency['ponctual']:
 | 
					        dates = schedule.dates_of_month()
 | 
				
			||||||
            continue
 | 
					        for date in dates:
 | 
				
			||||||
 | 
					            event = Event.objects \
 | 
				
			||||||
 | 
					                         .filter(date = date, parent__parent = schedule.parent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ev_date = timezone.datetime(date.year, date.month, date.day,
 | 
					            if event.count():
 | 
				
			||||||
                                    schedule.date.hour, schedule.date.minute)
 | 
					                if not unsaved_only:
 | 
				
			||||||
 | 
					                    events.append(event)
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # if event exists, pass
 | 
					            # get episode
 | 
				
			||||||
        n = Event.objects() \
 | 
					            ep_date = date
 | 
				
			||||||
                 .filter(date = ev_date, parent__parent = schedule.parent) \
 | 
					            if schedule.rerun:
 | 
				
			||||||
                 .count()
 | 
					                ep_date = schedule.rerun.date
 | 
				
			||||||
        if n:
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ep_date = ev_date
 | 
					            episode = Episode.objects().filter( date = ep_date
 | 
				
			||||||
 | 
					                                              , parent = schedule.parent )
 | 
				
			||||||
 | 
					            episode  = episode[0] if episode.count() else None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # rerun?
 | 
					            # make event
 | 
				
			||||||
        if schedule.rerun:
 | 
					            event = Event( parent = episode
 | 
				
			||||||
            schedule = schedule.rerun
 | 
					                         , program = schedule.parent
 | 
				
			||||||
            date_ = schedule.date
 | 
					                         , type = EventType['diffuse']
 | 
				
			||||||
 | 
					                         , date = date
 | 
				
			||||||
        episode = Episode.objects().filter(date = date)
 | 
					                         , stream = settings.AIRCOX_SCHEDULED_STREAM
 | 
				
			||||||
 | 
					                         , scheduled = True
 | 
				
			||||||
 | 
					                         )
 | 
				
			||||||
 | 
					            event.program = schedule.program
 | 
				
			||||||
 | 
					            events.append(event)
 | 
				
			||||||
 | 
					    return events
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user