fixes, updates
This commit is contained in:
		
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							@ -2,9 +2,13 @@
 | 
				
			|||||||
Platform to manage radio programs, schedules, cms, etc. -- main test repo
 | 
					Platform to manage radio programs, schedules, cms, etc. -- main test repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Applications
 | 
					# Applications
 | 
				
			||||||
* **programs**: core application that have all defined models
 | 
					* **programs**: programs, episodes, schedules, sounds and tracks;
 | 
				
			||||||
 | 
					* **streams**:  streams and diffusions, links with LiquidSoap;
 | 
				
			||||||
# Note
 | 
					* **website**: website rendering, using models defined by the previous apps;
 | 
				
			||||||
We make the assumption that admin is used with autocomplete-light and django-suit
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Code and names conventions and uses
 | 
				
			||||||
 | 
					* absolute dates: datetime fields, named "begin" "end" for ranges and "date" otherwise
 | 
				
			||||||
 | 
					* time range: timefield name "duration"
 | 
				
			||||||
 | 
					* parents: when only one parent, named "parent", otherwise model/reference's name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,16 +3,20 @@ import copy
 | 
				
			|||||||
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
 | 
					from suit.admin import SortableTabularInline, SortableModelAdmin
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# TODO: inherits from the corresponding admin view
 | 
					# TODO: inherits from the corresponding admin view
 | 
				
			||||||
 | 
					class SoundInline (admin.TabularInline):
 | 
				
			||||||
 | 
					    model = Sound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ScheduleInline (admin.TabularInline):
 | 
					class ScheduleInline (admin.TabularInline):
 | 
				
			||||||
    model = Schedule
 | 
					    model = Schedule
 | 
				
			||||||
    extra = 1
 | 
					    extra = 1
 | 
				
			||||||
@ -20,12 +24,11 @@ class ScheduleInline (admin.TabularInline):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class DiffusionInline (admin.TabularInline):
 | 
					class DiffusionInline (admin.TabularInline):
 | 
				
			||||||
    model = Diffusion
 | 
					    model = Diffusion
 | 
				
			||||||
    fields = ('episode', 'type', 'begin', 'end', 'stream')
 | 
					    fields = ('episode', 'type', 'date', 'stream')
 | 
				
			||||||
    readonly_fields = ('begin', 'end', 'stream')
 | 
					    readonly_fields = ('date', 'stream')
 | 
				
			||||||
    extra = 1
 | 
					    extra = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
class TrackInline (SortableTabularInline):
 | 
					class TrackInline (SortableTabularInline):
 | 
				
			||||||
    fields = ['artist', 'title', 'tags', 'position']
 | 
					    fields = ['artist', 'title', 'tags', 'position']
 | 
				
			||||||
    form = TrackForm
 | 
					    form = TrackForm
 | 
				
			||||||
