update
This commit is contained in:
		
							
								
								
									
										7
									
								
								aircox/admin/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								aircox/admin/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					from .article import ArticleAdmin
 | 
				
			||||||
 | 
					from .episode import DiffusionAdmin, EpisodeAdmin
 | 
				
			||||||
 | 
					from .log import LogAdmin
 | 
				
			||||||
 | 
					from .program import ProgramAdmin, ScheduleAdmin, StreamAdmin
 | 
				
			||||||
 | 
					from .sound import SoundAdmin, TrackAdmin
 | 
				
			||||||
 | 
					from .station import StationAdmin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										18
									
								
								aircox/admin/article.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								aircox/admin/article.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					import copy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..models import Article
 | 
				
			||||||
 | 
					from .page import PageAdmin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__all__ = ['ArticleAdmin']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Article)
 | 
				
			||||||
 | 
					class ArticleAdmin(PageAdmin):
 | 
				
			||||||
 | 
					    list_filter = PageAdmin.list_filter
 | 
				
			||||||
 | 
					    search_fields = PageAdmin.search_fields + ['parent__title']
 | 
				
			||||||
 | 
					    # TODO: readonly field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										58
									
								
								aircox/admin/episode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								aircox/admin/episode.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					import copy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.utils.translation import ugettext as _, ugettext_lazy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..models import Episode, Diffusion, Sound, Track
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .page import PageAdmin
 | 
				
			||||||
 | 
					from .sound import SoundInline, TracksInline
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DiffusionBaseAdmin:
 | 
				
			||||||
 | 
					    fields = ['type', 'start', 'end']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_readonly_fields(self, request, obj=None):
 | 
				
			||||||
 | 
					        fields = super().get_readonly_fields(request, obj)
 | 
				
			||||||
 | 
					        if not request.user.has_perm('aircox_program.scheduling'):
 | 
				
			||||||
 | 
					            fields += ['program', 'start', 'end']
 | 
				
			||||||
 | 
					        return [field for field in fields if field in self.fields]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Diffusion)
 | 
				
			||||||
 | 
					class DiffusionAdmin(DiffusionBaseAdmin, admin.ModelAdmin):
 | 
				
			||||||
 | 
					    def start_date(self, obj):
 | 
				
			||||||
 | 
					        return obj.local_start.strftime('%Y/%m/%d %H:%M')
 | 
				
			||||||
 | 
					    start_date.short_description = _('start')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def end_date(self, obj):
 | 
				
			||||||
 | 
					        return obj.local_end.strftime('%H:%M')
 | 
				
			||||||
 | 
					    end_date.short_description = _('end')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list_display = ('episode', 'start_date', 'end_date', 'type', 'initial')
 | 
				
			||||||
 | 
					    list_filter = ('type', 'start', 'program')
 | 
				
			||||||
 | 
					    list_editable = ('type',)
 | 
				
			||||||
 | 
					    ordering = ('-start', 'id')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fields = ['type', 'start', 'end', 'initial', 'program']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DiffusionInline(DiffusionBaseAdmin, admin.TabularInline):
 | 
				
			||||||
 | 
					    model = Diffusion
 | 
				
			||||||
 | 
					    fk_name = 'episode'
 | 
				
			||||||
 | 
					    extra = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def has_add_permission(self, request):
 | 
				
			||||||
 | 
					        return request.user.has_perm('aircox_program.scheduling')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Episode)
 | 
				
			||||||
 | 
					class EpisodeAdmin(PageAdmin):
 | 
				
			||||||
 | 
					    list_display = PageAdmin.list_display
 | 
				
			||||||
 | 
					    list_filter = PageAdmin.list_filter
 | 
				
			||||||
 | 
					    search_fields = PageAdmin.search_fields + ['parent__title']
 | 
				
			||||||
 | 
					    # readonly_fields = ('parent',)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inlines = [TracksInline, SoundInline, DiffusionInline]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										13
									
								
								aircox/admin/log.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								aircox/admin/log.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..models import Log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__all__ = ['LogAdmin']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Log)
 | 
				
			||||||
 | 
					class LogAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    list_display = ['id', 'date', 'station', 'source', 'type', 'comment']
 | 
				
			||||||
 | 
					    list_filter = ['date', 'source', 'station']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										42
									
								
								aircox/admin/mixins.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								aircox/admin/mixins.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					class UnrelatedInlineMixin:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Inline class that can be included in an admin change view whose model
 | 
				
			||||||
 | 
					    is not directly related to inline's model.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    view_model = None
 | 
				
			||||||
 | 
					    parent_model = None
 | 
				
			||||||
 | 
					    parent_fk = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, parent_model, admin_site):
 | 
				
			||||||
 | 
					        self.view_model = parent_model
 | 
				
			||||||
 | 
					        super().__init__(self.parent_model, admin_site)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_parent(self, view_obj):
 | 
				
			||||||
 | 
					        """ Get formset's instance from `obj` of AdminSite's change form. """
 | 
				
			||||||
 | 
					        field = self.parent_model._meta.get_field(self.parent_fk).remote_field
 | 
				
			||||||
 | 
					        return getattr(view_obj, field.name, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def save_parent(self, parent, view_obj):
 | 
				
			||||||
 | 
					        """ Save formset's instance. """
 | 
				
			||||||
 | 
					        setattr(parent, self.parent_fk, view_obj)
 | 
				
			||||||
 | 
					        parent.save()
 | 
				
			||||||
 | 
					        return parent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_formset(self, request, obj):
 | 
				
			||||||
 | 
					        ParentFormSet = super().get_formset(request, obj)
 | 
				
			||||||
 | 
					        inline = self
 | 
				
			||||||
 | 
					        class FormSet(ParentFormSet):
 | 
				
			||||||
 | 
					            view_obj = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def __init__(self, *args, instance=None, **kwargs):
 | 
				
			||||||
 | 
					                self.view_obj = instance
 | 
				
			||||||
 | 
					                instance = inline.get_parent(instance)
 | 
				
			||||||
 | 
					                self.instance = instance
 | 
				
			||||||
 | 
					                super().__init__(*args, instance=instance, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def save(self):
 | 
				
			||||||
 | 
					                inline.save_parent(self.instance, self.view_obj)
 | 
				
			||||||
 | 
					                return super().save()
 | 
				
			||||||
 | 
					        return FormSet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										52
									
								
								aircox/admin/page.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								aircox/admin/page.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.utils.safestring import mark_safe
 | 
				
			||||||
 | 
					from django.utils.translation import ugettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from adminsortable2.admin import SortableInlineAdminMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..models import Category, Article, NavItem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__all__ = ['CategoryAdmin', 'PageAdmin', 'NavItemInline']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Category)
 | 
				
			||||||
 | 
					class CategoryAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    list_display = ['pk', 'title', 'slug']
 | 
				
			||||||
 | 
					    list_editable = ['title', 'slug']
 | 
				
			||||||
 | 
					    search_fields = ['title']
 | 
				
			||||||
 | 
					    fields = ['title', 'slug']
 | 
				
			||||||
 | 
					    prepopulated_fields = {"slug": ("title",)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# limit category choice
 | 
				
			||||||
 | 
					class PageAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    list_display = ('cover_thumb', 'title', 'status', 'category', 'parent')
 | 
				
			||||||
 | 
					    list_display_links = ('cover_thumb', 'title')
 | 
				
			||||||
 | 
					    list_editable = ('status', 'category')
 | 
				
			||||||
 | 
					    list_filter = ('status', 'category')
 | 
				
			||||||
 | 
					    prepopulated_fields = {"slug": ("title",)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    search_fields = ['title', 'category__title']
 | 
				
			||||||
 | 
					    fieldsets = [
 | 
				
			||||||
 | 
					        ('', {
 | 
				
			||||||
 | 
					            'fields': ['title', 'slug', 'category', 'cover', 'content'],
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        (_('Publication Settings'), {
 | 
				
			||||||
 | 
					            'fields': ['featured', 'allow_comments', 'status', 'parent'],
 | 
				
			||||||
 | 
					            'classes': ('collapse',),
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    change_form_template = 'admin/aircox/page_change_form.html'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def cover_thumb(self, obj):
 | 
				
			||||||
 | 
					        return mark_safe('<img src="{}"/>'.format(obj.cover.icons['64'])) \
 | 
				
			||||||
 | 
					            if obj.cover else ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NavItemInline(SortableInlineAdminMixin, admin.TabularInline):
 | 
				
			||||||
 | 
					    model = NavItem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										76
									
								
								aircox/admin/program.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								aircox/admin/program.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					from copy import deepcopy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.utils.translation import ugettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..models import Program, Schedule, Stream
 | 
				
			||||||
 | 
					from .page import PageAdmin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ScheduleInline(admin.TabularInline):
 | 
				
			||||||
 | 
					    model = Schedule
 | 
				
			||||||
 | 
					    extra = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StreamInline(admin.TabularInline):
 | 
				
			||||||
 | 
					    fields = ['delay', 'begin', 'end']
 | 
				
			||||||
 | 
					    model = Stream
 | 
				
			||||||
 | 
					    extra = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Program)
 | 
				
			||||||
 | 
					class ProgramAdmin(PageAdmin):
 | 
				
			||||||
 | 
					    def schedule(self, obj):
 | 
				
			||||||
 | 
					        return Schedule.objects.filter(program=obj).count() > 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    schedule.boolean = True
 | 
				
			||||||
 | 
					    schedule.short_description = _("Schedule")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list_display = PageAdmin.list_display + ('schedule', 'station', 'active')
 | 
				
			||||||
 | 
					    list_filter = PageAdmin.list_filter + ('station', 'active')
 | 
				
			||||||
 | 
					    fieldsets = deepcopy(PageAdmin.fieldsets) + [
 | 
				
			||||||
 | 
					        (_('Program Settings'), {
 | 
				
			||||||
 | 
					            'fields': ['active', 'station', 'sync'],
 | 
				
			||||||
 | 
					            'classes': ('collapse',),
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    prepopulated_fields = {'slug': ('title',)}
 | 
				
			||||||
 | 
					    search_fields = ['title']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inlines = [ScheduleInline, StreamInline]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Schedule)
 | 
				
			||||||
 | 
					class ScheduleAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    def program_title(self, obj):
 | 
				
			||||||
 | 
					        return obj.program.title
 | 
				
			||||||
 | 
					    program_title.short_description = _('Program')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def freq(self, obj):
 | 
				
			||||||
 | 
					        return obj.get_frequency_verbose()
 | 
				
			||||||
 | 
					    freq.short_description = _('Day')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def rerun(self, obj):
 | 
				
			||||||
 | 
					        return obj.initial is not None
 | 
				
			||||||
 | 
					    rerun.short_description = _('Rerun')
 | 
				
			||||||
 | 
					    rerun.boolean = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list_filter = ['frequency', 'program']
 | 
				
			||||||
 | 
					    list_display = ['program_title', 'freq', 'time', 'timezone', 'duration',
 | 
				
			||||||
 | 
					                    'rerun']
 | 
				
			||||||
 | 
					    list_editable = ['time', 'duration']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_readonly_fields(self, request, obj=None):
 | 
				
			||||||
 | 
					        if obj:
 | 
				
			||||||
 | 
					            return ['program', 'date', 'frequency']
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Stream)
 | 
				
			||||||
 | 
					class StreamAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    list_display = ('id', 'program', 'delay', 'begin', 'end')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										65
									
								
								aircox/admin/sound.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								aircox/admin/sound.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.utils.translation import ugettext as _, ugettext_lazy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from adminsortable2.admin import SortableInlineAdminMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..models import Sound, Track
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TracksInline(SortableInlineAdminMixin, admin.TabularInline):
 | 
				
			||||||
 | 
					    template = 'admin/aircox/playlist_inline.html'
 | 
				
			||||||
 | 
					    model = Track
 | 
				
			||||||
 | 
					    extra = 0
 | 
				
			||||||
 | 
					    fields = ('position', 'artist', 'title', 'info', 'timestamp', 'tags')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list_display = ['artist', 'title', 'tags', 'related']
 | 
				
			||||||
 | 
					    list_filter = ['artist', 'title', 'tags']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SoundInline(admin.TabularInline):
 | 
				
			||||||
 | 
					    model = Sound
 | 
				
			||||||
 | 
					    fields = ['type', 'path', 'embed', 'duration', 'is_public']
 | 
				
			||||||
 | 
					    readonly_fields = ['type', 'path', 'duration']
 | 
				
			||||||
 | 
					    extra = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Sound)
 | 
				
			||||||
 | 
					class SoundAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    def filename(self, obj):
 | 
				
			||||||
 | 
					        return '/'.join(obj.path.split('/')[-2:])
 | 
				
			||||||
 | 
					    filename.short_description=_('file')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fields = None
 | 
				
			||||||
 | 
					    list_display = ['id', 'name', 'program', 'type', 'duration',
 | 
				
			||||||
 | 
					                    'is_public', 'is_good_quality', 'episode', 'filename']
 | 
				
			||||||
 | 
					    list_filter = ('program', 'type', 'is_good_quality', 'is_public')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    search_fields = ['name', 'program']
 | 
				
			||||||
 | 
					    fieldsets = [
 | 
				
			||||||
 | 
					        (None, {'fields': ['name', 'path', 'type', 'program', 'episode']}),
 | 
				
			||||||
 | 
					        (None, {'fields': ['embed', 'duration', 'is_public', 'mtime']}),
 | 
				
			||||||
 | 
					        (None, {'fields': ['is_good_quality']})
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    readonly_fields = ('path', 'duration',)
 | 
				
			||||||
 | 
					    inlines = [TracksInline]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Track)
 | 
				
			||||||
 | 
					class TrackAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    def tag_list(self, obj):
 | 
				
			||||||
 | 
					        return u", ".join(o.name for o in obj.tags.all())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list_display = ['pk', 'artist', 'title', 'tag_list', 'episode', 'sound', 'timestamp']
 | 
				
			||||||
 | 
					    list_editable = ['artist', 'title']
 | 
				
			||||||
 | 
					    list_filter = ['artist', 'title', 'tags']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    search_fields = ['artist', 'title']
 | 
				
			||||||
 | 
					    fieldsets = [
 | 
				
			||||||
 | 
					        (_('Playlist'), {'fields': ['episode', 'sound', 'position', 'timestamp']}),
 | 
				
			||||||
 | 
					        (_('Info'), {'fields': ['artist', 'title', 'info', 'tags']}),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO on edit: readonly_fields = ['episode', 'sound']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										15
									
								
								aircox/admin/station.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								aircox/admin/station.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..models import Station
 | 
				
			||||||
 | 
					from .page import NavItemInline
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__all__ = ['StationAdmin']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Station)
 | 
				
			||||||
 | 
					class StationAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    prepopulated_fields = {'slug': ('name',)}
 | 
				
			||||||
 | 
					    inlines = (NavItemInline,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										56
									
								
								aircox/admin_site.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								aircox/admin_site.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.urls import path, include, reverse
 | 
				
			||||||
 | 
					from django.utils.translation import ugettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from rest_framework.routers import DefaultRouter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .models import Program
 | 
				
			||||||
 | 
					from .views.admin import StatisticsView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__all__ = ['AdminSite']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AdminSite(admin.AdminSite):
 | 
				
			||||||
 | 
					    extra_urls = None
 | 
				
			||||||
 | 
					    tools = [
 | 
				
			||||||
 | 
					        (_('Statistics'), 'admin:tools-stats'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					        self.router = DefaultRouter()
 | 
				
			||||||
 | 
					        self.extra_urls = []
 | 
				
			||||||
 | 
					        self.tools = type(self).tools.copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def each_context(self, request):
 | 
				
			||||||
 | 
					        context = super().each_context(request)
 | 
				
			||||||
 | 
					        context.update({
 | 
				
			||||||
 | 
					            'programs': Program.objects.all().active().values('pk', 'title'),
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_urls(self):
 | 
				
			||||||
 | 
					        urls = super().get_urls() + [
 | 
				
			||||||
 | 
					            path('api/', include((self.router.urls, 'api'))),
 | 
				
			||||||
 | 
					            path('tools/statistics/',
 | 
				
			||||||
 | 
					                 self.admin_view(StatisticsView.as_view()),
 | 
				
			||||||
 | 
					                 name='tools-stats'),
 | 
				
			||||||
 | 
					            path('tools/statistics/<date:date>/',
 | 
				
			||||||
 | 
					                 self.admin_view(StatisticsView.as_view()),
 | 
				
			||||||
 | 
					                 name='tools-stats'),
 | 
				
			||||||
 | 
					        ] + self.extra_urls
 | 
				
			||||||
 | 
					        return urls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_tools(self):
 | 
				
			||||||
 | 
					        return [(label, reverse(url)) for label, url in self.tools]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def route_view(self, url, view, name, admin_view=True, label=None):
 | 
				
			||||||
 | 
					        self.extra_urls.append(path(
 | 
				
			||||||
 | 
					            url, self.admin_view(view) if admin_view else view, name=name
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if label:
 | 
				
			||||||
 | 
					            self.tools.append((label, 'admin:' + name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							@ -34,6 +34,9 @@ class SoundQuerySet(models.QuerySet):
 | 
				
			|||||||
        id = diffusion.pk if id is None else id
 | 
					        id = diffusion.pk if id is None else id
 | 
				
			||||||
        return self.filter(episode__diffusion__id=id)
 | 
					        return self.filter(episode__diffusion__id=id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def available(self):
 | 
				
			||||||
 | 
					        return self.exclude(type=Sound.TYPE_REMOVED)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def podcasts(self):
 | 
					    def podcasts(self):
 | 
				
			||||||
        """ Return sounds available as podcasts """
 | 
					        """ Return sounds available as podcasts """
 | 
				
			||||||
        return self.filter(Q(embed__isnull=False) | Q(is_public=True))
 | 
					        return self.filter(Q(embed__isnull=False) | Q(is_public=True))
 | 
				
			||||||
@ -87,6 +90,9 @@ class Sound(models.Model):
 | 
				
			|||||||
        verbose_name=_('episode'),
 | 
					        verbose_name=_('episode'),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    type = models.SmallIntegerField(_('type'), choices=TYPE_CHOICES)
 | 
					    type = models.SmallIntegerField(_('type'), choices=TYPE_CHOICES)
 | 
				
			||||||
 | 
					    position = models.PositiveSmallIntegerField(
 | 
				
			||||||
 | 
					        _('order'), default=0, help_text=_('position in the playlist'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    # FIXME: url() does not use the same directory than here
 | 
					    # FIXME: url() does not use the same directory than here
 | 
				
			||||||
    #        should we use FileField for more reliability?
 | 
					    #        should we use FileField for more reliability?
 | 
				
			||||||
    path = models.FilePathField(
 | 
					    path = models.FilePathField(
 | 
				
			||||||
@ -278,9 +284,7 @@ class Track(models.Model):
 | 
				
			|||||||
        verbose_name=_('sound'),
 | 
					        verbose_name=_('sound'),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    position = models.PositiveSmallIntegerField(
 | 
					    position = models.PositiveSmallIntegerField(
 | 
				
			||||||
        _('order'),
 | 
					        _('order'), default=0, help_text=_('position in the playlist'),
 | 
				
			||||||
        default=0,
 | 
					 | 
				
			||||||
        help_text=_('position in the playlist'),
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    timestamp = models.PositiveSmallIntegerField(
 | 
					    timestamp = models.PositiveSmallIntegerField(
 | 
				
			||||||
        _('timestamp'),
 | 
					        _('timestamp'),
 | 
				
			||||||
 | 
				
			|||||||
@ -282,7 +282,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue_
 | 
				
			|||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
					/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
eval("__webpack_require__.r(__webpack_exports__);\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (class {\n    constructor(url, timeout) {\n        this.url = url;\n        this.timeout = timeout;\n        this.promise = null;\n        this.items = [];\n    }\n\n    drop() {\n        this.promise = null;\n    }\n\n    fetch() {\n        const promise = fetch(this.url).then(response =>\n            response.ok ? response.json()\n                        : Promise.reject(response)\n        ).then(data => {\n            this.items = data;\n            return this.items\n        })\n\n        this.promise = promise;\n        return promise;\n    }\n\n    refresh() {\n        const promise = this.fetch();\n        promise.then(data => {\n            if(promise != this.promise)\n                return [];\n\n            window.setTimeout(() => this.refresh(), this.timeout*1000)\n        })\n        return promise\n    }\n});\n\n\n//# sourceURL=webpack:///./assets/public/liveInfo.js?");
 | 
					eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var public_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! public/utils */ \"./assets/public/utils.js\");\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (class {\n    constructor(url, timeout) {\n        this.url = url;\n        this.timeout = timeout;\n        this.promise = null;\n        this.items = [];\n    }\n\n    drop() {\n        this.promise = null;\n    }\n\n    fetch() {\n        const promise = fetch(this.url).then(response =>\n            response.ok ? response.json()\n                        : Promise.reject(response)\n        ).then(data => {\n            this.items = data;\n            return this.items\n        })\n\n        this.promise = promise;\n        return promise;\n    }\n\n    refresh() {\n        const promise = this.fetch();\n        promise.then(data => {\n            if(promise != this.promise)\n                return [];\n\n            Object(public_utils__WEBPACK_IMPORTED_MODULE_0__[\"setEcoTimeout\"])(() => this.refresh(), this.timeout*1000)\n        })\n        return promise\n    }\n});\n\n\n//# sourceURL=webpack:///./assets/public/liveInfo.js?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/***/ }),
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -333,6 +333,18 @@ eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./asse
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/***/ }),
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ "./assets/public/utils.js":
 | 
				
			||||||
 | 
					/*!********************************!*\
 | 
				
			||||||
 | 
					  !*** ./assets/public/utils.js ***!
 | 
				
			||||||
 | 
					  \********************************/
 | 
				
			||||||
 | 
					/*! exports provided: setEcoTimeout, setEcoInterval */
 | 
				
			||||||
 | 
					/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setEcoTimeout\", function() { return setEcoTimeout; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setEcoInterval\", function() { return setEcoInterval; });\n\n\nfunction setEcoTimeout(func, ...args) {\n    return setTimeout((...args) => {\n        !document.hidden && func(...args)\n    }, ...args)\n}\n\n\nfunction setEcoInterval(func, ...args) {\n    return setInterval((...args) => {\n        !document.hidden && func(...args)\n    }, ...args)\n}\n\n\n\n//# sourceURL=webpack:///./assets/public/utils.js?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/***/ "./node_modules/vue-loader/lib/index.js?!./assets/admin/statistics.vue?vue&type=script&lang=js&":
 | 
					/***/ "./node_modules/vue-loader/lib/index.js?!./assets/admin/statistics.vue?vue&type=script&lang=js&":
 | 
				
			||||||
/*!****************************************************************************************************************!*\
 | 
					/*!****************************************************************************************************************!*\
 | 
				
			||||||
  !*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/admin/statistics.vue?vue&type=script&lang=js& ***!
 | 
					  !*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/admin/statistics.vue?vue&type=script&lang=js& ***!
 | 
				
			||||||
 | 
				
			|||||||
@ -223,7 +223,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue_
 | 
				
			|||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
					/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
eval("__webpack_require__.r(__webpack_exports__);\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (class {\n    constructor(url, timeout) {\n        this.url = url;\n        this.timeout = timeout;\n        this.promise = null;\n        this.items = [];\n    }\n\n    drop() {\n        this.promise = null;\n    }\n\n    fetch() {\n        const promise = fetch(this.url).then(response =>\n            response.ok ? response.json()\n                        : Promise.reject(response)\n        ).then(data => {\n            this.items = data;\n            return this.items\n        })\n\n        this.promise = promise;\n        return promise;\n    }\n\n    refresh() {\n        const promise = this.fetch();\n        promise.then(data => {\n            if(promise != this.promise)\n                return [];\n\n            window.setTimeout(() => this.refresh(), this.timeout*1000)\n        })\n        return promise\n    }\n});\n\n\n//# sourceURL=webpack:///./assets/public/liveInfo.js?");
 | 
					eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var public_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! public/utils */ \"./assets/public/utils.js\");\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (class {\n    constructor(url, timeout) {\n        this.url = url;\n        this.timeout = timeout;\n        this.promise = null;\n        this.items = [];\n    }\n\n    drop() {\n        this.promise = null;\n    }\n\n    fetch() {\n        const promise = fetch(this.url).then(response =>\n            response.ok ? response.json()\n                        : Promise.reject(response)\n        ).then(data => {\n            this.items = data;\n            return this.items\n        })\n\n        this.promise = promise;\n        return promise;\n    }\n\n    refresh() {\n        const promise = this.fetch();\n        promise.then(data => {\n            if(promise != this.promise)\n                return [];\n\n            Object(public_utils__WEBPACK_IMPORTED_MODULE_0__[\"setEcoTimeout\"])(() => this.refresh(), this.timeout*1000)\n        })\n        return promise\n    }\n});\n\n\n//# sourceURL=webpack:///./assets/public/liveInfo.js?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/***/ }),
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -274,6 +274,18 @@ eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./asse
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/***/ }),
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ "./assets/public/utils.js":
 | 
				
			||||||
 | 
					/*!********************************!*\
 | 
				
			||||||
 | 
					  !*** ./assets/public/utils.js ***!
 | 
				
			||||||
 | 
					  \********************************/
 | 
				
			||||||
 | 
					/*! exports provided: setEcoTimeout, setEcoInterval */
 | 
				
			||||||
 | 
					/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setEcoTimeout\", function() { return setEcoTimeout; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setEcoInterval\", function() { return setEcoInterval; });\n\n\nfunction setEcoTimeout(func, ...args) {\n    return setTimeout((...args) => {\n        !document.hidden && func(...args)\n    }, ...args)\n}\n\n\nfunction setEcoInterval(func, ...args) {\n    return setInterval((...args) => {\n        !document.hidden && func(...args)\n    }, ...args)\n}\n\n\n\n//# sourceURL=webpack:///./assets/public/utils.js?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/***/ "./node_modules/vue-loader/lib/index.js?!./assets/public/autocomplete.vue?vue&type=script&lang=js&":
 | 
					/***/ "./node_modules/vue-loader/lib/index.js?!./assets/public/autocomplete.vue?vue&type=script&lang=js&":
 | 
				
			||||||
/*!*******************************************************************************************************************!*\
 | 
					/*!*******************************************************************************************************************!*\
 | 
				
			||||||
  !*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/public/autocomplete.vue?vue&type=script&lang=js& ***!
 | 
					  !*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/public/autocomplete.vue?vue&type=script&lang=js& ***!
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										230
									
								
								aircox/static/aircox/streamer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								aircox/static/aircox/streamer.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,230 @@
 | 
				
			|||||||
 | 
					/******/ (function(modules) { // webpackBootstrap
 | 
				
			||||||
 | 
					/******/ 	// install a JSONP callback for chunk loading
 | 
				
			||||||
 | 
					/******/ 	function webpackJsonpCallback(data) {
 | 
				
			||||||
 | 
					/******/ 		var chunkIds = data[0];
 | 
				
			||||||
 | 
					/******/ 		var moreModules = data[1];
 | 
				
			||||||
 | 
					/******/ 		var executeModules = data[2];
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 		// add "moreModules" to the modules object,
 | 
				
			||||||
 | 
					/******/ 		// then flag all "chunkIds" as loaded and fire callback
 | 
				
			||||||
 | 
					/******/ 		var moduleId, chunkId, i = 0, resolves = [];
 | 
				
			||||||
 | 
					/******/ 		for(;i < chunkIds.length; i++) {
 | 
				
			||||||
 | 
					/******/ 			chunkId = chunkIds[i];
 | 
				
			||||||
 | 
					/******/ 			if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
 | 
				
			||||||
 | 
					/******/ 				resolves.push(installedChunks[chunkId][0]);
 | 
				
			||||||
 | 
					/******/ 			}
 | 
				
			||||||
 | 
					/******/ 			installedChunks[chunkId] = 0;
 | 
				
			||||||
 | 
					/******/ 		}
 | 
				
			||||||
 | 
					/******/ 		for(moduleId in moreModules) {
 | 
				
			||||||
 | 
					/******/ 			if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
 | 
				
			||||||
 | 
					/******/ 				modules[moduleId] = moreModules[moduleId];
 | 
				
			||||||
 | 
					/******/ 			}
 | 
				
			||||||
 | 
					/******/ 		}
 | 
				
			||||||
 | 
					/******/ 		if(parentJsonpFunction) parentJsonpFunction(data);
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 		while(resolves.length) {
 | 
				
			||||||
 | 
					/******/ 			resolves.shift()();
 | 
				
			||||||
 | 
					/******/ 		}
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 		// add entry modules from loaded chunk to deferred list
 | 
				
			||||||
 | 
					/******/ 		deferredModules.push.apply(deferredModules, executeModules || []);
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 		// run deferred modules when all chunks ready
 | 
				
			||||||
 | 
					/******/ 		return checkDeferredModules();
 | 
				
			||||||
 | 
					/******/ 	};
 | 
				
			||||||
 | 
					/******/ 	function checkDeferredModules() {
 | 
				
			||||||
 | 
					/******/ 		var result;
 | 
				
			||||||
 | 
					/******/ 		for(var i = 0; i < deferredModules.length; i++) {
 | 
				
			||||||
 | 
					/******/ 			var deferredModule = deferredModules[i];
 | 
				
			||||||
 | 
					/******/ 			var fulfilled = true;
 | 
				
			||||||
 | 
					/******/ 			for(var j = 1; j < deferredModule.length; j++) {
 | 
				
			||||||
 | 
					/******/ 				var depId = deferredModule[j];
 | 
				
			||||||
 | 
					/******/ 				if(installedChunks[depId] !== 0) fulfilled = false;
 | 
				
			||||||
 | 
					/******/ 			}
 | 
				
			||||||
 | 
					/******/ 			if(fulfilled) {
 | 
				
			||||||
 | 
					/******/ 				deferredModules.splice(i--, 1);
 | 
				
			||||||
 | 
					/******/ 				result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
 | 
				
			||||||
 | 
					/******/ 			}
 | 
				
			||||||
 | 
					/******/ 		}
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 		return result;
 | 
				
			||||||
 | 
					/******/ 	}
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	// The module cache
 | 
				
			||||||
 | 
					/******/ 	var installedModules = {};
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	// object to store loaded and loading chunks
 | 
				
			||||||
 | 
					/******/ 	// undefined = chunk not loaded, null = chunk preloaded/prefetched
 | 
				
			||||||
 | 
					/******/ 	// Promise = chunk loading, 0 = chunk loaded
 | 
				
			||||||
 | 
					/******/ 	var installedChunks = {
 | 
				
			||||||
 | 
					/******/ 		"streamer": 0
 | 
				
			||||||
 | 
					/******/ 	};
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	var deferredModules = [];
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	// The require function
 | 
				
			||||||
 | 
					/******/ 	function __webpack_require__(moduleId) {
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 		// Check if module is in cache
 | 
				
			||||||
 | 
					/******/ 		if(installedModules[moduleId]) {
 | 
				
			||||||
 | 
					/******/ 			return installedModules[moduleId].exports;
 | 
				
			||||||
 | 
					/******/ 		}
 | 
				
			||||||
 | 
					/******/ 		// Create a new module (and put it into the cache)
 | 
				
			||||||
 | 
					/******/ 		var module = installedModules[moduleId] = {
 | 
				
			||||||
 | 
					/******/ 			i: moduleId,
 | 
				
			||||||
 | 
					/******/ 			l: false,
 | 
				
			||||||
 | 
					/******/ 			exports: {}
 | 
				
			||||||
 | 
					/******/ 		};
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 		// Execute the module function
 | 
				
			||||||
 | 
					/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 		// Flag the module as loaded
 | 
				
			||||||
 | 
					/******/ 		module.l = true;
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 		// Return the exports of the module
 | 
				
			||||||
 | 
					/******/ 		return module.exports;
 | 
				
			||||||
 | 
					/******/ 	}
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	// expose the modules object (__webpack_modules__)
 | 
				
			||||||
 | 
					/******/ 	__webpack_require__.m = modules;
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	// expose the module cache
 | 
				
			||||||
 | 
					/******/ 	__webpack_require__.c = installedModules;
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	// define getter function for harmony exports
 | 
				
			||||||
 | 
					/******/ 	__webpack_require__.d = function(exports, name, getter) {
 | 
				
			||||||
 | 
					/******/ 		if(!__webpack_require__.o(exports, name)) {
 | 
				
			||||||
 | 
					/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
 | 
				
			||||||
 | 
					/******/ 		}
 | 
				
			||||||
 | 
					/******/ 	};
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	// define __esModule on exports
 | 
				
			||||||
 | 
					/******/ 	__webpack_require__.r = function(exports) {
 | 
				
			||||||
 | 
					/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
 | 
				
			||||||
 | 
					/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
 | 
				
			||||||
 | 
					/******/ 		}
 | 
				
			||||||
 | 
					/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
 | 
				
			||||||
 | 
					/******/ 	};
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	// create a fake namespace object
 | 
				
			||||||
 | 
					/******/ 	// mode & 1: value is a module id, require it
 | 
				
			||||||
 | 
					/******/ 	// mode & 2: merge all properties of value into the ns
 | 
				
			||||||
 | 
					/******/ 	// mode & 4: return value when already ns object
 | 
				
			||||||
 | 
					/******/ 	// mode & 8|1: behave like require
 | 
				
			||||||
 | 
					/******/ 	__webpack_require__.t = function(value, mode) {
 | 
				
			||||||
 | 
					/******/ 		if(mode & 1) value = __webpack_require__(value);
 | 
				
			||||||
 | 
					/******/ 		if(mode & 8) return value;
 | 
				
			||||||
 | 
					/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
 | 
				
			||||||
 | 
					/******/ 		var ns = Object.create(null);
 | 
				
			||||||
 | 
					/******/ 		__webpack_require__.r(ns);
 | 
				
			||||||
 | 
					/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
 | 
				
			||||||
 | 
					/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
 | 
				
			||||||
 | 
					/******/ 		return ns;
 | 
				
			||||||
 | 
					/******/ 	};
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	// getDefaultExport function for compatibility with non-harmony modules
 | 
				
			||||||
 | 
					/******/ 	__webpack_require__.n = function(module) {
 | 
				
			||||||
 | 
					/******/ 		var getter = module && module.__esModule ?
 | 
				
			||||||
 | 
					/******/ 			function getDefault() { return module['default']; } :
 | 
				
			||||||
 | 
					/******/ 			function getModuleExports() { return module; };
 | 
				
			||||||
 | 
					/******/ 		__webpack_require__.d(getter, 'a', getter);
 | 
				
			||||||
 | 
					/******/ 		return getter;
 | 
				
			||||||
 | 
					/******/ 	};
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	// Object.prototype.hasOwnProperty.call
 | 
				
			||||||
 | 
					/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	// __webpack_public_path__
 | 
				
			||||||
 | 
					/******/ 	__webpack_require__.p = "";
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
 | 
				
			||||||
 | 
					/******/ 	var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
 | 
				
			||||||
 | 
					/******/ 	jsonpArray.push = webpackJsonpCallback;
 | 
				
			||||||
 | 
					/******/ 	jsonpArray = jsonpArray.slice();
 | 
				
			||||||
 | 
					/******/ 	for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
 | 
				
			||||||
 | 
					/******/ 	var parentJsonpFunction = oldJsonpFunction;
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/
 | 
				
			||||||
 | 
					/******/ 	// add entry module to deferred list
 | 
				
			||||||
 | 
					/******/ 	deferredModules.push(["./assets/streamer/index.js","vendor"]);
 | 
				
			||||||
 | 
					/******/ 	// run deferred modules when ready
 | 
				
			||||||
 | 
					/******/ 	return checkDeferredModules();
 | 
				
			||||||
 | 
					/******/ })
 | 
				
			||||||
 | 
					/************************************************************************/
 | 
				
			||||||
 | 
					/******/ ({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ "./assets/public/app.js":
 | 
				
			||||||
 | 
					/*!******************************!*\
 | 
				
			||||||
 | 
					  !*** ./assets/public/app.js ***!
 | 
				
			||||||
 | 
					  \******************************/
 | 
				
			||||||
 | 
					/*! exports provided: appBaseConfig, setAppConfig, getAppConfig, loadApp */
 | 
				
			||||||
 | 
					/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"appBaseConfig\", function() { return appBaseConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setAppConfig\", function() { return setAppConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getAppConfig\", function() { return getAppConfig; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"loadApp\", function() { return loadApp; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\n\nconst appBaseConfig = {\n    el: '#app',\n    delimiters: ['[[', ']]'],\n}\n\n/**\n * Application config for the main application instance\n */\nvar appConfig = {};\n\nfunction setAppConfig(config) {\n    for(var member in appConfig) delete appConfig[member];\n    return Object.assign(appConfig, config)\n}\n\nfunction getAppConfig(config) {\n    if(config instanceof Function)\n        config = config()\n    config = config == null ? appConfig : config;\n    return {...appBaseConfig, ...config}\n}\n\n\n/**\n * Create Vue application at window 'load' event and return a Promise\n * resolving to the created app.\n *\n * config: defaults to appConfig (checked when window is loaded)\n */\nfunction loadApp(config=null) {\n    return new Promise(function(resolve, reject) {\n        window.addEventListener('load', function() {\n            try {\n                config = getAppConfig(config)\n                const el = document.querySelector(config.el)\n                if(!el) {\n                    reject(`Error: missing element ${config.el}`);\n                    return;\n                }\n\n                resolve(new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"](config))\n            }\n            catch(error) { reject(error) }\n        })\n    })\n}\n\n\n\n\n\n\n//# sourceURL=webpack:///./assets/public/app.js?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ "./assets/public/model.js":
 | 
				
			||||||
 | 
					/*!********************************!*\
 | 
				
			||||||
 | 
					  !*** ./assets/public/model.js ***!
 | 
				
			||||||
 | 
					  \********************************/
 | 
				
			||||||
 | 
					/*! exports provided: getCsrf, default */
 | 
				
			||||||
 | 
					/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getCsrf\", function() { return getCsrf; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Model; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n\n\nfunction getCookie(name) {\n    if(document.cookie && document.cookie !== '') {\n        const cookie = document.cookie.split(';')\n                               .find(c => c.trim().startsWith(name + '='))\n        return cookie ? decodeURIComponent(cookie.split('=')[1]) : null;\n    }\n    return null;\n}\n\nvar csrfToken = null;\n\nfunction getCsrf() {\n    if(csrfToken === null)\n        csrfToken = getCookie('csrftoken')\n    return csrfToken;\n}\n\n\n// TODO: move in another module for reuse\n// TODO: prevent duplicate simple fetch\nclass Model {\n    constructor(data, {url=null}={}) {\n        this.commit(data);\n    }\n\n    static getId(data) {\n        return data.id;\n    }\n\n    static getOptions(options) {\n        return {\n            headers: {\n                'Content-Type': 'application/json',\n                'Accept': 'application/json',\n                'X-CSRFToken': getCsrf(),\n            },\n            ...options,\n        }\n    }\n\n    static fetch(url, options=null, initArgs=null) {\n        options = this.getOptions(options)\n        return fetch(url, options)\n            .then(response => response.json())\n            .then(data => new this(d, {url: url, ...initArgs}));\n    }\n\n    static fetchAll(url, options=null, initArgs=null) {\n        options = this.getOptions(options)\n        return fetch(url, options)\n            .then(response => response.json())\n            .then(data => {\n                if(!(data instanceof Array))\n                    data = data.results;\n                data = data.map(d => new this(d, {baseUrl: url, ...initArgs}));\n                return data\n            })\n    }\n\n    /**\n     * Fetch data from server.\n     */\n    fetch(options) {\n        options = this.constructor.getOptions(options)\n        return fetch(this.url, options)\n            .then(response => response.json())\n            .then(data => this.commit(data));\n    }\n\n    /**\n     * Call API action on object.\n     */\n    action(path, options, commit=false) {\n        options = this.constructor.getOptions(options)\n        const promise = fetch(this.url + path, options);\n        return commit ? promise.then(data => data.json())\n                               .then(data => { this.commit(data); this.data })\n                      : promise;\n    }\n\n    /**\n     * Update instance's data with provided data. Return None\n     */\n    commit(data) {\n        this.id = this.constructor.getId(data);\n        this.url = data.url_;\n        vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].set(this, 'data', data);\n    }\n\n    /**\n     * Return data as model with url prepent by `this.url`.\n     */\n    asChild(model, data, prefix='') {\n        return new model(data, {baseUrl: `${this.url}${prefix}/`})\n    }\n\n    getChildOf(attr, id) {\n        const index = this.data[attr].findIndex(o => o.id = id)\n        return index == -1 ? null : this.data[attr][index];\n    }\n\n    static updateList(list=[], old=[], ...initArgs) {\n         return list.reduce((items, data) => {\n            const id = this.getId(data);\n            let [index, obj] = [old.findIndex(o => o.id == id), null];\n            if(index != -1) {\n                old[index].commit(data)\n                items.push(old[index]);\n            }\n            else\n                items.push(new this(data, ...initArgs))\n            return items;\n        }, [])\n    }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/model.js?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ "./assets/public/sound.js":
 | 
				
			||||||
 | 
					/*!********************************!*\
 | 
				
			||||||
 | 
					  !*** ./assets/public/sound.js ***!
 | 
				
			||||||
 | 
					  \********************************/
 | 
				
			||||||
 | 
					/*! exports provided: default */
 | 
				
			||||||
 | 
					/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Sound; });\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./model */ \"./assets/public/model.js\");\n\n\n\nclass Sound extends _model__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n    get name() { return this.data.name }\n\n    static getId(data) { return data.pk }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/public/sound.js?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ "./assets/public/utils.js":
 | 
				
			||||||
 | 
					/*!********************************!*\
 | 
				
			||||||
 | 
					  !*** ./assets/public/utils.js ***!
 | 
				
			||||||
 | 
					  \********************************/
 | 
				
			||||||
 | 
					/*! exports provided: setEcoTimeout, setEcoInterval */
 | 
				
			||||||
 | 
					/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setEcoTimeout\", function() { return setEcoTimeout; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"setEcoInterval\", function() { return setEcoInterval; });\n\n\nfunction setEcoTimeout(func, ...args) {\n    return setTimeout((...args) => {\n        !document.hidden && func(...args)\n    }, ...args)\n}\n\n\nfunction setEcoInterval(func, ...args) {\n    return setInterval((...args) => {\n        !document.hidden && func(...args)\n    }, ...args)\n}\n\n\n\n//# sourceURL=webpack:///./assets/public/utils.js?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ "./assets/streamer/controllers.js":
 | 
				
			||||||
 | 
					/*!****************************************!*\
 | 
				
			||||||
 | 
					  !*** ./assets/streamer/controllers.js ***!
 | 
				
			||||||
 | 
					  \****************************************/
 | 
				
			||||||
 | 
					/*! exports provided: Streamer, Request, Source, Playlist, Queue */
 | 
				
			||||||
 | 
					/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Streamer\", function() { return Streamer; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Request\", function() { return Request; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Source\", function() { return Source; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Playlist\", function() { return Playlist; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"Queue\", function() { return Queue; });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var public_model__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! public/model */ \"./assets/public/model.js\");\n/* harmony import */ var public_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! public/utils */ \"./assets/public/utils.js\");\n\n\n\n\n\n\nclass Streamer extends public_model__WEBPACK_IMPORTED_MODULE_1__[\"default\"] {\n    get queues() { return this.data ? this.data.queues : []; }\n    get playlists() { return this.data ? this.data.playlists : []; }\n    get sources() { return [...this.queues, ...this.playlists]; }\n    get source() { return this.sources.find(o => o.id == this.data.source) }\n\n    commit(data) {\n        if(!this.data)\n            this.data = { id: data.id, playlists: [], queues: [] }\n\n        data.playlists = Playlist.updateList(data.playlists, this.playlists, {streamer: this})\n        data.queues = Queue.updateList(data.queues, this.queues, {streamer: this})\n        super.commit(data)\n    }\n}\n\nclass Request extends public_model__WEBPACK_IMPORTED_MODULE_1__[\"default\"] {\n    static getId(data) { return data.rid; }\n}\n\nclass Source extends public_model__WEBPACK_IMPORTED_MODULE_1__[\"default\"] {\n    constructor(data, {streamer=null, ...options}={}) {\n        super(data, options);\n        this.streamer = streamer;\n        Object(public_utils__WEBPACK_IMPORTED_MODULE_2__[\"setEcoInterval\"])(() => this.tick(), 1000)\n    }\n\n    get isQueue() { return false; }\n    get isPlaylist() { return false; }\n    get isPlaying() { return this.data.status == 'playing' }\n    get isPaused() { return this.data.status == 'paused' }\n\n    get remainingString() {\n        if(!this.remaining)\n            return '00:00';\n\n        const seconds = Math.floor(this.remaining % 60);\n        const minutes = Math.floor(this.remaining / 60);\n        return String(minutes).padStart(2, '0') + ':' +\n               String(seconds).padStart(2, '0');\n    }\n\n    sync() { return this.action('sync/', {method: 'POST'}, true); }\n    skip() { return this.action('skip/', {method: 'POST'}, true); }\n    restart() { return this.action('restart/', {method: 'POST'}, true); }\n\n    seek(count) {\n        return this.action('seek/', {\n            method: 'POST',\n            body: JSON.stringify({count: count})\n        }, true)\n    }\n\n    tick() {\n        if(!this.data.remaining || !this.isPlaying)\n            return;\n        const delta = (Date.now() - this.commitDate) / 1000;\n        vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].set(this, 'remaining', this.data.remaining - delta)\n    }\n\n    commit(data) {\n        if(data.air_time)\n            data.air_time = new Date(data.air_time);\n\n        this.commitDate = Date.now()\n        super.commit(data)\n        vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].set(this, 'remaining', data.remaining)\n    }\n}\n\n\nclass Playlist extends Source {\n    get isPlaylist() { return true; }\n}\n\n\nclass Queue extends Source {\n    get isQueue() { return true; }\n    get queue() { return this.data && this.data.queue; }\n\n    commit(data) {\n        data.queue = Request.updateList(data.queue, this.queue)\n        super.commit(data)\n    }\n\n    push(soundId) {\n        return this.action('push/', {\n            method: 'POST',\n            body: JSON.stringify({'sound_id': parseInt(soundId)})\n        }, true);\n    }\n}\n\n\n\n\n//# sourceURL=webpack:///./assets/streamer/controllers.js?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ "./assets/streamer/index.js":
 | 
				
			||||||
 | 
					/*!**********************************!*\
 | 
				
			||||||
 | 
					  !*** ./assets/streamer/index.js ***!
 | 
				
			||||||
 | 
					  \**********************************/
 | 
				
			||||||
 | 
					/*! no exports provided */
 | 
				
			||||||
 | 
					/***/ (function(module, __webpack_exports__, __webpack_require__) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var buefy_dist_components_button__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! buefy/dist/components/button */ \"./node_modules/buefy/dist/components/button/index.js\");\n/* harmony import */ var buefy_dist_components_button__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(buefy_dist_components_button__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var public_app__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! public/app */ \"./assets/public/app.js\");\n/* harmony import */ var public_model__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! public/model */ \"./assets/public/model.js\");\n/* harmony import */ var public_sound__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! public/sound */ \"./assets/public/sound.js\");\n/* harmony import */ var _controllers__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./controllers */ \"./assets/streamer/controllers.js\");\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].use(buefy_dist_components_button__WEBPACK_IMPORTED_MODULE_1___default.a)\n\n\n\n\n\n\nwindow.aircox.appConfig = {\n    data() {\n        return {\n            // current streamer\n            streamer: null,\n            // all streamers\n            streamers: [],\n            // fetch interval id\n            fetchInterval: null,\n\n            Sound: public_sound__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n        }\n    },\n\n    computed: {\n        apiUrl() {\n            return this.$el && this.$el.dataset.apiUrl;\n        },\n\n        sources() {\n            var sources = this.streamer ? this.streamer.sources : [];\n            return sources.filter(s => s.data)\n        },\n    },\n\n    methods: {\n        fetchStreamers() {\n            _controllers__WEBPACK_IMPORTED_MODULE_5__[\"Streamer\"].fetchAll(this.apiUrl, null)\n                .then(streamers => {\n                    vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].set(this, 'streamers', streamers);\n                    vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].set(this, 'streamer', streamers ? streamers[0] : null);\n                })\n        },\n    },\n\n    mounted() {\n        this.fetchStreamers();\n        this.fetchInterval = setInterval(() => this.streamer && this.streamer.fetch(), 5000)\n    },\n\n    destroyed() {\n        if(this.fetchInterval !== null)\n            clearInterval(this.fetchInterval)\n    }\n}\n\n\n\n//# sourceURL=webpack:///./assets/streamer/index.js?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/***/ })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/******/ });
 | 
				
			||||||
@ -133,7 +133,7 @@ Blocks:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            <hr>
 | 
					            <hr>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div id="player">{% include "aircox/player.html" %}</div>
 | 
					        <div id="player">{% include "aircox/widgets/player.html" %}</div>
 | 
				
			||||||
    </body>
 | 
					    </body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@
 | 
				
			|||||||
    </noscript>
 | 
					    </noscript>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <a-player ref="player" src="{{ audio_streams.0 }}"
 | 
					    <a-player ref="player" src="{{ audio_streams.0 }}"
 | 
				
			||||||
            live-info-url="{% url "api-live" %}" :live-info-timeout="20"
 | 
					            live-info-url="{% url "api:live" %}" :live-info-timeout="20"
 | 
				
			||||||
            button-title="{% trans "Play or pause audio" %}">
 | 
					            button-title="{% trans "Play or pause audio" %}">
 | 
				
			||||||
        <template v-slot:sources>
 | 
					        <template v-slot:sources>
 | 
				
			||||||
            {% for stream in audio_streams %}
 | 
					            {% for stream in audio_streams %}
 | 
				
			||||||
							
								
								
									
										9
									
								
								aircox/templatetags/aircox_admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								aircox/templatetags/aircox_admin.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					from django import template
 | 
				
			||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					register = template.Library()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@register.simple_tag(name='get_admin_tools')
 | 
				
			||||||
 | 
					def do_get_admin_tools():
 | 
				
			||||||
 | 
					    return admin.site.get_tools()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1,10 +1,15 @@
 | 
				
			|||||||
from django.urls import include, path, register_converter
 | 
					from django.urls import include, path, register_converter
 | 
				
			||||||
from django.utils.translation import ugettext_lazy as _
 | 
					from django.utils.translation import ugettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import views, models
 | 
					from rest_framework.routers import DefaultRouter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from . import models, views, viewsets
 | 
				
			||||||
from .converters import PagePathConverter, DateConverter, WeekConverter
 | 
					from .converters import PagePathConverter, DateConverter, WeekConverter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__all__ = ['api', 'urls']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
register_converter(PagePathConverter, 'page_path')
 | 
					register_converter(PagePathConverter, 'page_path')
 | 
				
			||||||
register_converter(DateConverter, 'date')
 | 
					register_converter(DateConverter, 'date')
 | 
				
			||||||
register_converter(WeekConverter, 'week')
 | 
					register_converter(WeekConverter, 'week')
 | 
				
			||||||
@ -17,14 +22,18 @@ register_converter(WeekConverter, 'week')
 | 
				
			|||||||
# ]
 | 
					# ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router = DefaultRouter()
 | 
				
			||||||
 | 
					router.register('sound', viewsets.SoundViewSet, basename='sound')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
api = [
 | 
					api = [
 | 
				
			||||||
    path('logs/', views.api.LogListAPIView.as_view(), name='api-live'),
 | 
					    path('logs/', views.LogListAPIView.as_view(), name='live'),
 | 
				
			||||||
]
 | 
					] + router.urls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
urls = [
 | 
					urls = [
 | 
				
			||||||
    path('', views.HomeView.as_view(), name='home'),
 | 
					    path('', views.HomeView.as_view(), name='home'),
 | 
				
			||||||
    path('api/', include(api)),
 | 
					    path('api/', include((api, 'aircox'), namespace='api')),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # path('', views.PageDetailView.as_view(model=models.Article),
 | 
					    # path('', views.PageDetailView.as_view(model=models.Article),
 | 
				
			||||||
    #     name='home'),
 | 
					    #     name='home'),
 | 
				
			||||||
@ -61,6 +70,5 @@ urls = [
 | 
				
			|||||||
         views.ArticleListView.as_view(), name='article-list'),
 | 
					         views.ArticleListView.as_view(), name='article-list'),
 | 
				
			||||||
    path(_('programs/<slug:parent_slug>/publications/'),
 | 
					    path(_('programs/<slug:parent_slug>/publications/'),
 | 
				
			||||||
         views.ProgramPageListView.as_view(), name='program-page-list'),
 | 
					         views.ProgramPageListView.as_view(), name='program-page-list'),
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,11 @@
 | 
				
			|||||||
from . import api, admin
 | 
					from . import admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .base import BaseView
 | 
					from .base import BaseView, BaseAPIView
 | 
				
			||||||
from .home import HomeView
 | 
					from .home import HomeView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .article import ArticleDetailView, ArticleListView
 | 
					from .article import ArticleDetailView, ArticleListView
 | 
				
			||||||
from .episode import EpisodeDetailView, EpisodeListView, DiffusionListView
 | 
					from .episode import EpisodeDetailView, EpisodeListView, DiffusionListView
 | 
				
			||||||
from .log import LogListView
 | 
					from .log import LogListView, LogListAPIView
 | 
				
			||||||
from .page import PageDetailView, PageListView
 | 
					from .page import PageDetailView, PageListView
 | 
				
			||||||
from .program import ProgramDetailView, ProgramListView, \
 | 
					from .program import ProgramDetailView, ProgramListView, \
 | 
				
			||||||
        ProgramPageDetailView, ProgramPageListView
 | 
					        ProgramPageDetailView, ProgramPageListView
 | 
				
			||||||
 | 
				
			|||||||
@ -1,48 +0,0 @@
 | 
				
			|||||||
import datetime
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.utils import timezone as tz
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from rest_framework.generics import ListAPIView
 | 
					 | 
				
			||||||
from rest_framework import viewsets
 | 
					 | 
				
			||||||
from rest_framework.decorators import action
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from ..models import Log
 | 
					 | 
				
			||||||
from ..serializers import LogInfo, LogInfoSerializer
 | 
					 | 
				
			||||||
from .log import LogListMixin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class BaseAPIView:
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def station(self):
 | 
					 | 
				
			||||||
        return self.request.station
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_queryset(self):
 | 
					 | 
				
			||||||
        return super().get_queryset().station(self.station)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class LogListAPIView(LogListMixin, BaseAPIView, ListAPIView):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Return logs list, including diffusions. By default return logs of
 | 
					 | 
				
			||||||
    the last 30 minutes.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Available GET parameters:
 | 
					 | 
				
			||||||
    - "date": return logs for a specified date (
 | 
					 | 
				
			||||||
    - "full": (staff user only) don't merge diffusion and logs
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    serializer_class = LogInfoSerializer
 | 
					 | 
				
			||||||
    queryset = Log.objects.all()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        self.date = self.get_date()
 | 
					 | 
				
			||||||
        if self.date is None:
 | 
					 | 
				
			||||||
            self.min_date = tz.now() - tz.timedelta(minutes=30)
 | 
					 | 
				
			||||||
        return super().get(*args, **kwargs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_object_list(self, logs, full):
 | 
					 | 
				
			||||||
        return [LogInfo(obj) for obj in super().get_object_list(logs, full)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_serializer(self, queryset, *args, **kwargs):
 | 
					 | 
				
			||||||
        full = bool(self.request.GET.get('full'))
 | 
					 | 
				
			||||||
        return super().get_serializer(self.get_object_list(queryset, full),
 | 
					 | 
				
			||||||
                                      *args, **kwargs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@ -59,3 +59,12 @@ class BaseView(TemplateResponseMixin, ContextMixin):
 | 
				
			|||||||
        return super().get_context_data(**kwargs)
 | 
					        return super().get_context_data(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BaseAPIView:
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def station(self):
 | 
				
			||||||
 | 
					        return self.request.station
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_queryset(self):
 | 
				
			||||||
 | 
					        return super().get_queryset().station(self.station)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,8 +4,13 @@ import datetime
 | 
				
			|||||||
from django.views.generic import ListView
 | 
					from django.views.generic import ListView
 | 
				
			||||||
from django.utils import timezone as tz
 | 
					from django.utils import timezone as tz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from rest_framework.generics import ListAPIView
 | 
				
			||||||
 | 
					from rest_framework import viewsets
 | 
				
			||||||
 | 
					from rest_framework.decorators import action
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ..models import Diffusion, Log
 | 
					from ..models import Diffusion, Log
 | 
				
			||||||
from .base import BaseView
 | 
					from ..serializers import LogInfo, LogInfoSerializer
 | 
				
			||||||
 | 
					from .base import BaseView, BaseAPIView
 | 
				
			||||||
from .mixins import GetDateMixin
 | 
					from .mixins import GetDateMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -68,3 +73,30 @@ class LogListView(BaseView, LogListMixin, ListView):
 | 
				
			|||||||
        return super().get_context_data(**kwargs)
 | 
					        return super().get_context_data(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Logs are accessible through API only with this list view
 | 
				
			||||||
 | 
					class LogListAPIView(LogListMixin, BaseAPIView, ListAPIView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Return logs list, including diffusions. By default return logs of
 | 
				
			||||||
 | 
					    the last 30 minutes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Available GET parameters:
 | 
				
			||||||
 | 
					    - "date": return logs for a specified date (
 | 
				
			||||||
 | 
					    - "full": (staff user only) don't merge diffusion and logs
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    serializer_class = LogInfoSerializer
 | 
				
			||||||
 | 
					    queryset = Log.objects.all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.date = self.get_date()
 | 
				
			||||||
 | 
					        if self.date is None:
 | 
				
			||||||
 | 
					            self.min_date = tz.now() - tz.timedelta(minutes=30)
 | 
				
			||||||
 | 
					        return super().get(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_object_list(self, logs, full):
 | 
				
			||||||
 | 
					        return [LogInfo(obj) for obj in super().get_object_list(logs, full)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_serializer(self, queryset, *args, **kwargs):
 | 
				
			||||||
 | 
					        full = bool(self.request.GET.get('full'))
 | 
				
			||||||
 | 
					        return super().get_serializer(self.get_object_list(queryset, full),
 | 
				
			||||||
 | 
					                                      *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										26
									
								
								aircox/viewsets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								aircox/viewsets.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					from django.db.models import Q
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from rest_framework import viewsets
 | 
				
			||||||
 | 
					from django_filters import rest_framework as filters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .models import Sound
 | 
				
			||||||
 | 
					from .serializers import SoundSerializer
 | 
				
			||||||
 | 
					from .views import BaseAPIView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SoundFilter(filters.FilterSet):
 | 
				
			||||||
 | 
					    station = filters.NumberFilter(field_name='program__station__id')
 | 
				
			||||||
 | 
					    program = filters.NumberFilter(field_name='program_id')
 | 
				
			||||||
 | 
					    episode = filters.NumberFilter(field_name='episode_id')
 | 
				
			||||||
 | 
					    search = filters.CharFilter(field_name='search', method='search_filter')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def search_filter(self, queryset, name, value):
 | 
				
			||||||
 | 
					        return queryset.search(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SoundViewSet(BaseAPIView, viewsets.ModelViewSet):
 | 
				
			||||||
 | 
					    serializer_class = SoundSerializer
 | 
				
			||||||
 | 
					    queryset = Sound.objects.available().order_by('-pk')
 | 
				
			||||||
 | 
					    filter_backends = (filters.DjangoFilterBackend,)
 | 
				
			||||||
 | 
					    filterset_class = SoundFilter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -41,7 +41,8 @@
 | 
				
			|||||||
            <h6 class="title is-6 is-marginless">{% trans "Add sound" %}</h6>
 | 
					            <h6 class="title is-6 is-marginless">{% trans "Add sound" %}</h6>
 | 
				
			||||||
            <form class="columns" @submit.prevent="source.push($event.target.elements['sound_id'].value)">
 | 
					            <form class="columns" @submit.prevent="source.push($event.target.elements['sound_id'].value)">
 | 
				
			||||||
                <div class="column field is-small">
 | 
					                <div class="column field is-small">
 | 
				
			||||||
                    <a-autocomplete url="{% url "admin:api:streamer-queue-autocomplete-push" station_pk=station.pk %}?q=${query}"
 | 
					                    {# TODO: select station => change the shit #}
 | 
				
			||||||
 | 
					                    <a-autocomplete url="{% url "aircox:sound-list" %}?station={{ station.pk }}&search=${query}"
 | 
				
			||||||
                        class="is-fullwidth"
 | 
					                        class="is-fullwidth"
 | 
				
			||||||
                        :model="Sound" field="name" value-field="sound_id" value-attr="id"
 | 
					                        :model="Sound" field="name" value-field="sound_id" value-attr="id"
 | 
				
			||||||
                        {# FIXME dirty hack awaiting the vue component #}
 | 
					                        {# FIXME dirty hack awaiting the vue component #}
 | 
				
			||||||
 | 
				
			|||||||
@ -73,7 +73,7 @@ streamers = Streamers()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class BaseControllerAPIView(viewsets.ViewSet):
 | 
					class BaseControllerAPIView(viewsets.ViewSet):
 | 
				
			||||||
    permission_classes = (IsAdminUser,)
 | 
					    permission_classes = (IsAdminUser,)
 | 
				
			||||||
    serializer = None
 | 
					    serializer_class = None
 | 
				
			||||||
    streamer = None
 | 
					    streamer = None
 | 
				
			||||||
    object = None
 | 
					    object = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -85,7 +85,7 @@ class BaseControllerAPIView(viewsets.ViewSet):
 | 
				
			|||||||
        return streamers[id]
 | 
					        return streamers[id]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_serializer(self, **kwargs):
 | 
					    def get_serializer(self, **kwargs):
 | 
				
			||||||
        return self.serializer(self.object, **kwargs)
 | 
					        return self.serializer_class(self.object, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def serialize(self, obj, **kwargs):
 | 
					    def serialize(self, obj, **kwargs):
 | 
				
			||||||
        self.object = obj
 | 
					        self.object = obj
 | 
				
			||||||
@ -98,11 +98,11 @@ class BaseControllerAPIView(viewsets.ViewSet):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RequestViewSet(BaseControllerAPIView):
 | 
					class RequestViewSet(BaseControllerAPIView):
 | 
				
			||||||
    serializer = RequestSerializer
 | 
					    serializer_class = RequestSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StreamerViewSet(BaseControllerAPIView):
 | 
					class StreamerViewSet(BaseControllerAPIView):
 | 
				
			||||||
    serializer = StreamerSerializer
 | 
					    serializer_class = StreamerSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def retrieve(self, request, pk=None):
 | 
					    def retrieve(self, request, pk=None):
 | 
				
			||||||
        return Response(self.serialize(self.streamer))
 | 
					        return Response(self.serialize(self.streamer))
 | 
				
			||||||
@ -121,7 +121,7 @@ class StreamerViewSet(BaseControllerAPIView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SourceViewSet(BaseControllerAPIView):
 | 
					class SourceViewSet(BaseControllerAPIView):
 | 
				
			||||||
    serializer = SourceSerializer
 | 
					    serializer_class = SourceSerializer
 | 
				
			||||||
    model = controllers.Source
 | 
					    model = controllers.Source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_sources(self):
 | 
					    def get_sources(self):
 | 
				
			||||||
@ -168,27 +168,17 @@ class SourceViewSet(BaseControllerAPIView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PlaylistSourceViewSet(SourceViewSet):
 | 
					class PlaylistSourceViewSet(SourceViewSet):
 | 
				
			||||||
    serializer = PlaylistSerializer
 | 
					    serializer_class = PlaylistSerializer
 | 
				
			||||||
    model = controllers.PlaylistSource
 | 
					    model = controllers.PlaylistSource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class QueueSourceViewSet(SourceViewSet):
 | 
					class QueueSourceViewSet(SourceViewSet):
 | 
				
			||||||
    serializer = QueueSourceSerializer
 | 
					    serializer_class = QueueSourceSerializer
 | 
				
			||||||
    model = controllers.QueueSource
 | 
					    model = controllers.QueueSource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_sound_queryset(self):
 | 
					    def get_sound_queryset(self):
 | 
				
			||||||
        return Sound.objects.station(self.request.station).archive()
 | 
					        return Sound.objects.station(self.request.station).archive()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @action(detail=False, url_path='autocomplete/push',
 | 
					 | 
				
			||||||
            url_name='autocomplete-push')
 | 
					 | 
				
			||||||
    def autcomplete_push(self, request):
 | 
					 | 
				
			||||||
        query = request.GET.get('q')
 | 
					 | 
				
			||||||
        qs = self.get_sound_queryset().search(query)
 | 
					 | 
				
			||||||
        serializer = SoundSerializer(qs, many=True, context={
 | 
					 | 
				
			||||||
            'request': self.request
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        return Response({'results': serializer.data})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @action(detail=True, methods=['POST'])
 | 
					    @action(detail=True, methods=['POST'])
 | 
				
			||||||
    def push(self, request, pk):
 | 
					    def push(self, request, pk):
 | 
				
			||||||
        if not request.data.get('sound_id'):
 | 
					        if not request.data.get('sound_id'):
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					import {setEcoTimeout} from 'public/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class {
 | 
					export default class {
 | 
				
			||||||
    constructor(url, timeout) {
 | 
					    constructor(url, timeout) {
 | 
				
			||||||
@ -30,7 +32,7 @@ export default class {
 | 
				
			|||||||
            if(promise != this.promise)
 | 
					            if(promise != this.promise)
 | 
				
			||||||
                return [];
 | 
					                return [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            window.setTimeout(() => this.refresh(), this.timeout*1000)
 | 
					            setEcoTimeout(() => this.refresh(), this.timeout*1000)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        return promise
 | 
					        return promise
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										15
									
								
								assets/public/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								assets/public/utils.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function setEcoTimeout(func, ...args) {
 | 
				
			||||||
 | 
					    return setTimeout((...args) => {
 | 
				
			||||||
 | 
					        !document.hidden && func(...args)
 | 
				
			||||||
 | 
					    }, ...args)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function setEcoInterval(func, ...args) {
 | 
				
			||||||
 | 
					    return setInterval((...args) => {
 | 
				
			||||||
 | 
					        !document.hidden && func(...args)
 | 
				
			||||||
 | 
					    }, ...args)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Model from 'public/model';
 | 
					import Model from 'public/model';
 | 
				
			||||||
 | 
					import {setEcoInterval} from 'public/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Streamer extends Model {
 | 
					export class Streamer extends Model {
 | 
				
			||||||
@ -27,7 +28,7 @@ export class Source extends Model {
 | 
				
			|||||||
    constructor(data, {streamer=null, ...options}={}) {
 | 
					    constructor(data, {streamer=null, ...options}={}) {
 | 
				
			||||||
        super(data, options);
 | 
					        super(data, options);
 | 
				
			||||||
        this.streamer = streamer;
 | 
					        this.streamer = streamer;
 | 
				
			||||||
        setInterval(() => this.tick(), 1000)
 | 
					        setEcoInterval(() => this.tick(), 1000)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    get isQueue() { return false; }
 | 
					    get isQueue() { return false; }
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user