@ -34,9 +37,6 @@ class TrackInline (SortableTabularInline):
 | 
				
			|||||||
    extra = 10
 | 
					    extra = 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Parents
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
class MetadataAdmin (admin.ModelAdmin):
 | 
					class MetadataAdmin (admin.ModelAdmin):
 | 
				
			||||||
    fieldsets = [
 | 
					    fieldsets = [
 | 
				
			||||||
        ( None, {
 | 
					        ( None, {
 | 
				
			||||||
@ -47,31 +47,27 @@ class MetadataAdmin (admin.ModelAdmin):
 | 
				
			|||||||
        }),
 | 
					        }),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def save_model (self, request, obj, form, change):
 | 
					    def save_model (self, request, obj, form, change):
 | 
				
			||||||
 | 
					        # FIXME: if request.data.author?
 | 
				
			||||||
        if not obj.author:
 | 
					        if not obj.author:
 | 
				
			||||||
            obj.author = request.user
 | 
					            obj.author = request.user
 | 
				
			||||||
        obj.save()
 | 
					        obj.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from autocomplete_light.contrib.taggit_field import TaggitWidget, TaggitField
 | 
					 | 
				
			||||||
class PublicationAdmin (MetadataAdmin):
 | 
					class PublicationAdmin (MetadataAdmin):
 | 
				
			||||||
    fieldsets = copy.deepcopy(MetadataAdmin.fieldsets)
 | 
					    fieldsets = copy.deepcopy(MetadataAdmin.fieldsets)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    list_display = ('id', 'title', 'date', 'public', 'parent')
 | 
					    list_display = ('id', 'title', 'date', 'public', 'enumerable', 'parent')
 | 
				
			||||||
    list_filter = ['date', 'public', 'parent', 'author']
 | 
					    list_filter = ['date', 'public', 'parent', 'author']
 | 
				
			||||||
 | 
					    list_editable = ('public', 'enumerable')
 | 
				
			||||||
    search_fields = ['title', 'content']
 | 
					    search_fields = ['title', 'content']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    fieldsets[0][1]['fields'].insert(1, 'subtitle')
 | 
					    fieldsets[0][1]['fields'].insert(1, 'subtitle')
 | 
				
			||||||
    fieldsets[0][1]['fields'] += [ 'img', 'content' ]
 | 
					    fieldsets[0][1]['fields'] += [ 'img', 'content' ]
 | 
				
			||||||
    fieldsets[1][1]['fields'] += [ 'parent' ] #, 'meta' ],
 | 
					    fieldsets[1][1]['fields'] += [ 'parent' ] #, 'meta' ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Sound)
 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# ModelAdmin list
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
class SoundAdmin (MetadataAdmin):
 | 
					class SoundAdmin (MetadataAdmin):
 | 
				
			||||||
    fieldsets = [
 | 
					    fieldsets = [
 | 
				
			||||||
        (None, { 'fields': ['title', 'tags', 'path' ] } ),
 | 
					        (None, { 'fields': ['title', 'tags', 'path' ] } ),
 | 
				
			||||||
@ -79,12 +75,21 @@ class SoundAdmin (MetadataAdmin):
 | 
				
			|||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Stream)
 | 
				
			||||||
 | 
					class StreamAdmin (SortableModelAdmin):
 | 
				
			||||||
 | 
					    list_display = ('id', 'name', 'type', 'public', 'enumerable', 'priority')
 | 
				
			||||||
 | 
					    list_editable = ('public', 'enumerable')
 | 
				
			||||||
 | 
					    sortable = "priority"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Article)
 | 
				
			||||||
class ArticleAdmin (PublicationAdmin):
 | 
					class ArticleAdmin (PublicationAdmin):
 | 
				
			||||||
    fieldsets           = copy.deepcopy(PublicationAdmin.fieldsets)
 | 
					    fieldsets           = copy.deepcopy(PublicationAdmin.fieldsets)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fieldsets[1][1]['fields'] += ['static_page']
 | 
					    fieldsets[1][1]['fields'] += ['static_page']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Program)
 | 
				
			||||||
class ProgramAdmin (PublicationAdmin):
 | 
					class ProgramAdmin (PublicationAdmin):
 | 
				
			||||||
    fieldsets           = copy.deepcopy(PublicationAdmin.fieldsets)
 | 
					    fieldsets           = copy.deepcopy(PublicationAdmin.fieldsets)
 | 
				
			||||||
    inlines             = [ ScheduleInline ]
 | 
					    inlines             = [ ScheduleInline ]
 | 
				
			||||||
@ -92,6 +97,7 @@ class ProgramAdmin (PublicationAdmin):
 | 
				
			|||||||
    fieldsets[1][1]['fields'] += ['email', 'url']
 | 
					    fieldsets[1][1]['fields'] += ['email', 'url']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@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
 | 
				
			||||||
@ -101,18 +107,20 @@ class EpisodeAdmin (PublicationAdmin):
 | 
				
			|||||||
    inlines = (TrackInline, DiffusionInline)
 | 
					    inlines = (TrackInline, DiffusionInline)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Diffusion)
 | 
				
			||||||
class DiffusionAdmin (admin.ModelAdmin):
 | 
					class DiffusionAdmin (admin.ModelAdmin):
 | 
				
			||||||
    list_display = ('type', 'begin', 'end', 'episode', 'program', 'stream')
 | 
					    list_display = ('id', 'type', 'date', 'episode', 'program', 'stream')
 | 
				
			||||||
    list_filter = ('type', 'begin', 'program', 'stream')
 | 
					    list_filter = ('type', 'date', 'program', 'stream')
 | 
				
			||||||
 | 
					    list_editable = ('type', 'date')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_queryset(self, request):
 | 
				
			||||||
 | 
					        qs = super(DiffusionAdmin, self).get_queryset(request)
 | 
				
			||||||
 | 
					        if 'type__exact' in request.GET and \
 | 
				
			||||||
 | 
					                str(Diffusion.Type['unconfirmed']) in request.GET['type__exact']:
 | 
				
			||||||
 | 
					            return qs
 | 
				
			||||||
 | 
					        return qs.exclude(type = Diffusion.Type['unconfirmed'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
admin.site.register(Track)
 | 
					admin.site.register(Track)
 | 
				
			||||||
admin.site.register(Sound, SoundAdmin)
 | 
					 | 
				
			||||||
admin.site.register(Schedule)
 | 
					admin.site.register(Schedule)
 | 
				
			||||||
admin.site.register(Article, ArticleAdmin)
 | 
					 | 
				
			||||||
admin.site.register(Program, ProgramAdmin)
 | 
					 | 
				
			||||||
admin.site.register(Episode, EpisodeAdmin)
 | 
					 | 
				
			||||||
admin.site.register(Diffusion, DiffusionAdmin)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -255,7 +255,6 @@ class Command (BaseCommand):
 | 
				
			|||||||
            parser.epilog += '\n  ' + model.model.type() + ': \n' \
 | 
					            parser.epilog += '\n  ' + model.model.type() + ': \n' \
 | 
				
			||||||
                           + model.to_string()
 | 
					                           + model.to_string()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def handle (self, *args, **options):
 | 
					    def handle (self, *args, **options):
 | 
				
			||||||
        model = options.get('model')
 | 
					        model = options.get('model')
 | 
				
			||||||
        if not model:
 | 
					        if not model:
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,7 @@ 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
 | 
					from django.utils                           import timezone as tz
 | 
				
			||||||
from django.utils.html                      import strip_tags
 | 
					from django.utils.html                      import strip_tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# extensions
 | 
					# extensions
 | 
				
			||||||
@ -16,75 +16,42 @@ from taggit.managers                        import TaggableManager
 | 
				
			|||||||
import programs.settings                    as settings
 | 
					import programs.settings                    as settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def date_or_default (date, date_only = False):
 | 
				
			||||||
# Frequency for schedules. Basically, it is a mask of bits where each bit is a
 | 
					    """
 | 
				
			||||||
# week. Bits > rank 5 are used for special schedules.
 | 
					    Return date or default value (now) if not defined, and remove time info
 | 
				
			||||||
# Important: the first week is always the first week where the weekday of the
 | 
					    if date_only is True
 | 
				
			||||||
# schedule is present.
 | 
					    """
 | 
				
			||||||
Frequency = {
 | 
					    date = date or tz.datetime.today()
 | 
				
			||||||
    'ponctual':         0b000000,
 | 
					    if not tz.is_aware(date):
 | 
				
			||||||
    'first':            0b000001,
 | 
					        date = tz.make_aware(date)
 | 
				
			||||||
    'second':           0b000010,
 | 
					    if date_only:
 | 
				
			||||||
    'third':            0b000100,
 | 
					        return date.replace(hour = 0, minute = 0, second = 0, microsecond = 0)
 | 
				
			||||||
    'fourth':           0b001000,
 | 
					    return date
 | 
				
			||||||
    'last':             0b010000,
 | 
					 | 
				
			||||||
    'first and third':  0b000101,
 | 
					 | 
				
			||||||
    'second and fourth': 0b001010,
 | 
					 | 
				
			||||||
    'every':            0b011111,
 | 
					 | 
				
			||||||
    'one on two':       0b100000,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Translators: html safe values
 | 
					#class Model (models.Model):
 | 
				
			||||||
ugettext_lazy('ponctual')
 | 
					#    @classmethod
 | 
				
			||||||
ugettext_lazy('every')
 | 
					#    def type (cl):
 | 
				
			||||||
ugettext_lazy('first')
 | 
					#        """
 | 
				
			||||||
ugettext_lazy('second')
 | 
					#        Return a string with the type of the model (class name lowered)
 | 
				
			||||||
ugettext_lazy('third')
 | 
					#        """
 | 
				
			||||||
ugettext_lazy('fourth')
 | 
					#        name = cl.__name__.lower()
 | 
				
			||||||
ugettext_lazy('first and third')
 | 
					#        return name
 | 
				
			||||||
ugettext_lazy('second and fourth')
 | 
					
 | 
				
			||||||
ugettext_lazy('one on two')
 | 
					#    @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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DiffusionType = {
 | 
					class Metadata (models.Model):
 | 
				
			||||||
    'diffuse': 0x01,    # the diffusion is planified or done
 | 
					 | 
				
			||||||
    'scheduled': 0x02,  # the diffusion has been scheduled automatically
 | 
					 | 
				
			||||||
    'cancel': 0x03,     # the diffusion has been canceled from grid; useful to
 | 
					 | 
				
			||||||
                        # give the info to the users
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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 type_plural (cl):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Return a string with the name in plural of the model (cf. name())
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        return cl.type() + 's'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @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 (Model):
 | 
					 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    meta is used to extend a model for future needs
 | 
					    meta is used to extend a model for future needs
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@ -99,7 +66,7 @@ class Metadata (Model):
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    date = models.DateTimeField(
 | 
					    date = models.DateTimeField(
 | 
				
			||||||
        _('date'),
 | 
					        _('date'),
 | 
				
			||||||
        default = timezone.datetime.now,
 | 
					        default = tz.datetime.now,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    public = models.BooleanField(
 | 
					    public = models.BooleanField(
 | 
				
			||||||
        _('public'),
 | 
					        _('public'),
 | 
				
			||||||
@ -170,7 +137,7 @@ class Publication (Metadata):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        res = {}
 | 
					        res = {}
 | 
				
			||||||
        res[prefix + 'public'] = False
 | 
					        res[prefix + 'public'] = False
 | 
				
			||||||
        res[prefix + 'date__gt'] = timezone.now()
 | 
					        res[prefix + 'date__gt'] = tz.now()
 | 
				
			||||||
        return res
 | 
					        return res
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
@ -182,7 +149,7 @@ class Publication (Metadata):
 | 
				
			|||||||
        Otherwise, return None
 | 
					        Otherwise, return None
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        kwargs['public'] = True
 | 
					        kwargs['public'] = True
 | 
				
			||||||
        kwargs['date__lte'] = timezone.now()
 | 
					        kwargs['date__lte'] = tz.now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        e = cl.objects.filter(**kwargs)
 | 
					        e = cl.objects.filter(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -197,8 +164,7 @@ class Publication (Metadata):
 | 
				
			|||||||
        abstract = True
 | 
					        abstract = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Track (models.Model):
 | 
				
			||||||
class Track (Model):
 | 
					 | 
				
			||||||
    # There are no nice solution for M2M relations ship (even without
 | 
					    # There are no nice solution for M2M relations ship (even without
 | 
				
			||||||
    # through) in django-admin. So we unfortunately need to make one-
 | 
					    # through) in django-admin. So we unfortunately need to make one-
 | 
				
			||||||
    # to-one relations and add a position argument
 | 
					    # to-one relations and add a position argument
 | 
				
			||||||
@ -250,6 +216,11 @@ class Sound (Metadata):
 | 
				
			|||||||
        recursive = True,
 | 
					        recursive = True,
 | 
				
			||||||
        blank = True, null = True,
 | 
					        blank = True, null = True,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    embed = models.TextField(
 | 
				
			||||||
 | 
					        _('embed HTML code from external website'),
 | 
				
			||||||
 | 
					        blank = True, null = True,
 | 
				
			||||||
 | 
					        help_text = _('if set, consider the sound podcastable'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    duration = models.TimeField(
 | 
					    duration = models.TimeField(
 | 
				
			||||||
        _('duration'),
 | 
					        _('duration'),
 | 
				
			||||||
        blank = True, null = True,
 | 
					        blank = True, null = True,
 | 
				
			||||||
@ -259,11 +230,6 @@ class Sound (Metadata):
 | 
				
			|||||||
        default = False,
 | 
					        default = False,
 | 
				
			||||||
        help_text = _("the file has been cut"),
 | 
					        help_text = _("the file has been cut"),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    embed = models.TextField(
 | 
					 | 
				
			||||||
        _('embed HTML code from external website'),
 | 
					 | 
				
			||||||
        blank = True, null = True,
 | 
					 | 
				
			||||||
        help_text = _('if set, consider the sound podcastable'),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    removed = models.BooleanField(
 | 
					    removed = models.BooleanField(
 | 
				
			||||||
        default = False,
 | 
					        default = False,
 | 
				
			||||||
        help_text = _('this sound has been removed from filesystem'),
 | 
					        help_text = _('this sound has been removed from filesystem'),
 | 
				
			||||||
@ -274,8 +240,8 @@ class Sound (Metadata):
 | 
				
			|||||||
        Get the last modification date from file
 | 
					        Get the last modification date from file
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        mtime = os.stat(self.path).st_mtime
 | 
					        mtime = os.stat(self.path).st_mtime
 | 
				
			||||||
        mtime = timezone.datetime.fromtimestamp(mtime)
 | 
					        mtime = tz.datetime.fromtimestamp(mtime)
 | 
				
			||||||
        return timezone.make_aware(mtime, timezone.get_current_timezone())
 | 
					        return tz.make_aware(mtime, timezone.get_current_timezone())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def save (self, *args, **kwargs):
 | 
					    def save (self, *args, **kwargs):
 | 
				
			||||||
        if not self.pk:
 | 
					        if not self.pk:
 | 
				
			||||||
@ -290,15 +256,33 @@ class Sound (Metadata):
 | 
				
			|||||||
        verbose_name_plural = _('Sounds')
 | 
					        verbose_name_plural = _('Sounds')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Schedule (Model):
 | 
					class Schedule (models.Model):
 | 
				
			||||||
 | 
					    # 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.
 | 
				
			||||||
 | 
					    # For ponctual programs, there is no need for a schedule, only a diffusion
 | 
				
			||||||
 | 
					    Frequency = {
 | 
				
			||||||
 | 
					        'first':            0b000001,
 | 
				
			||||||
 | 
					        'second':           0b000010,
 | 
				
			||||||
 | 
					        'third':            0b000100,
 | 
				
			||||||
 | 
					        'fourth':           0b001000,
 | 
				
			||||||
 | 
					        'last':             0b010000,
 | 
				
			||||||
 | 
					        'first and third':  0b000101,
 | 
				
			||||||
 | 
					        'second and fourth': 0b001010,
 | 
				
			||||||
 | 
					        'every':            0b011111,
 | 
				
			||||||
 | 
					        'one on two':       0b100000,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for key, value in Frequency.items():
 | 
				
			||||||
 | 
					        ugettext_lazy(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parent = models.ForeignKey(
 | 
					    parent = models.ForeignKey(
 | 
				
			||||||
        'Program',
 | 
					        'Program',
 | 
				
			||||||
        blank = True, null = True,
 | 
					        blank = True, null = True,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    begin = models.DateTimeField(_('begin'))
 | 
					    date = models.DateTimeField(_('date'))
 | 
				
			||||||
    end = models.DateTimeField(
 | 
					    duration = models.TimeField(
 | 
				
			||||||
        _('end'),
 | 
					        _('duration'),
 | 
				
			||||||
        blank = True, null = True,
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    frequency = models.SmallIntegerField(
 | 
					    frequency = models.SmallIntegerField(
 | 
				
			||||||
        _('frequency'),
 | 
					        _('frequency'),
 | 
				
			||||||
@ -310,16 +294,14 @@ class Schedule (Model):
 | 
				
			|||||||
        help_text = "Schedule of a rerun",
 | 
					        help_text = "Schedule of a rerun",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def match (self, date = None, check_time = False):
 | 
					    def match (self, date = None, check_time = True):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Return True if the given datetime matches the schedule
 | 
					        Return True if the given datetime matches the schedule
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if not date:
 | 
					        date = date_or_default(date)
 | 
				
			||||||
            date = timezone.datetime.today()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.date.weekday() == date.weekday() and self.match_week(date):
 | 
					        if self.date.weekday() == date.weekday() and self.match_week(date):
 | 
				
			||||||
            return (check_time and self.date.time() == date.date.time()) \
 | 
					            return self.date.time() == date.time() if check_time else True
 | 
				
			||||||
                    or True
 | 
					 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def match_week (self, date = None):
 | 
					    def match_week (self, date = None):
 | 
				
			||||||
@ -328,17 +310,12 @@ class Schedule (Model):
 | 
				
			|||||||
        otherwise.
 | 
					        otherwise.
 | 
				
			||||||
        If the schedule is ponctual, return None.
 | 
					        If the schedule is ponctual, return None.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if not date:
 | 
					        date = date_or_default(date)
 | 
				
			||||||
            date = timezone.datetime.today()
 | 
					        if self.frequency == Schedule.Frequency['one on two']:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self.frequency == Frequency['ponctual']:
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self.frequency == Frequency['one on two']:
 | 
					 | 
				
			||||||
            week = date.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(date.year, date.month, 1)
 | 
					        first_of_month = tz.datetime.date(date.year, date.month, 1)
 | 
				
			||||||
        week = date.isocalendar()[1] - first_of_month.isocalendar()[1]
 | 
					        week = date.isocalendar()[1] - first_of_month.isocalendar()[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # weeks of month
 | 
					        # weeks of month
 | 
				
			||||||
@ -355,32 +332,23 @@ class Schedule (Model):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def dates_of_month (self, date = None):
 | 
					    def dates_of_month (self, date = None):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Return a list with all matching dates of the month of the given
 | 
					        Return a list with all matching dates of date.month (=today)
 | 
				
			||||||
        date (= today).
 | 
					 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if self.frequency == Frequency['ponctual']:
 | 
					        date = date_or_default(date, True).replace(day=1)
 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not date:
 | 
					 | 
				
			||||||
            date = timezone.datetime.today()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        date = timezone.datetime(year = date.year, month = date.month, day = 1)
 | 
					 | 
				
			||||||
        wday = self.date.weekday()
 | 
					        wday = self.date.weekday()
 | 
				
			||||||
        fwday = date.weekday()
 | 
					        fwday = date.weekday()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # move date to the date weekday of the schedule
 | 
					        # move date to the date weekday of the schedule
 | 
				
			||||||
        # check on SO#3284452 for the formula
 | 
					        # check on SO#3284452 for the formula
 | 
				
			||||||
        date += timezone.timedelta(
 | 
					        date += tz.timedelta(days = (7 if fwday > wday else 0) - fwday + wday)
 | 
				
			||||||
            days = (7 if fwday > wday else 0) - fwday + wday
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        fwday = date.weekday()
 | 
					        fwday = date.weekday()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # special frequency case
 | 
					        # special frequency case
 | 
				
			||||||
        weeks = self.frequency
 | 
					        weeks = self.frequency
 | 
				
			||||||
        if self.frequency == Frequency['last']:
 | 
					        if self.frequency == Schedule.Frequency['last']:
 | 
				
			||||||
            date += timezone.timedelta(month = 1, days = -7)
 | 
					            date += tz.timedelta(month = 1, days = -7)
 | 
				
			||||||
            return self.normalize([date])
 | 
					            return self.normalize([date])
 | 
				
			||||||
        if weeks == Frequency['one on two']:
 | 
					        if weeks == Schedule.Frequency['one on two']:
 | 
				
			||||||
            # if both week are the same, then the date week of the month
 | 
					            # if both week are the same, then the date week of the month
 | 
				
			||||||
            # matches. Note: wday % 2 + fwday % 2 => (wday + fwday) % 2
 | 
					            # matches. Note: wday % 2 + fwday % 2 => (wday + fwday) % 2
 | 
				
			||||||
            fweek = date.isocalendar()[1]
 | 
					            fweek = date.isocalendar()[1]
 | 
				
			||||||
@ -392,61 +360,55 @@ class Schedule (Model):
 | 
				
			|||||||
            # there can be five weeks in a month
 | 
					            # there can be five weeks in a month
 | 
				
			||||||
            if not weeks & (0b1 << week):
 | 
					            if not weeks & (0b1 << week):
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					            wdate = date + tz.timedelta(days = week * 7)
 | 
				
			||||||
            wdate = date + timezone.timedelta(days = week * 7)
 | 
					 | 
				
			||||||
            if wdate.month == date.month:
 | 
					            if wdate.month == date.month:
 | 
				
			||||||
                dates.append(self.normalize(wdate))
 | 
					                dates.append(self.normalize(wdate))
 | 
				
			||||||
        return dates
 | 
					        return dates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def diffusions_of_month (self, date = None, exclude_saved = False):
 | 
					    def diffusions_of_month (self, date, exclude_saved = False):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Return a list of generated (unsaved) diffusions for this program for the
 | 
					        Return a list of Diffusion instances, from month of the given date, that
 | 
				
			||||||
        month of the given date. If exclude_saved, exclude all diffusions that
 | 
					        can be not in the database.
 | 
				
			||||||
        are yet in the database.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        When a diffusion is created, it tries to attach the corresponding
 | 
					        If exclude_saved, exclude all diffusions that are yet in the database.
 | 
				
			||||||
        episode.
 | 
					
 | 
				
			||||||
 | 
					        When a Diffusion is created, it tries to attach the corresponding
 | 
				
			||||||
 | 
					        episode using a match of episode.date (and takes care of rerun case);
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if not date:
 | 
					        dates = self.dates_of_month(date)
 | 
				
			||||||
            date = timezone.datetime.today()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        dates = self.dates_of_month()
 | 
					 | 
				
			||||||
        saved = Diffusion.objects.filter(date__in = dates,
 | 
					        saved = Diffusion.objects.filter(date__in = dates,
 | 
				
			||||||
                                         program = self.parent)
 | 
					                                         program = self.parent)
 | 
				
			||||||
        diffusions = []
 | 
					        diffusions = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # existing diffusions
 | 
					        # existing diffusions
 | 
				
			||||||
        for saved_item in saved:
 | 
					        for item in saved:
 | 
				
			||||||
            dates.remove(saved_item.date)
 | 
					            if item.date in dates:
 | 
				
			||||||
 | 
					                dates.remove(item.date)
 | 
				
			||||||
            if not exclude_saved:
 | 
					            if not exclude_saved:
 | 
				
			||||||
                diffusions.append(saved_item)
 | 
					                diffusions.append(item)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # others
 | 
					        # others
 | 
				
			||||||
        for date in dates:
 | 
					        for date in dates:
 | 
				
			||||||
            # get episode
 | 
					 | 
				
			||||||
            ep_date = date
 | 
					            ep_date = date
 | 
				
			||||||
            if self.rerun:
 | 
					            if self.rerun:
 | 
				
			||||||
                ep_date = self.rerun.date
 | 
					                ep_date -= self.date - self.rerun.date
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            episode = Episode.objects().filter(date = ep_date,
 | 
					            episode = Episode.objects.filter(date = date,
 | 
				
			||||||
                                               parent = self.parent)
 | 
					                                             parent = self.parent)
 | 
				
			||||||
            episode  = episode[0] if episode.count() else None
 | 
					            episode = episode[0] if episode.count() else None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # make diffusion
 | 
					            diffusions.append(Diffusion(
 | 
				
			||||||
            diffusion = Diffusion(
 | 
					                                 episode = episode,
 | 
				
			||||||
                episode = episode,
 | 
					                                 program = self.parent,
 | 
				
			||||||
                program = self.parent,
 | 
					                                 stream = self.parent.stream,
 | 
				
			||||||
                type = DiffusionType['scheduled'],
 | 
					                                 type = Diffusion.Type['unconfirmed'],
 | 
				
			||||||
                begin = date,
 | 
					                                 date = date,
 | 
				
			||||||
                end = timezone.datetime.combine(date.date(), self.end.time()),
 | 
					                             ))
 | 
				
			||||||
                stream = settings.AIRCOX_SCHEDULED_STREAM
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            diffusion.program = self.program
 | 
					 | 
				
			||||||
            diffusions.append(diffusion)
 | 
					 | 
				
			||||||
        return diffusions
 | 
					        return diffusions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__ (self):
 | 
					    def __str__ (self):
 | 
				
			||||||
        frequency = [ x for x,y in Frequency.items() if y == self.frequency ]
 | 
					        frequency = [ x for x,y in Schedule.Frequency.items()
 | 
				
			||||||
 | 
					                        if y == self.frequency ]
 | 
				
			||||||
        return self.parent.title + ': ' + frequency[0]
 | 
					        return self.parent.title + ': ' + frequency[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
@ -454,7 +416,104 @@ class Schedule (Model):
 | 
				
			|||||||
        verbose_name_plural = _('Schedules')
 | 
					        verbose_name_plural = _('Schedules')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Diffusion (models.Model):
 | 
				
			||||||
 | 
					    Type = {
 | 
				
			||||||
 | 
					        'normal':       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
 | 
				
			||||||
 | 
					        'stop':         0x04,   # diffusion has been forced to stop
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for key, value in Type.items():
 | 
				
			||||||
 | 
					        ugettext_lazy(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    episode = models.ForeignKey (
 | 
				
			||||||
 | 
					        'Episode',
 | 
				
			||||||
 | 
					        blank = True, null = True,
 | 
				
			||||||
 | 
					        verbose_name = _('episode'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    program = models.ForeignKey (
 | 
				
			||||||
 | 
					        'Program',
 | 
				
			||||||
 | 
					        verbose_name = _('program'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    # program.stream can change, but not the stream;
 | 
				
			||||||
 | 
					    stream = models.ForeignKey(
 | 
				
			||||||
 | 
					        'Stream',
 | 
				
			||||||
 | 
					        verbose_name = _('stream'),
 | 
				
			||||||
 | 
					        default = 0,
 | 
				
			||||||
 | 
					        help_text = 'stream id on which the diffusion happens',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    type = models.SmallIntegerField(
 | 
				
			||||||
 | 
					        verbose_name = _('type'),
 | 
				
			||||||
 | 
					        choices = [ (y, x) for x,y in Type.items() ],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    date = models.DateTimeField( _('start of the diffusion') )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def save (self, *args, **kwargs):
 | 
				
			||||||
 | 
					        if self.episode: # FIXME self.episode or kwargs['episode']
 | 
				
			||||||
 | 
					            self.program = self.episode.parent
 | 
				
			||||||
 | 
					        # check type against stream's type
 | 
				
			||||||
 | 
					        super(Diffusion, self).save(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__ (self):
 | 
				
			||||||
 | 
					        return self.program.title + ' on ' + str(self.date) \
 | 
				
			||||||
 | 
					               + str(self.type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        verbose_name = _('Diffusion')
 | 
				
			||||||
 | 
					        verbose_name_plural = _('Diffusions')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Stream (models.Model):
 | 
				
			||||||
 | 
					    Type = {
 | 
				
			||||||
 | 
					        'random':   0x00,   # selection using random function
 | 
				
			||||||
 | 
					        'schedule': 0x01,   # selection using schedule
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for key, value in Type.items():
 | 
				
			||||||
 | 
					        ugettext_lazy(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # FIXME: id as integer?
 | 
				
			||||||
 | 
					    name = models.CharField(
 | 
				
			||||||
 | 
					        _('name'),
 | 
				
			||||||
 | 
					        max_length = 32,
 | 
				
			||||||
 | 
					        blank = True,
 | 
				
			||||||
 | 
					        null = True,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    type = models.SmallIntegerField(
 | 
				
			||||||
 | 
					        verbose_name = _('type'),
 | 
				
			||||||
 | 
					        choices = [ (y, x) for x,y in Type.items() ],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    # FIXME unique value / suit's orderable
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    priority = models.SmallIntegerField(
 | 
				
			||||||
 | 
					        _('priority'),
 | 
				
			||||||
 | 
					        default = 0,
 | 
				
			||||||
 | 
					        help_text = _('priority of the stream')
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    public = models.BooleanField(
 | 
				
			||||||
 | 
					        _('public'),
 | 
				
			||||||
 | 
					        default = True,
 | 
				
			||||||
 | 
					        help_text = _('content is public'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    enumerable = models.BooleanField(
 | 
				
			||||||
 | 
					        _('enumerable'),
 | 
				
			||||||
 | 
					        default = True,
 | 
				
			||||||
 | 
					        help_text = _('publication is listable'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # get info for:
 | 
				
			||||||
 | 
					    # - random lists
 | 
				
			||||||
 | 
					    # - scheduled lists
 | 
				
			||||||
 | 
					    # link between Streams and Programs:
 | 
				
			||||||
 | 
					    #   - hours range (non-stop)
 | 
				
			||||||
 | 
					    #   - stream/pgm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__ (self):
 | 
				
			||||||
 | 
					        return self.name + ' / ' + str(self.priority)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Article (Publication):
 | 
					class Article (Publication):
 | 
				
			||||||
 | 
					    # FIXME: move to website?
 | 
				
			||||||
    parent = models.ForeignKey(
 | 
					    parent = models.ForeignKey(
 | 
				
			||||||
        'self',
 | 
					        'self',
 | 
				
			||||||
        verbose_name = _('parent'),
 | 
					        verbose_name = _('parent'),
 | 
				
			||||||
@ -482,6 +541,10 @@ class Program (Publication):
 | 
				
			|||||||
        blank = True, null = True,
 | 
					        blank = True, null = True,
 | 
				
			||||||
        help_text = _('parent article'),
 | 
					        help_text = _('parent article'),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    stream = models.ForeignKey(
 | 
				
			||||||
 | 
					        Stream,
 | 
				
			||||||
 | 
					        verbose_name = _('stream'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    email = models.EmailField(
 | 
					    email = models.EmailField(
 | 
				
			||||||
        _('email'),
 | 
					        _('email'),
 | 
				
			||||||
        max_length = 128,
 | 
					        max_length = 128,
 | 
				
			||||||
@ -491,9 +554,10 @@ class Program (Publication):
 | 
				
			|||||||
        _('website'),
 | 
					        _('website'),
 | 
				
			||||||
        blank = True, null = True,
 | 
					        blank = True, null = True,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    non_stop = models.BooleanField(
 | 
					    active = models.BooleanField(
 | 
				
			||||||
        _('non-stop'),
 | 
					        _('inactive'),
 | 
				
			||||||
        default = False,
 | 
					        default = True,
 | 
				
			||||||
 | 
					        help_text = _('if not set this program is no longer active')
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
@ -507,21 +571,11 @@ class Program (Publication):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        schedules = Schedule.objects.filter(parent = self)
 | 
					        schedules = Schedule.objects.filter(parent = self)
 | 
				
			||||||
        for schedule in schedules:
 | 
					        for schedule in schedules:
 | 
				
			||||||
            if schedule.match(date):
 | 
					            if schedule.match(date, check_time = False):
 | 
				
			||||||
                return schedule
 | 
					                return schedule
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        verbose_name = _('Program')
 | 
					 | 
				
			||||||
        verbose_name_plural = _('Programs')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Episode (Publication):
 | 
					class Episode (Publication):
 | 
				
			||||||
    # Note:
 | 
					 | 
				
			||||||
    #   We do not especially need a duration here, because even if an
 | 
					 | 
				
			||||||
    #   program's schedule can have specified durations, in practice this
 | 
					 | 
				
			||||||
    #   duration may vary. Furthermore, we want the users have to enter a
 | 
					 | 
				
			||||||
    #   minimum of values.
 | 
					 | 
				
			||||||
    #   Duration can be retrieved from the sound file if there is one.
 | 
					 | 
				
			||||||
    parent = models.ForeignKey(
 | 
					    parent = models.ForeignKey(
 | 
				
			||||||
        Program,
 | 
					        Program,
 | 
				
			||||||
        verbose_name = _('parent'),
 | 
					        verbose_name = _('parent'),
 | 
				
			||||||
@ -538,49 +592,3 @@ class Episode (Publication):
 | 
				
			|||||||
        verbose_name_plural = _('Episodes')
 | 
					        verbose_name_plural = _('Episodes')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
class Diffusion (Model):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Diffusion logs and planifications.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    A diffusion is:
 | 
					 | 
				
			||||||
    - scheduled: when it has been generated following programs' Schedule
 | 
					 | 
				
			||||||
    - planified: when it has been generated manually/ponctually or scheduled
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    episode = models.ForeignKey (
 | 
					 | 
				
			||||||
        Episode,
 | 
					 | 
				
			||||||
        blank = True, null = True,
 | 
					 | 
				
			||||||
        verbose_name = _('episode'),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    program = models.ForeignKey (
 | 
					 | 
				
			||||||
        Program,
 | 
					 | 
				
			||||||
        verbose_name = _('program'),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    type = models.SmallIntegerField(
 | 
					 | 
				
			||||||
        verbose_name = _('type'),
 | 
					 | 
				
			||||||
        choices = [ (y, x) for x,y in DiffusionType.items() ],
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    begin = models.DateTimeField( _('start of the diffusion') )
 | 
					 | 
				
			||||||
    end = models.DateTimeField(
 | 
					 | 
				
			||||||
        _('end of the diffusion'),
 | 
					 | 
				
			||||||
        blank = True, null = True,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    stream = models.SmallIntegerField(
 | 
					 | 
				
			||||||
        verbose_name = _('stream'),
 | 
					 | 
				
			||||||
        default = 0,
 | 
					 | 
				
			||||||
        help_text = 'stream id on which the diffusion happens',
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def save (self, *args, **kwargs):
 | 
					 | 
				
			||||||
        if self.episode:
 | 
					 | 
				
			||||||
            self.program = self.episode.parent
 | 
					 | 
				
			||||||
        super(Diffusion, self).save(*args, **kwargs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __str__ (self):
 | 
					 | 
				
			||||||
        return self.program.title + ' on ' + str(self.start) \
 | 
					 | 
				
			||||||
               + str(self.type)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        verbose_name = _('Diffusion')
 | 
					 | 
				
			||||||
        verbose_name_plural = _('Diffusions')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
Django>=1.9.0
 | 
					Django>=1.9.0
 | 
				
			||||||
taggit>=0.12.1
 | 
					django-taggit>=0.12.1
 | 
				
			||||||
sortedm2m>=1.0.2
 | 
					django-autocomplete-light>=2.2.5
 | 
				
			||||||
 | 
					django-suit>=0.2.14
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user