forked from rc/aircox
		
	work hard on this
This commit is contained in:
		
							
								
								
									
										208
									
								
								aircox/admin.py
									
									
									
									
									
								
							
							
						
						
									
										208
									
								
								aircox/admin.py
									
									
									
									
									
								
							@ -1,208 +0,0 @@
 | 
				
			|||||||
import copy
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django import forms
 | 
					 | 
				
			||||||
from django.contrib import admin
 | 
					 | 
				
			||||||
from django.contrib.contenttypes.admin import GenericTabularInline
 | 
					 | 
				
			||||||
from django.db import models
 | 
					 | 
				
			||||||
from django.utils.translation import ugettext as _, ugettext_lazy
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from aircox.models import *
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Inlines
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
class SoundInline(admin.TabularInline):
 | 
					 | 
				
			||||||
    model = Sound
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ScheduleInline(admin.TabularInline):
 | 
					 | 
				
			||||||
    model = Schedule
 | 
					 | 
				
			||||||
    extra = 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class StreamInline(admin.TabularInline):
 | 
					 | 
				
			||||||
    fields = ['delay', 'begin', 'end']
 | 
					 | 
				
			||||||
    model = Stream
 | 
					 | 
				
			||||||
    extra = 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SoundInline(admin.TabularInline):
 | 
					 | 
				
			||||||
    fields = ['type', 'path', 'duration','public']
 | 
					 | 
				
			||||||
    # readonly_fields = fields
 | 
					 | 
				
			||||||
    model = Sound
 | 
					 | 
				
			||||||
    extra = 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DiffusionInline(admin.StackedInline):
 | 
					 | 
				
			||||||
    model = Diffusion
 | 
					 | 
				
			||||||
    extra = 0
 | 
					 | 
				
			||||||
    fields = ['type', 'start', 'end']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class NameableAdmin(admin.ModelAdmin):
 | 
					 | 
				
			||||||
    fields = [ 'name' ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    list_display = ['id', 'name']
 | 
					 | 
				
			||||||
    list_filter = []
 | 
					 | 
				
			||||||
    search_fields = ['name',]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TrackInline(GenericTabularInline):
 | 
					 | 
				
			||||||
    ct_field = 'related_type'
 | 
					 | 
				
			||||||
    ct_fk_field = 'related_id'
 | 
					 | 
				
			||||||
    model = Track
 | 
					 | 
				
			||||||
    extra = 0
 | 
					 | 
				
			||||||
    fields = ('artist', 'title', 'info', 'position', 'in_seconds', 'tags')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    list_display = ['artist','title','tags','related']
 | 
					 | 
				
			||||||
    list_filter = ['artist','title','tags']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@admin.register(Sound)
 | 
					 | 
				
			||||||
class SoundAdmin(NameableAdmin):
 | 
					 | 
				
			||||||
    fields = None
 | 
					 | 
				
			||||||
    list_display = ['id', 'name', 'program', 'type', 'duration', 'mtime',
 | 
					 | 
				
			||||||
                    'public', 'good_quality', 'path']
 | 
					 | 
				
			||||||
    list_filter = ('program', 'type', 'good_quality', 'public')
 | 
					 | 
				
			||||||
    fieldsets = [
 | 
					 | 
				
			||||||
        (None, { 'fields': NameableAdmin.fields +
 | 
					 | 
				
			||||||
                           ['path', 'type', 'program', 'diffusion'] } ),
 | 
					 | 
				
			||||||
        (None, { 'fields': ['embed', 'duration', 'public', 'mtime'] }),
 | 
					 | 
				
			||||||
        (None, { 'fields': ['good_quality' ] } )
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
    readonly_fields = ('path', 'duration',)
 | 
					 | 
				
			||||||
    inlines = [TrackInline]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@admin.register(Stream)
 | 
					 | 
				
			||||||
class StreamAdmin(admin.ModelAdmin):
 | 
					 | 
				
			||||||
    list_display = ('id', 'program', 'delay', 'begin', 'end')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@admin.register(Program)
 | 
					 | 
				
			||||||
class ProgramAdmin(NameableAdmin):
 | 
					 | 
				
			||||||
    def schedule(self, obj):
 | 
					 | 
				
			||||||
        return Schedule.objects.filter(program = obj).count() > 0
 | 
					 | 
				
			||||||
    schedule.boolean = True
 | 
					 | 
				
			||||||
    schedule.short_description = _("Schedule")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    list_display = ('id', 'name', 'active', 'schedule', 'sync', 'station')
 | 
					 | 
				
			||||||
    fields = NameableAdmin.fields + [ 'active', 'station','sync' ]
 | 
					 | 
				
			||||||
    inlines = [ ScheduleInline, StreamInline ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # SO#8074161
 | 
					 | 
				
			||||||
    #def get_form(self, request, obj=None, **kwargs):
 | 
					 | 
				
			||||||
        #if obj:
 | 
					 | 
				
			||||||
        #    if Schedule.objects.filter(program = obj).count():
 | 
					 | 
				
			||||||
        #        self.inlines.remove(StreamInline)
 | 
					 | 
				
			||||||
        #    elif Stream.objects.filter(program = obj).count():
 | 
					 | 
				
			||||||
        #        self.inlines.remove(ScheduleInline)
 | 
					 | 
				
			||||||
        #return super().get_form(request, obj, **kwargs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@admin.register(Diffusion)
 | 
					 | 
				
			||||||
class DiffusionAdmin(admin.ModelAdmin):
 | 
					 | 
				
			||||||
    def archives(self, obj):
 | 
					 | 
				
			||||||
        sounds = [ str(s) for s in obj.get_sounds(archive=True)]
 | 
					 | 
				
			||||||
        return ', '.join(sounds) if sounds else ''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def conflicts_(self, obj):
 | 
					 | 
				
			||||||
        if obj.conflicts.count():
 | 
					 | 
				
			||||||
            return obj.conflicts.count()
 | 
					 | 
				
			||||||
        return ''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def start_date(self, obj):
 | 
					 | 
				
			||||||
        return obj.local_date.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')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def first(self, obj):
 | 
					 | 
				
			||||||
        return obj.initial.start if obj.initial else ''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    list_display = ('id', 'program', 'start_date', 'end_date', 'type', 'first', 'archives', 'conflicts_')
 | 
					 | 
				
			||||||
    list_filter = ('type', 'start', 'program')
 | 
					 | 
				
			||||||
    list_editable = ('type',)
 | 
					 | 
				
			||||||
    ordering = ('-start', 'id')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fields = ['type', 'start', 'end', 'initial', 'program', 'conflicts']
 | 
					 | 
				
			||||||
    readonly_fields = ('conflicts',)
 | 
					 | 
				
			||||||
    inlines = [ DiffusionInline, SoundInline ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_form(self, request, obj=None, **kwargs):
 | 
					 | 
				
			||||||
        if request.user.has_perm('aircox_program.programming'):
 | 
					 | 
				
			||||||
            self.readonly_fields = []
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            self.readonly_fields = ['program', 'start', 'end']
 | 
					 | 
				
			||||||
        return super().get_form(request, obj, **kwargs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_object(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        We want rerun to redirect to the given object.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        obj = super().get_object(*args, **kwargs)
 | 
					 | 
				
			||||||
        if obj and obj.initial:
 | 
					 | 
				
			||||||
            obj = obj.initial
 | 
					 | 
				
			||||||
        return obj
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_queryset(self, request):
 | 
					 | 
				
			||||||
        qs = super().get_queryset(request)
 | 
					 | 
				
			||||||
        if request.GET and len(request.GET):
 | 
					 | 
				
			||||||
            return qs
 | 
					 | 
				
			||||||
        return qs.exclude(type = Diffusion.Type.unconfirmed)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@admin.register(Schedule)
 | 
					 | 
				
			||||||
class ScheduleAdmin(admin.ModelAdmin):
 | 
					 | 
				
			||||||
    def program_name(self, obj):
 | 
					 | 
				
			||||||
        return obj.program.name
 | 
					 | 
				
			||||||
    program_name.short_description = _('Program')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def day(self, obj):
 | 
					 | 
				
			||||||
        return '' # obj.date.strftime('%A')
 | 
					 | 
				
			||||||
    day.short_description = _('Day')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def rerun(self, obj):
 | 
					 | 
				
			||||||
        return obj.initial != None
 | 
					 | 
				
			||||||
    rerun.short_description = _('Rerun')
 | 
					 | 
				
			||||||
    rerun.boolean = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    list_filter = ['frequency', 'program']
 | 
					 | 
				
			||||||
    list_display = ['id', 'program_name', 'frequency', 'day', 'date',
 | 
					 | 
				
			||||||
                    'time', 'duration', 'timezone', 'rerun']
 | 
					 | 
				
			||||||
    list_editable = ['time', 'timezone', 'duration']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_readonly_fields(self, request, obj=None):
 | 
					 | 
				
			||||||
        if obj:
 | 
					 | 
				
			||||||
            return ['program', 'date', 'frequency']
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            return []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@admin.register(Track)
 | 
					 | 
				
			||||||
class TrackAdmin(admin.ModelAdmin):
 | 
					 | 
				
			||||||
    list_display = ['id', 'title', 'artist', 'position', 'in_seconds', 'related']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# TODO: sort & redo
 | 
					 | 
				
			||||||
class PortInline(admin.StackedInline):
 | 
					 | 
				
			||||||
    model = Port
 | 
					 | 
				
			||||||
    extra = 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@admin.register(Station)
 | 
					 | 
				
			||||||
class StationAdmin(admin.ModelAdmin):
 | 
					 | 
				
			||||||
    inlines = [ PortInline ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@admin.register(Log)
 | 
					 | 
				
			||||||
class LogAdmin(admin.ModelAdmin):
 | 
					 | 
				
			||||||
    list_display = ['id', 'date', 'station', 'source', 'type', 'comment', 'diffusion', 'sound', 'track']
 | 
					 | 
				
			||||||
    list_filter = ['date', 'source', 'diffusion', 'sound', 'track']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
admin.site.register(Port)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							
								
								
									
										5
									
								
								aircox/admin/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								aircox/admin/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					from .base import *
 | 
				
			||||||
 | 
					from .diffusion import DiffusionAdmin
 | 
				
			||||||
 | 
					# from .playlist import PlaylistAdmin
 | 
				
			||||||
 | 
					from .sound import SoundAdmin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										94
									
								
								aircox/admin/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								aircox/admin/base.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					from django import forms
 | 
				
			||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.urls import reverse
 | 
				
			||||||
 | 
					from django.utils.translation import ugettext as _, ugettext_lazy
 | 
				
			||||||
 | 
					from django.utils.safestring import mark_safe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from adminsortable2.admin import SortableInlineAdminMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aircox.models import *
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ScheduleInline(admin.TabularInline):
 | 
				
			||||||
 | 
					    model = Schedule
 | 
				
			||||||
 | 
					    extra = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StreamInline(admin.TabularInline):
 | 
				
			||||||
 | 
					    fields = ['delay', 'begin', 'end']
 | 
				
			||||||
 | 
					    model = Stream
 | 
				
			||||||
 | 
					    extra = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NameableAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    fields = [ 'name' ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list_display = ['id', 'name']
 | 
				
			||||||
 | 
					    list_filter = []
 | 
				
			||||||
 | 
					    search_fields = ['name',]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Stream)
 | 
				
			||||||
 | 
					class StreamAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    list_display = ('id', 'program', 'delay', 'begin', 'end')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Program)
 | 
				
			||||||
 | 
					class ProgramAdmin(NameableAdmin):
 | 
				
			||||||
 | 
					    def schedule(self, obj):
 | 
				
			||||||
 | 
					        return Schedule.objects.filter(program = obj).count() > 0
 | 
				
			||||||
 | 
					    schedule.boolean = True
 | 
				
			||||||
 | 
					    schedule.short_description = _("Schedule")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list_display = ('id', 'name', 'active', 'schedule', 'sync', 'station')
 | 
				
			||||||
 | 
					    fields = NameableAdmin.fields + [ 'active', 'station','sync' ]
 | 
				
			||||||
 | 
					    inlines = [ ScheduleInline, StreamInline ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Schedule)
 | 
				
			||||||
 | 
					class ScheduleAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    def program_name(self, obj):
 | 
				
			||||||
 | 
					        return obj.program.name
 | 
				
			||||||
 | 
					    program_name.short_description = _('Program')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def day(self, obj):
 | 
				
			||||||
 | 
					        return '' # obj.date.strftime('%A')
 | 
				
			||||||
 | 
					    day.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 = ['id', 'program_name', 'frequency', 'day', 'date',
 | 
				
			||||||
 | 
					                    'time', 'duration', 'timezone', 'rerun']
 | 
				
			||||||
 | 
					    list_editable = ['time', 'timezone', 'duration']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_readonly_fields(self, request, obj=None):
 | 
				
			||||||
 | 
					        if obj:
 | 
				
			||||||
 | 
					            return ['program', 'date', 'frequency']
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TODO: sort & redo
 | 
				
			||||||
 | 
					class PortInline(admin.StackedInline):
 | 
				
			||||||
 | 
					    model = Port
 | 
				
			||||||
 | 
					    extra = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Station)
 | 
				
			||||||
 | 
					class StationAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    inlines = [PortInline]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Log)
 | 
				
			||||||
 | 
					class LogAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    list_display = ['id', 'date', 'station', 'source', 'type', 'comment', 'diffusion', 'sound', 'track']
 | 
				
			||||||
 | 
					    list_filter = ['date', 'source', 'diffusion', 'sound', 'track']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					admin.site.register(Port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										81
									
								
								aircox/admin/diffusion.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								aircox/admin/diffusion.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.utils.translation import ugettext as _, ugettext_lazy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aircox.models import Diffusion, Sound, Track
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .playlist import TracksInline
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SoundInline(admin.TabularInline):
 | 
				
			||||||
 | 
					    model = Sound
 | 
				
			||||||
 | 
					    fk_name = 'diffusion'
 | 
				
			||||||
 | 
					    fields = ['type', 'path', 'duration','public']
 | 
				
			||||||
 | 
					    readonly_fields = ['type']
 | 
				
			||||||
 | 
					    extra = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RediffusionInline(admin.StackedInline):
 | 
				
			||||||
 | 
					    model = Diffusion
 | 
				
			||||||
 | 
					    fk_name = 'initial'
 | 
				
			||||||
 | 
					    extra = 0
 | 
				
			||||||
 | 
					    fields = ['type', 'start', 'end']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Diffusion)
 | 
				
			||||||
 | 
					class DiffusionAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    def archives(self, obj):
 | 
				
			||||||
 | 
					        sounds = [str(s) for s in obj.get_sounds(archive=True)]
 | 
				
			||||||
 | 
					        return ', '.join(sounds) if sounds else ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def conflicts_count(self, obj):
 | 
				
			||||||
 | 
					        if obj.conflicts.count():
 | 
				
			||||||
 | 
					            return obj.conflicts.count()
 | 
				
			||||||
 | 
					        return ''
 | 
				
			||||||
 | 
					    conflicts_count.short_description = _('Conflicts')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start_date(self, obj):
 | 
				
			||||||
 | 
					        return obj.local_date.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')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def first(self, obj):
 | 
				
			||||||
 | 
					        return obj.initial.start if obj.initial else ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list_display = ('id', 'program', 'start_date', 'end_date', 'type', 'first', 'archives', 'conflicts_count')
 | 
				
			||||||
 | 
					    list_filter = ('type', 'start', 'program')
 | 
				
			||||||
 | 
					    list_editable = ('type',)
 | 
				
			||||||
 | 
					    ordering = ('-start', 'id')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fields = ['type', 'start', 'end', 'initial', 'program', 'conflicts']
 | 
				
			||||||
 | 
					    readonly_fields = ('conflicts',)
 | 
				
			||||||
 | 
					    inlines = [TracksInline, RediffusionInline, SoundInline]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_playlist(self, request, obj=None):
 | 
				
			||||||
 | 
					        return obj and getattr(obj, 'playlist', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_form(self, request, obj=None, **kwargs):
 | 
				
			||||||
 | 
					        if request.user.has_perm('aircox_program.programming'):
 | 
				
			||||||
 | 
					            self.readonly_fields = []
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.readonly_fields = ['program', 'start', 'end']
 | 
				
			||||||
 | 
					        return super().get_form(request, obj, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_object(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        We want rerun to redirect to the given object.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        obj = super().get_object(*args, **kwargs)
 | 
				
			||||||
 | 
					        if obj and obj.initial:
 | 
				
			||||||
 | 
					            obj = obj.initial
 | 
				
			||||||
 | 
					        return obj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_queryset(self, request):
 | 
				
			||||||
 | 
					        qs = super().get_queryset(request)
 | 
				
			||||||
 | 
					        if request.GET and len(request.GET):
 | 
				
			||||||
 | 
					            return qs
 | 
				
			||||||
 | 
					        return qs.exclude(type=Diffusion.Type.unconfirmed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										41
									
								
								aircox/admin/playlist.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								aircox/admin/playlist.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.utils.translation import ugettext as _, ugettext_lazy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from adminsortable2.admin import SortableInlineAdminMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aircox.models import 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']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Track)
 | 
				
			||||||
 | 
					class TrackAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    # TODO: url to filter by tag
 | 
				
			||||||
 | 
					    def tag_list(self, obj):
 | 
				
			||||||
 | 
					        return u", ".join(o.name for o in obj.tags.all())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list_display = ['pk', 'artist', 'title', 'tag_list', 'diffusion', 'sound']
 | 
				
			||||||
 | 
					    list_editable = ['artist', 'title']
 | 
				
			||||||
 | 
					    list_filter = ['sound', 'diffusion', 'artist', 'title', 'tags']
 | 
				
			||||||
 | 
					    fieldsets = [
 | 
				
			||||||
 | 
					        (_('Playlist'), {'fields': ['diffusion', 'sound', 'position', 'timestamp']}),
 | 
				
			||||||
 | 
					        (_('Info'), {'fields': ['artist', 'title', 'info', 'tags']}),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO on edit: readonly_fields = ['diffusion', 'sound']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#@admin.register(Playlist)
 | 
				
			||||||
 | 
					#class PlaylistAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					#    fields = ['diffusion', 'sound']
 | 
				
			||||||
 | 
					#    inlines = [TracksInline]
 | 
				
			||||||
 | 
					#    # TODO: dynamic read only fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										24
									
								
								aircox/admin/sound.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								aircox/admin/sound.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.utils.translation import ugettext as _, ugettext_lazy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aircox.models import Sound
 | 
				
			||||||
 | 
					from .base import NameableAdmin
 | 
				
			||||||
 | 
					from .playlist import TracksInline
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(Sound)
 | 
				
			||||||
 | 
					class SoundAdmin(NameableAdmin):
 | 
				
			||||||
 | 
					    fields = None
 | 
				
			||||||
 | 
					    list_display = ['id', 'name', 'program', 'type', 'duration', 'mtime',
 | 
				
			||||||
 | 
					                    'public', 'good_quality', 'path']
 | 
				
			||||||
 | 
					    list_filter = ('program', 'type', 'good_quality', 'public')
 | 
				
			||||||
 | 
					    fieldsets = [
 | 
				
			||||||
 | 
					        (None, {'fields': NameableAdmin.fields +
 | 
				
			||||||
 | 
					                          ['path', 'type', 'program', 'diffusion']}),
 | 
				
			||||||
 | 
					        (None, {'fields': ['embed', 'duration', 'public', 'mtime']}),
 | 
				
			||||||
 | 
					        (None, {'fields': ['good_quality']})
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    readonly_fields = ('path', 'duration',)
 | 
				
			||||||
 | 
					    inlines = [TracksInline]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -29,62 +29,65 @@ class Importer:
 | 
				
			|||||||
    path = None
 | 
					    path = None
 | 
				
			||||||
    data = None
 | 
					    data = None
 | 
				
			||||||
    tracks = None
 | 
					    tracks = None
 | 
				
			||||||
 | 
					    track_kwargs = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, related = None, path = None, save = False):
 | 
					    def __init__(self, path=None, **track_kwargs):
 | 
				
			||||||
        if path:
 | 
					        self.path = path
 | 
				
			||||||
            self.read(path)
 | 
					        self.track_kwargs = track_kwargs
 | 
				
			||||||
            if related:
 | 
					 | 
				
			||||||
                self.make_playlist(related, save)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def reset(self):
 | 
					    def reset(self):
 | 
				
			||||||
        self.data = None
 | 
					        self.data = None
 | 
				
			||||||
        self.tracks = None
 | 
					        self.tracks = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def read(self, path):
 | 
					    def run(self):
 | 
				
			||||||
        if not os.path.exists(path):
 | 
					        self.read()
 | 
				
			||||||
 | 
					        if self.track_kwargs.get('sound') is not None:
 | 
				
			||||||
 | 
					            self.make_playlist()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self):
 | 
				
			||||||
 | 
					        if not os.path.exists(self.path):
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
        with open(path, 'r') as file:
 | 
					        with open(self.path, 'r') as file:
 | 
				
			||||||
            logger.info('start reading csv ' + path)
 | 
					            logger.info('start reading csv ' + self.path)
 | 
				
			||||||
            self.path = path
 | 
					 | 
				
			||||||
            self.data = list(csv.DictReader(
 | 
					            self.data = list(csv.DictReader(
 | 
				
			||||||
                (row for row in file
 | 
					                (row for row in file
 | 
				
			||||||
                    if not (row.startswith('#') or row.startswith('\ufeff#'))
 | 
					                    if not (row.startswith('#') or row.startswith('\ufeff#'))
 | 
				
			||||||
                            and row.strip()
 | 
					                    and row.strip()),
 | 
				
			||||||
                ),
 | 
					                fieldnames=settings.AIRCOX_IMPORT_PLAYLIST_CSV_COLS,
 | 
				
			||||||
                fieldnames = settings.AIRCOX_IMPORT_PLAYLIST_CSV_COLS,
 | 
					                delimiter=settings.AIRCOX_IMPORT_PLAYLIST_CSV_DELIMITER,
 | 
				
			||||||
                delimiter = settings.AIRCOX_IMPORT_PLAYLIST_CSV_DELIMITER,
 | 
					                quotechar=settings.AIRCOX_IMPORT_PLAYLIST_CSV_TEXT_QUOTE,
 | 
				
			||||||
                quotechar = settings.AIRCOX_IMPORT_PLAYLIST_CSV_TEXT_QUOTE,
 | 
					 | 
				
			||||||
            ))
 | 
					            ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def make_playlist(self, related, save = False):
 | 
					    def make_playlist(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Make a playlist from the read data, and return it. If save is
 | 
					        Make a playlist from the read data, and return it. If save is
 | 
				
			||||||
        true, save it into the database
 | 
					        true, save it into the database
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					        if self.track_kwargs.get('sound') is None:
 | 
				
			||||||
 | 
					            logger.error('related track\'s sound is missing. Skip import of ' +
 | 
				
			||||||
 | 
					                         self.path + '.')
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        maps = settings.AIRCOX_IMPORT_PLAYLIST_CSV_COLS
 | 
					        maps = settings.AIRCOX_IMPORT_PLAYLIST_CSV_COLS
 | 
				
			||||||
        tracks = []
 | 
					        tracks = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logger.info('parse csv file ' + self.path)
 | 
					        logger.info('parse csv file ' + self.path)
 | 
				
			||||||
        in_seconds = ('minutes' or 'seconds') in maps
 | 
					        has_timestamp = ('minutes' or 'seconds') in maps
 | 
				
			||||||
        for index, line in enumerate(self.data):
 | 
					        for index, line in enumerate(self.data):
 | 
				
			||||||
            if ('title' or 'artist') not in line:
 | 
					            if ('title' or 'artist') not in line:
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
 | 
					 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                position = \
 | 
					                timestamp = int(line.get('minutes') or 0) * 60 + \
 | 
				
			||||||
                    int(line.get('minutes') or 0) * 60 + \
 | 
					                            int(line.get('seconds') or 0) \
 | 
				
			||||||
                    int(line.get('seconds') or 0) \
 | 
					                            if has_timestamp else None
 | 
				
			||||||
                    if in_seconds else index
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                track, created = Track.objects.get_or_create(
 | 
					                track, created = Track.objects.get_or_create(
 | 
				
			||||||
                    related_type = ContentType.objects.get_for_model(related),
 | 
					                    title=line.get('title'),
 | 
				
			||||||
                    related_id = related.pk,
 | 
					                    artist=line.get('artist'),
 | 
				
			||||||
                    title = line.get('title'),
 | 
					                    position=index,
 | 
				
			||||||
                    artist = line.get('artist'),
 | 
					                    **self.track_kwargs
 | 
				
			||||||
                    position = position,
 | 
					 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					                track.timestamp = timestamp
 | 
				
			||||||
                track.in_seconds = in_seconds
 | 
					 | 
				
			||||||
                track.info = line.get('info')
 | 
					                track.info = line.get('info')
 | 
				
			||||||
                tags = line.get('tags')
 | 
					                tags = line.get('tags')
 | 
				
			||||||
                if tags:
 | 
					                if tags:
 | 
				
			||||||
@ -97,8 +100,7 @@ class Importer:
 | 
				
			|||||||
                )
 | 
					                )
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if save:
 | 
					            track.save()
 | 
				
			||||||
                track.save()
 | 
					 | 
				
			||||||
            tracks.append(track)
 | 
					            tracks.append(track)
 | 
				
			||||||
        self.tracks = tracks
 | 
					        self.tracks = tracks
 | 
				
			||||||
        return tracks
 | 
					        return tracks
 | 
				
			||||||
@ -107,10 +109,8 @@ class Importer:
 | 
				
			|||||||
class Command (BaseCommand):
 | 
					class Command (BaseCommand):
 | 
				
			||||||
    help= __doc__
 | 
					    help= __doc__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def add_arguments (self, parser):
 | 
					    def add_arguments(self, parser):
 | 
				
			||||||
        parser.formatter_class=RawTextHelpFormatter
 | 
					        parser.formatter_class=RawTextHelpFormatter
 | 
				
			||||||
        now = tz.datetime.today()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        parser.add_argument(
 | 
					        parser.add_argument(
 | 
				
			||||||
            'path', metavar='PATH', type=str,
 | 
					            'path', metavar='PATH', type=str,
 | 
				
			||||||
            help='path of the input playlist to read'
 | 
					            help='path of the input playlist to read'
 | 
				
			||||||
@ -128,27 +128,24 @@ class Command (BaseCommand):
 | 
				
			|||||||
    def handle (self, path, *args, **options):
 | 
					    def handle (self, path, *args, **options):
 | 
				
			||||||
        # FIXME: absolute/relative path of sounds vs given path
 | 
					        # FIXME: absolute/relative path of sounds vs given path
 | 
				
			||||||
        if options.get('sound'):
 | 
					        if options.get('sound'):
 | 
				
			||||||
            related = Sound.objects.filter(
 | 
					            sound = Sound.objects.filter(
 | 
				
			||||||
                path__icontains = options.get('sound')
 | 
					                path__icontains=options.get('sound')
 | 
				
			||||||
            ).first()
 | 
					            ).first()
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            path_, ext = os.path.splitext(path)
 | 
					            path_, ext = os.path.splitext(path)
 | 
				
			||||||
            related = Sound.objects.filter(path__icontains = path_).first()
 | 
					            sound = Sound.objects.filter(path__icontains=path_).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not related:
 | 
					        if not sound:
 | 
				
			||||||
            logger.error('no sound found in the database for the path ' \
 | 
					            logger.error('no sound found in the database for the path ' \
 | 
				
			||||||
                         '{path}'.format(path=path))
 | 
					                         '{path}'.format(path=path))
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if options.get('diffusion') and related.diffusion:
 | 
					        if options.get('diffusion') and sound.diffusion:
 | 
				
			||||||
            related = related.diffusion
 | 
					            sound = sound.diffusion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        importer = Importer(related = related, path = path, save = True)
 | 
					        importer = Importer(path, sound=sound).run()
 | 
				
			||||||
        for track in importer.tracks:
 | 
					        for track in importer.tracks:
 | 
				
			||||||
            logger.info('imported track at {pos}: {title}, by '
 | 
					            logger.info('track #{pos} imported: {title}, by {artist}'.format(
 | 
				
			||||||
                        '{artist}'.format(
 | 
					                pos=track.position, title=track.title, artist=track.artist
 | 
				
			||||||
                    pos = track.position,
 | 
					            ))
 | 
				
			||||||
                    title = track.title, artist = track.artist
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -43,6 +43,7 @@ import aircox.utils as utils
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger('aircox.tools')
 | 
					logger = logging.getLogger('aircox.tools')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SoundInfo:
 | 
					class SoundInfo:
 | 
				
			||||||
    name = ''
 | 
					    name = ''
 | 
				
			||||||
    sound = None
 | 
					    sound = None
 | 
				
			||||||
@ -76,7 +77,7 @@ class SoundInfo:
 | 
				
			|||||||
                      file_name)
 | 
					                      file_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not (r and r.groupdict()):
 | 
					        if not (r and r.groupdict()):
 | 
				
			||||||
            r = { 'name': file_name }
 | 
					            r = {'name': file_name}
 | 
				
			||||||
            logger.info('file name can not be parsed -> %s', value)
 | 
					            logger.info('file name can not be parsed -> %s', value)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            r = r.groupdict()
 | 
					            r = r.groupdict()
 | 
				
			||||||
@ -93,7 +94,7 @@ class SoundInfo:
 | 
				
			|||||||
        self.n = r.get('n')
 | 
					        self.n = r.get('n')
 | 
				
			||||||
        return r
 | 
					        return r
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, path = '', sound = None):
 | 
					    def __init__(self, path='', sound=None):
 | 
				
			||||||
        self.path = path
 | 
					        self.path = path
 | 
				
			||||||
        self.sound = sound
 | 
					        self.sound = sound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -107,7 +108,7 @@ class SoundInfo:
 | 
				
			|||||||
            self.duration = duration
 | 
					            self.duration = duration
 | 
				
			||||||
            return duration
 | 
					            return duration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_sound(self, save = True, **kwargs):
 | 
					    def get_sound(self, save=True, **kwargs):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Get or create a sound using self info.
 | 
					        Get or create a sound using self info.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -115,8 +116,8 @@ class SoundInfo:
 | 
				
			|||||||
        (if save is True, sync to DB), and check for a playlist file.
 | 
					        (if save is True, sync to DB), and check for a playlist file.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        sound, created = Sound.objects.get_or_create(
 | 
					        sound, created = Sound.objects.get_or_create(
 | 
				
			||||||
            path = self.path,
 | 
					            path=self.path,
 | 
				
			||||||
            defaults = kwargs
 | 
					            defaults=kwargs
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        if created or sound.check_on_file():
 | 
					        if created or sound.check_on_file():
 | 
				
			||||||
            logger.info('sound is new or have been modified -> %s', self.path)
 | 
					            logger.info('sound is new or have been modified -> %s', self.path)
 | 
				
			||||||
@ -127,7 +128,7 @@ class SoundInfo:
 | 
				
			|||||||
        self.sound = sound
 | 
					        self.sound = sound
 | 
				
			||||||
        return sound
 | 
					        return sound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def find_playlist(self, sound, use_default = True):
 | 
					    def find_playlist(self, sound, use_default=True):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Find a playlist file corresponding to the sound path, such as:
 | 
					        Find a playlist file corresponding to the sound path, such as:
 | 
				
			||||||
            my_sound.ogg => my_sound.csv
 | 
					            my_sound.ogg => my_sound.csv
 | 
				
			||||||
@ -135,11 +136,11 @@ class SoundInfo:
 | 
				
			|||||||
        If use_default is True and there is no playlist find found,
 | 
					        If use_default is True and there is no playlist find found,
 | 
				
			||||||
        use sound file's metadata.
 | 
					        use sound file's metadata.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if sound.tracks.count():
 | 
					        if sound.track_set.count():
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        import aircox.management.commands.import_playlist \
 | 
					        import aircox.management.commands.import_playlist \
 | 
				
			||||||
                as import_playlist
 | 
					            as import_playlist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # no playlist, try to retrieve metadata
 | 
					        # no playlist, try to retrieve metadata
 | 
				
			||||||
        path = os.path.splitext(self.sound.path)[0] + '.csv'
 | 
					        path = os.path.splitext(self.sound.path)[0] + '.csv'
 | 
				
			||||||
@ -151,9 +152,9 @@ class SoundInfo:
 | 
				
			|||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # else, import
 | 
					        # else, import
 | 
				
			||||||
        import_playlist.Importer(sound, path, save=True)
 | 
					        import_playlist.Importer(path, sound=sound).run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def find_diffusion(self, program, save = True):
 | 
					    def find_diffusion(self, program, save=True):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        For a given program, check if there is an initial diffusion
 | 
					        For a given program, check if there is an initial diffusion
 | 
				
			||||||
        to associate to, using the date info we have. Update self.sound
 | 
					        to associate to, using the date info we have. Update self.sound
 | 
				
			||||||
@ -163,7 +164,7 @@ class SoundInfo:
 | 
				
			|||||||
        rerun.
 | 
					        rerun.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if self.year == None or not self.sound or self.sound.diffusion:
 | 
					        if self.year == None or not self.sound or self.sound.diffusion:
 | 
				
			||||||
            return;
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.hour is None:
 | 
					        if self.hour is None:
 | 
				
			||||||
            date = datetime.date(self.year, self.month, self.day)
 | 
					            date = datetime.date(self.year, self.month, self.day)
 | 
				
			||||||
@ -173,7 +174,7 @@ class SoundInfo:
 | 
				
			|||||||
            date = tz.get_current_timezone().localize(date)
 | 
					            date = tz.get_current_timezone().localize(date)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        qs = Diffusion.objects.station(program.station).after(date) \
 | 
					        qs = Diffusion.objects.station(program.station).after(date) \
 | 
				
			||||||
                      .filter(program = program, initial__isnull = True)
 | 
					                      .filter(program=program, initial__isnull=True)
 | 
				
			||||||
        diffusion = qs.first()
 | 
					        diffusion = qs.first()
 | 
				
			||||||
        if not diffusion:
 | 
					        if not diffusion:
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
@ -190,18 +191,19 @@ class MonitorHandler(PatternMatchingEventHandler):
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    Event handler for watchdog, in order to be used in monitoring.
 | 
					    Event handler for watchdog, in order to be used in monitoring.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, subdir):
 | 
					    def __init__(self, subdir):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        subdir: AIRCOX_SOUND_ARCHIVES_SUBDIR or AIRCOX_SOUND_EXCERPTS_SUBDIR
 | 
					        subdir: AIRCOX_SOUND_ARCHIVES_SUBDIR or AIRCOX_SOUND_EXCERPTS_SUBDIR
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.subdir = subdir
 | 
					        self.subdir = subdir
 | 
				
			||||||
        if self.subdir == settings.AIRCOX_SOUND_ARCHIVES_SUBDIR:
 | 
					        if self.subdir == settings.AIRCOX_SOUND_ARCHIVES_SUBDIR:
 | 
				
			||||||
            self.sound_kwargs = { 'type': Sound.Type.archive }
 | 
					            self.sound_kwargs = {'type': Sound.Type.archive}
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.sound_kwargs = { 'type': Sound.Type.excerpt }
 | 
					            self.sound_kwargs = {'type': Sound.Type.excerpt}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        patterns = ['*/{}/*{}'.format(self.subdir, ext)
 | 
					        patterns = ['*/{}/*{}'.format(self.subdir, ext)
 | 
				
			||||||
                    for ext in settings.AIRCOX_SOUND_FILE_EXT ]
 | 
					                    for ext in settings.AIRCOX_SOUND_FILE_EXT]
 | 
				
			||||||
        super().__init__(patterns=patterns, ignore_directories=True)
 | 
					        super().__init__(patterns=patterns, ignore_directories=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def on_created(self, event):
 | 
					    def on_created(self, event):
 | 
				
			||||||
@ -215,14 +217,14 @@ class MonitorHandler(PatternMatchingEventHandler):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        si = SoundInfo(event.src_path)
 | 
					        si = SoundInfo(event.src_path)
 | 
				
			||||||
        self.sound_kwargs['program'] = program
 | 
					        self.sound_kwargs['program'] = program
 | 
				
			||||||
        si.get_sound(save = True, **self.sound_kwargs)
 | 
					        si.get_sound(save=True, **self.sound_kwargs)
 | 
				
			||||||
        if si.year is not None:
 | 
					        if si.year is not None:
 | 
				
			||||||
            si.find_diffusion(program)
 | 
					            si.find_diffusion(program)
 | 
				
			||||||
        si.sound.save(True)
 | 
					        si.sound.save(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def on_deleted(self, event):
 | 
					    def on_deleted(self, event):
 | 
				
			||||||
        logger.info('sound deleted: %s', event.src_path)
 | 
					        logger.info('sound deleted: %s', event.src_path)
 | 
				
			||||||
        sound = Sound.objects.filter(path = event.src_path)
 | 
					        sound = Sound.objects.filter(path=event.src_path)
 | 
				
			||||||
        if sound:
 | 
					        if sound:
 | 
				
			||||||
            sound = sound[0]
 | 
					            sound = sound[0]
 | 
				
			||||||
            sound.type = sound.Type.removed
 | 
					            sound.type = sound.Type.removed
 | 
				
			||||||
@ -230,7 +232,7 @@ class MonitorHandler(PatternMatchingEventHandler):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def on_moved(self, event):
 | 
					    def on_moved(self, event):
 | 
				
			||||||
        logger.info('sound moved: %s -> %s', event.src_path, event.dest_path)
 | 
					        logger.info('sound moved: %s -> %s', event.src_path, event.dest_path)
 | 
				
			||||||
        sound = Sound.objects.filter(path = event.src_path)
 | 
					        sound = Sound.objects.filter(path=event.src_path)
 | 
				
			||||||
        if not sound:
 | 
					        if not sound:
 | 
				
			||||||
            self.on_modified(
 | 
					            self.on_modified(
 | 
				
			||||||
                FileModifiedEvent(event.dest_path)
 | 
					                FileModifiedEvent(event.dest_path)
 | 
				
			||||||
@ -242,18 +244,19 @@ class MonitorHandler(PatternMatchingEventHandler):
 | 
				
			|||||||
        if not sound.diffusion:
 | 
					        if not sound.diffusion:
 | 
				
			||||||
            program = Program.get_from_path(event.src_path)
 | 
					            program = Program.get_from_path(event.src_path)
 | 
				
			||||||
            if program:
 | 
					            if program:
 | 
				
			||||||
                si = SoundInfo(sound.path, sound = sound)
 | 
					                si = SoundInfo(sound.path, sound=sound)
 | 
				
			||||||
                if si.year is not None:
 | 
					                if si.year is not None:
 | 
				
			||||||
                    si.find_diffusion(program)
 | 
					                    si.find_diffusion(program)
 | 
				
			||||||
        sound.save()
 | 
					        sound.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Command(BaseCommand):
 | 
					class Command(BaseCommand):
 | 
				
			||||||
    help= __doc__
 | 
					    help = __doc__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def report(self, program = None, component = None, *content):
 | 
					    def report(self, program=None, component=None, *content):
 | 
				
			||||||
        if not component:
 | 
					        if not component:
 | 
				
			||||||
            logger.info('%s: %s', str(program), ' '.join([str(c) for c in content]))
 | 
					            logger.info('%s: %s', str(program),
 | 
				
			||||||
 | 
					                        ' '.join([str(c) for c in content]))
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            logger.info('%s, %s: %s', str(program), str(component),
 | 
					            logger.info('%s, %s: %s', str(program), str(component),
 | 
				
			||||||
                        ' '.join([str(c) for c in content]))
 | 
					                        ' '.join([str(c) for c in content]))
 | 
				
			||||||
@ -270,11 +273,11 @@ class Command(BaseCommand):
 | 
				
			|||||||
            logger.info('#%d %s', program.id, program.name)
 | 
					            logger.info('#%d %s', program.id, program.name)
 | 
				
			||||||
            self.scan_for_program(
 | 
					            self.scan_for_program(
 | 
				
			||||||
                program, settings.AIRCOX_SOUND_ARCHIVES_SUBDIR,
 | 
					                program, settings.AIRCOX_SOUND_ARCHIVES_SUBDIR,
 | 
				
			||||||
                type = Sound.Type.archive,
 | 
					                type=Sound.Type.archive,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            self.scan_for_program(
 | 
					            self.scan_for_program(
 | 
				
			||||||
                program, settings.AIRCOX_SOUND_EXCERPTS_SUBDIR,
 | 
					                program, settings.AIRCOX_SOUND_EXCERPTS_SUBDIR,
 | 
				
			||||||
                type = Sound.Type.excerpt,
 | 
					                type=Sound.Type.excerpt,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            dirs.append(os.path.join(program.path))
 | 
					            dirs.append(os.path.join(program.path))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -300,14 +303,14 @@ class Command(BaseCommand):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            si = SoundInfo(path)
 | 
					            si = SoundInfo(path)
 | 
				
			||||||
            sound_kwargs['program'] = program
 | 
					            sound_kwargs['program'] = program
 | 
				
			||||||
            si.get_sound(save = True, **sound_kwargs)
 | 
					            si.get_sound(save=True, **sound_kwargs)
 | 
				
			||||||
            si.find_diffusion(program, save = True)
 | 
					            si.find_diffusion(program, save=True)
 | 
				
			||||||
            si.find_playlist(si.sound)
 | 
					            si.find_playlist(si.sound)
 | 
				
			||||||
            sounds.append(si.sound.pk)
 | 
					            sounds.append(si.sound.pk)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # sounds in db & unchecked
 | 
					        # sounds in db & unchecked
 | 
				
			||||||
        sounds = Sound.objects.filter(path__startswith = subdir). \
 | 
					        sounds = Sound.objects.filter(path__startswith=subdir). \
 | 
				
			||||||
                               exclude(pk__in = sounds)
 | 
					            exclude(pk__in=sounds)
 | 
				
			||||||
        self.check_sounds(sounds)
 | 
					        self.check_sounds(sounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
@ -318,18 +321,18 @@ class Command(BaseCommand):
 | 
				
			|||||||
        # check files
 | 
					        # check files
 | 
				
			||||||
        for sound in qs:
 | 
					        for sound in qs:
 | 
				
			||||||
            if sound.check_on_file():
 | 
					            if sound.check_on_file():
 | 
				
			||||||
                sound.save(check = False)
 | 
					                sound.save(check=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def check_quality(self, check = False):
 | 
					    def check_quality(self, check=False):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Check all files where quality has been set to bad
 | 
					        Check all files where quality has been set to bad
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        import aircox.management.commands.sounds_quality_check \
 | 
					        import aircox.management.commands.sounds_quality_check \
 | 
				
			||||||
                as quality_check
 | 
					            as quality_check
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # get available sound files
 | 
					        # get available sound files
 | 
				
			||||||
        sounds = Sound.objects.filter(good_quality = False) \
 | 
					        sounds = Sound.objects.filter(good_quality=False) \
 | 
				
			||||||
                      .exclude(type = Sound.Type.removed)
 | 
					                      .exclude(type=Sound.Type.removed)
 | 
				
			||||||
        if check:
 | 
					        if check:
 | 
				
			||||||
            self.check_sounds(sounds)
 | 
					            self.check_sounds(sounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -341,11 +344,12 @@ class Command(BaseCommand):
 | 
				
			|||||||
        # check quality
 | 
					        # check quality
 | 
				
			||||||
        logger.info('quality check...',)
 | 
					        logger.info('quality check...',)
 | 
				
			||||||
        cmd = quality_check.Command()
 | 
					        cmd = quality_check.Command()
 | 
				
			||||||
        cmd.handle( files = files,
 | 
					        cmd.handle(files=files,
 | 
				
			||||||
                    **settings.AIRCOX_SOUND_QUALITY )
 | 
					                   **settings.AIRCOX_SOUND_QUALITY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # update stats
 | 
					        # update stats
 | 
				
			||||||
        logger.info('update stats in database')
 | 
					        logger.info('update stats in database')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def update_stats(sound_info, sound):
 | 
					        def update_stats(sound_info, sound):
 | 
				
			||||||
            stats = sound_info.get_file_stats()
 | 
					            stats = sound_info.get_file_stats()
 | 
				
			||||||
            if stats:
 | 
					            if stats:
 | 
				
			||||||
@ -353,25 +357,25 @@ class Command(BaseCommand):
 | 
				
			|||||||
                sound.duration = utils.seconds_to_time(duration)
 | 
					                sound.duration = utils.seconds_to_time(duration)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for sound_info in cmd.good:
 | 
					        for sound_info in cmd.good:
 | 
				
			||||||
            sound = Sound.objects.get(path = sound_info.path)
 | 
					            sound = Sound.objects.get(path=sound_info.path)
 | 
				
			||||||
            sound.good_quality = True
 | 
					            sound.good_quality = True
 | 
				
			||||||
            update_stats(sound_info, sound)
 | 
					            update_stats(sound_info, sound)
 | 
				
			||||||
            sound.save(check = False)
 | 
					            sound.save(check=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for sound_info in cmd.bad:
 | 
					        for sound_info in cmd.bad:
 | 
				
			||||||
            sound = Sound.objects.get(path = sound_info.path)
 | 
					            sound = Sound.objects.get(path=sound_info.path)
 | 
				
			||||||
            update_stats(sound_info, sound)
 | 
					            update_stats(sound_info, sound)
 | 
				
			||||||
            sound.save(check = False)
 | 
					            sound.save(check=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def monitor(self):
 | 
					    def monitor(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Run in monitor mode
 | 
					        Run in monitor mode
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        archives_handler = MonitorHandler(
 | 
					        archives_handler = MonitorHandler(
 | 
				
			||||||
            subdir = settings.AIRCOX_SOUND_ARCHIVES_SUBDIR
 | 
					            subdir=settings.AIRCOX_SOUND_ARCHIVES_SUBDIR
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        excerpts_handler = MonitorHandler(
 | 
					        excerpts_handler = MonitorHandler(
 | 
				
			||||||
            subdir = settings.AIRCOX_SOUND_EXCERPTS_SUBDIR
 | 
					            subdir=settings.AIRCOX_SOUND_EXCERPTS_SUBDIR
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        observer = Observer()
 | 
					        observer = Observer()
 | 
				
			||||||
@ -390,10 +394,10 @@ class Command(BaseCommand):
 | 
				
			|||||||
            time.sleep(1)
 | 
					            time.sleep(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def add_arguments(self, parser):
 | 
					    def add_arguments(self, parser):
 | 
				
			||||||
        parser.formatter_class=RawTextHelpFormatter
 | 
					        parser.formatter_class = RawTextHelpFormatter
 | 
				
			||||||
        parser.add_argument(
 | 
					        parser.add_argument(
 | 
				
			||||||
            '-q', '--quality_check', action='store_true',
 | 
					            '-q', '--quality_check', action='store_true',
 | 
				
			||||||
            help='Enable quality check using sound_quality_check on all ' \
 | 
					            help='Enable quality check using sound_quality_check on all '
 | 
				
			||||||
                 'sounds marqued as not good'
 | 
					                 'sounds marqued as not good'
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        parser.add_argument(
 | 
					        parser.add_argument(
 | 
				
			||||||
@ -411,7 +415,6 @@ class Command(BaseCommand):
 | 
				
			|||||||
        if options.get('scan'):
 | 
					        if options.get('scan'):
 | 
				
			||||||
            self.scan()
 | 
					            self.scan()
 | 
				
			||||||
        if options.get('quality_check'):
 | 
					        if options.get('quality_check'):
 | 
				
			||||||
            self.check_quality(check = (not options.get('scan')) )
 | 
					            self.check_quality(check=(not options.get('scan')))
 | 
				
			||||||
        if options.get('monitor'):
 | 
					        if options.get('monitor'):
 | 
				
			||||||
            self.monitor()
 | 
					            self.monitor()
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -82,17 +82,14 @@ class Monitor:
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        Last sound log of monitored station that occurred on_air
 | 
					        Last sound log of monitored station that occurred on_air
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return self.get_last_log(type = Log.Type.on_air,
 | 
					        return self.get_last_log(type=Log.Type.on_air, sound__isnull=False)
 | 
				
			||||||
                                 sound__isnull = False)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def last_diff_start(self):
 | 
					    def last_diff_start(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Log of last triggered item (sound or diffusion)
 | 
					        Log of last triggered item (sound or diffusion)
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return self.get_last_log(type = Log.Type.start,
 | 
					        return self.get_last_log(type=Log.Type.start, diffusion__isnull=False)
 | 
				
			||||||
                                 diffusion__isnull = False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, station, **kwargs):
 | 
					    def __init__(self, station, **kwargs):
 | 
				
			||||||
        self.station = station
 | 
					        self.station = station
 | 
				
			||||||
@ -120,12 +117,11 @@ class Monitor:
 | 
				
			|||||||
        self.sync_playlists()
 | 
					        self.sync_playlists()
 | 
				
			||||||
        self.handle()
 | 
					        self.handle()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def log(self, date = None, **kwargs):
 | 
					    def log(self, date=None, **kwargs):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Create a log using **kwargs, and print info
 | 
					        Create a log using **kwargs, and print info
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        log = Log(station = self.station, date = date or tz.now(),
 | 
					        log = Log(station=self.station, date=date or tz.now(), **kwargs)
 | 
				
			||||||
                  **kwargs)
 | 
					 | 
				
			||||||
        log.save()
 | 
					        log.save()
 | 
				
			||||||
        log.print()
 | 
					        log.print()
 | 
				
			||||||
        return log
 | 
					        return log
 | 
				
			||||||
@ -142,14 +138,14 @@ class Monitor:
 | 
				
			|||||||
        air_times = (air_time - delta, air_time + delta)
 | 
					        air_times = (air_time - delta, air_time + delta)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        log = self.log_qs.on_air().filter(
 | 
					        log = self.log_qs.on_air().filter(
 | 
				
			||||||
            source = source.id, sound__path = sound_path,
 | 
					            source=source.id, sound__path=sound_path,
 | 
				
			||||||
            date__range = air_times,
 | 
					            date__range=air_times,
 | 
				
			||||||
        ).last()
 | 
					        ).last()
 | 
				
			||||||
        if log:
 | 
					        if log:
 | 
				
			||||||
            return log
 | 
					            return log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # get sound
 | 
					        # get sound
 | 
				
			||||||
        sound = Sound.objects.filter(path = sound_path) \
 | 
					        sound = Sound.objects.filter(path=sound_path) \
 | 
				
			||||||
                     .select_related('diffusion').first()
 | 
					                     .select_related('diffusion').first()
 | 
				
			||||||
        diff = None
 | 
					        diff = None
 | 
				
			||||||
        if sound and sound.diffusion:
 | 
					        if sound and sound.diffusion:
 | 
				
			||||||
@ -157,20 +153,16 @@ class Monitor:
 | 
				
			|||||||
            # check for reruns
 | 
					            # check for reruns
 | 
				
			||||||
            if not diff.is_date_in_range(air_time) and not diff.initial:
 | 
					            if not diff.is_date_in_range(air_time) and not diff.initial:
 | 
				
			||||||
                diff = Diffusion.objects.at(air_time) \
 | 
					                diff = Diffusion.objects.at(air_time) \
 | 
				
			||||||
                                .filter(initial = diff).first()
 | 
					                                .filter(initial=diff).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # log sound on air
 | 
					        # log sound on air
 | 
				
			||||||
        return self.log(
 | 
					        return self.log(
 | 
				
			||||||
            type = Log.Type.on_air,
 | 
					            type=Log.Type.on_air, source=source.id, date=source.on_air,
 | 
				
			||||||
            source = source.id,
 | 
					            sound=sound, diffusion=diff,
 | 
				
			||||||
            date = source.on_air,
 | 
					 | 
				
			||||||
            sound = sound,
 | 
					 | 
				
			||||||
            diffusion = diff,
 | 
					 | 
				
			||||||
            # if sound is removed, we keep sound path info
 | 
					            # if sound is removed, we keep sound path info
 | 
				
			||||||
            comment = sound_path,
 | 
					            comment=sound_path,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def trace_tracks(self, log):
 | 
					    def trace_tracks(self, log):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Log tracks for the given sound log (for streamed programs only).
 | 
					        Log tracks for the given sound log (for streamed programs only).
 | 
				
			||||||
@ -178,23 +170,21 @@ class Monitor:
 | 
				
			|||||||
        if log.diffusion:
 | 
					        if log.diffusion:
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tracks = Track.objects.related(object = log.sound) \
 | 
					        tracks = Track.objects.filter(sound=log.sound, timestamp_isnull=False)
 | 
				
			||||||
                              .filter(in_seconds = True)
 | 
					 | 
				
			||||||
        if not tracks.exists():
 | 
					        if not tracks.exists():
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tracks = tracks.exclude(log__station = self.station,
 | 
					        tracks = tracks.exclude(log__station=self.station, log__pk__gt=log.pk)
 | 
				
			||||||
                                log__pk__gt = log.pk)
 | 
					 | 
				
			||||||
        now = tz.now()
 | 
					        now = tz.now()
 | 
				
			||||||
        for track in tracks:
 | 
					        for track in tracks:
 | 
				
			||||||
            pos = log.date + tz.timedelta(seconds = track.position)
 | 
					            pos = log.date + tz.timedelta(seconds=track.position)
 | 
				
			||||||
            if pos > now:
 | 
					            if pos > now:
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
            # log track on air
 | 
					            # log track on air
 | 
				
			||||||
            self.log(
 | 
					            self.log(
 | 
				
			||||||
                type = Log.Type.on_air, source = log.source,
 | 
					                type=Log.Type.on_air, source=log.source,
 | 
				
			||||||
                date = pos, track = track,
 | 
					                date=pos, track=track,
 | 
				
			||||||
                comment = track,
 | 
					                comment=track,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def sync_playlists(self):
 | 
					    def sync_playlists(self):
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										748
									
								
								aircox/models.py
									
									
									
									
									
								
							
							
						
						
									
										748
									
								
								aircox/models.py
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										6
									
								
								aircox/templates/admin/aircox/playlist_inline.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								aircox/templates/admin/aircox/playlist_inline.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					{% load static i18n %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% with inline_admin_formset.formset.instance as playlist %}
 | 
				
			||||||
 | 
					{% include "adminsortable2/tabular.html" %}
 | 
				
			||||||
 | 
					{% endwith %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -78,8 +78,7 @@ A stream is a source that:
 | 
				
			|||||||
- is interactive
 | 
					- is interactive
 | 
				
			||||||
{% endcomment %}
 | 
					{% endcomment %}
 | 
				
			||||||
def stream (id, file) =
 | 
					def stream (id, file) =
 | 
				
			||||||
    s = playlist(id = '#{id}_playlist', mode = "random", reload_mode='watch',
 | 
					    s = playlist(id = '#{id}_playlist', mode = "random", reload_mode='watch', file)
 | 
				
			||||||
                 file)
 | 
					 | 
				
			||||||
    interactive_source(id, s)
 | 
					    interactive_source(id, s)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,7 @@ class Stations:
 | 
				
			|||||||
        if self.fetch_timeout and self.fetch_timeout > tz.now():
 | 
					        if self.fetch_timeout and self.fetch_timeout > tz.now():
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.fetch_timeout = tz.now() + tz.timedelta(seconds = 5)
 | 
					        self.fetch_timeout = tz.now() + tz.timedelta(seconds=5)
 | 
				
			||||||
        for station in self.stations:
 | 
					        for station in self.stations:
 | 
				
			||||||
            station.streamer.fetch()
 | 
					            station.streamer.fetch()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -39,62 +39,54 @@ def on_air(request):
 | 
				
			|||||||
    except:
 | 
					    except:
 | 
				
			||||||
        cms = None
 | 
					        cms = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    station = request.GET.get('station');
 | 
					    station = request.GET.get('station')
 | 
				
			||||||
    if station:
 | 
					    if station:
 | 
				
			||||||
        # FIXME: by name???
 | 
					        # FIXME: by name???
 | 
				
			||||||
        station = stations.stations.filter(name = station)
 | 
					        station = stations.stations.filter(name=station)
 | 
				
			||||||
        if not station.count():
 | 
					        if not station.count():
 | 
				
			||||||
            return HttpResponse('{}')
 | 
					            return HttpResponse('{}')
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        station = stations.stations
 | 
					        station = stations.stations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    station = station.first()
 | 
					    station = station.first()
 | 
				
			||||||
    on_air = station.on_air(count = 10).select_related('track','diffusion')
 | 
					    on_air = station.on_air(count=10).select_related('track', 'diffusion')
 | 
				
			||||||
    if not on_air.count():
 | 
					    if not on_air.count():
 | 
				
			||||||
        return HttpResponse('')
 | 
					        return HttpResponse('')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    last = on_air.first()
 | 
					    last = on_air.first()
 | 
				
			||||||
    if last.track:
 | 
					    if last.track:
 | 
				
			||||||
        last = {
 | 
					        last = {'date': last.date, 'type': 'track',
 | 
				
			||||||
            'type': 'track',
 | 
					                'artist': last.track.artist, 'title': last.track.title}
 | 
				
			||||||
            'artist': last.related.artist,
 | 
					 | 
				
			||||||
            'title': last.related.title,
 | 
					 | 
				
			||||||
            'date': last.date,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            diff = last.diffusion
 | 
					            diff = last.diffusion
 | 
				
			||||||
            publication = None
 | 
					            publication = None
 | 
				
			||||||
 | 
					            # FIXME CMS
 | 
				
			||||||
            if cms:
 | 
					            if cms:
 | 
				
			||||||
                publication = \
 | 
					                publication = \
 | 
				
			||||||
                    cms.DiffusionPage.objects.filter(
 | 
					                    cms.DiffusionPage.objects.filter(
 | 
				
			||||||
                        diffusion = diff.initial or diff).first() or \
 | 
					                        diffusion=diff.initial or diff).first() or \
 | 
				
			||||||
                    cms.ProgramPage.objects.filter(
 | 
					                    cms.ProgramPage.objects.filter(
 | 
				
			||||||
                        program = last.program).first()
 | 
					                        program=last.program).first()
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					        last = {'date': diff.start, 'type': 'diffusion',
 | 
				
			||||||
        last = {
 | 
					                'title': diff.program.name,
 | 
				
			||||||
            'type': 'diffusion',
 | 
					                'url': publication.specific.url if publication else None}
 | 
				
			||||||
            'title': diff.program.name,
 | 
					 | 
				
			||||||
            'date': diff.start,
 | 
					 | 
				
			||||||
            'url': publication.specific.url if publication else None,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    last['date'] = str(last['date'])
 | 
					    last['date'] = str(last['date'])
 | 
				
			||||||
    return HttpResponse(json.dumps(last))
 | 
					    return HttpResponse(json.dumps(last))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO:
 | 
					# TODO:
 | 
				
			||||||
#   - login url
 | 
					#   - login url
 | 
				
			||||||
class Monitor(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
					class Monitor(View, TemplateResponseMixin, LoginRequiredMixin):
 | 
				
			||||||
    template_name = 'aircox/controllers/monitor.html'
 | 
					    template_name = 'aircox/controllers/monitor.html'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_context_data(self, **kwargs):
 | 
					    def get_context_data(self, **kwargs):
 | 
				
			||||||
        stations.fetch()
 | 
					        stations.fetch()
 | 
				
			||||||
        return { 'stations': stations.stations }
 | 
					        return {'stations': stations.stations}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get(self, request = None, **kwargs):
 | 
					    def get(self, request=None, **kwargs):
 | 
				
			||||||
        if not request.user.is_active:
 | 
					        if not request.user.is_active:
 | 
				
			||||||
            return Http404()
 | 
					            return Http404()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -102,7 +94,7 @@ class Monitor(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
				
			|||||||
        context = self.get_context_data(**kwargs)
 | 
					        context = self.get_context_data(**kwargs)
 | 
				
			||||||
        return render(request, self.template_name, context)
 | 
					        return render(request, self.template_name, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post(self, request = None, **kwargs):
 | 
					    def post(self, request=None, **kwargs):
 | 
				
			||||||
        if not request.user.is_active:
 | 
					        if not request.user.is_active:
 | 
				
			||||||
            return Http404()
 | 
					            return Http404()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -113,15 +105,15 @@ class Monitor(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
				
			|||||||
        controller = POST.get('controller')
 | 
					        controller = POST.get('controller')
 | 
				
			||||||
        action = POST.get('action')
 | 
					        action = POST.get('action')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        station = stations.stations.filter(name = POST.get('station')) \
 | 
					        station = stations.stations.filter(name=POST.get('station')) \
 | 
				
			||||||
                                   .first()
 | 
					                                   .first()
 | 
				
			||||||
        if not station:
 | 
					        if not station:
 | 
				
			||||||
            return Http404()
 | 
					            return Http404()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        source = None
 | 
					        source = None
 | 
				
			||||||
        if 'source' in POST:
 | 
					        if 'source' in POST:
 | 
				
			||||||
            source = [ s for s in station.sources
 | 
					            source = [s for s in station.sources
 | 
				
			||||||
                        if s.name == POST['source'] ]
 | 
					                      if s.name == POST['source']]
 | 
				
			||||||
            source = source[0]
 | 
					            source = source[0]
 | 
				
			||||||
            if not source:
 | 
					            if not source:
 | 
				
			||||||
                return Http404
 | 
					                return Http404
 | 
				
			||||||
@ -141,11 +133,11 @@ class Monitor(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
				
			|||||||
        source.restart()
 | 
					        source.restart()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StatisticsView(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
					class StatisticsView(View, TemplateResponseMixin, LoginRequiredMixin):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    View for statistics.
 | 
					    View for statistics.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    # we cannot manipulate queryset, since we have to be able to read from archives
 | 
					    # we cannot manipulate queryset: we have to be able to read from archives
 | 
				
			||||||
    template_name = 'aircox/controllers/stats.html'
 | 
					    template_name = 'aircox/controllers/stats.html'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Item:
 | 
					    class Item:
 | 
				
			||||||
@ -179,7 +171,7 @@ class StatisticsView(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
				
			|||||||
            self.__dict__.update(kwargs)
 | 
					            self.__dict__.update(kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Note: one row contains a column for diffusions and one for streams
 | 
					        # Note: one row contains a column for diffusions and one for streams
 | 
				
			||||||
        #def append(self, log):
 | 
					        # def append(self, log):
 | 
				
			||||||
        #    if log.col == 0:
 | 
					        #    if log.col == 0:
 | 
				
			||||||
        #        self.rows.append((log, []))
 | 
					        #        self.rows.append((log, []))
 | 
				
			||||||
        #        return
 | 
					        #        return
 | 
				
			||||||
@ -194,13 +186,12 @@ class StatisticsView(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
				
			|||||||
        #    # all other cases: new row
 | 
					        #    # all other cases: new row
 | 
				
			||||||
        #    self.rows.append((None, [log]))
 | 
					        #    self.rows.append((None, [log]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_stats(self, station, date):
 | 
					    def get_stats(self, station, date):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Return statistics for the given station and date.
 | 
					        Return statistics for the given station and date.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        stats = self.Stats(station = station, date = date,
 | 
					        stats = self.Stats(station=station, date=date,
 | 
				
			||||||
                           items = [], tags = {})
 | 
					                           items=[], tags={})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        qs = Log.objects.station(station).on_air() \
 | 
					        qs = Log.objects.station(station).on_air() \
 | 
				
			||||||
                .prefetch_related('diffusion', 'sound', 'track', 'track__tags')
 | 
					                .prefetch_related('diffusion', 'sound', 'track', 'track__tags')
 | 
				
			||||||
@ -209,37 +200,26 @@ class StatisticsView(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        sound_log = None
 | 
					        sound_log = None
 | 
				
			||||||
        for log in qs:
 | 
					        for log in qs:
 | 
				
			||||||
            rel = None
 | 
					            rel, item = None, None
 | 
				
			||||||
            item = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if log.diffusion:
 | 
					            if log.diffusion:
 | 
				
			||||||
                rel = log.diffusion
 | 
					                rel, item = log.diffusion, self.Item(
 | 
				
			||||||
                item = self.Item(
 | 
					                    name=rel.program.name, type=_('Diffusion'), col=0,
 | 
				
			||||||
                    name = rel.program.name,
 | 
					                    tracks=models.Track.objects.filter(diffusion=log.diffusion)
 | 
				
			||||||
                    type = _('Diffusion'),
 | 
					                                       .prefetch_related('tags'),
 | 
				
			||||||
                    col = 0,
 | 
					 | 
				
			||||||
                    tracks = models.Track.objects.related(object = rel)
 | 
					 | 
				
			||||||
                                         .prefetch_related('tags'),
 | 
					 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                sound_log = None
 | 
					                sound_log = None
 | 
				
			||||||
            elif log.sound:
 | 
					            elif log.sound:
 | 
				
			||||||
                rel = log.sound
 | 
					                rel, item = log.sound, self.Item(
 | 
				
			||||||
                item = self.Item(
 | 
					                    name=rel.program.name + ': ' + os.path.basename(rel.path),
 | 
				
			||||||
                    name = rel.program.name + ': ' + os.path.basename(rel.path),
 | 
					                    type=_('Stream'), col=1, tracks=[],
 | 
				
			||||||
                    type = _('Stream'),
 | 
					 | 
				
			||||||
                    col = 1,
 | 
					 | 
				
			||||||
                    tracks = [],
 | 
					 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                sound_log = item
 | 
					                sound_log = item
 | 
				
			||||||
            elif log.track:
 | 
					            elif log.track:
 | 
				
			||||||
                # append to last sound log
 | 
					                # append to last sound log
 | 
				
			||||||
                if not sound_log:
 | 
					                if not sound_log:
 | 
				
			||||||
                    # TODO: create item ? should never happen
 | 
					 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
 | 
					 | 
				
			||||||
                sound_log.tracks.append(log.track)
 | 
					                sound_log.tracks.append(log.track)
 | 
				
			||||||
                sound_log.end = log.end
 | 
					                sound_log.end = log.end
 | 
				
			||||||
                sound_log
 | 
					 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            item.date = log.date
 | 
					            item.date = log.date
 | 
				
			||||||
@ -247,7 +227,6 @@ class StatisticsView(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
				
			|||||||
            item.related = rel
 | 
					            item.related = rel
 | 
				
			||||||
            # stats.append(item)
 | 
					            # stats.append(item)
 | 
				
			||||||
            stats.items.append(item)
 | 
					            stats.items.append(item)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        return stats
 | 
					        return stats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_context_data(self, **kwargs):
 | 
					    def get_context_data(self, **kwargs):
 | 
				
			||||||
@ -270,7 +249,7 @@ class StatisticsView(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return context
 | 
					        return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get(self, request = None, **kwargs):
 | 
					    def get(self, request=None, **kwargs):
 | 
				
			||||||
        if not request.user.is_active:
 | 
					        if not request.user.is_active:
 | 
				
			||||||
            return Http404()
 | 
					            return Http404()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -279,4 +258,3 @@ class StatisticsView(View,TemplateResponseMixin,LoginRequiredMixin):
 | 
				
			|||||||
        return render(request, self.template_name, context)
 | 
					        return render(request, self.template_name, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										0
									
								
								aircox_web/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								aircox_web/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										68
									
								
								aircox_web/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								aircox_web/admin.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					import copy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.utils.translation import ugettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from content_editor.admin import ContentEditor
 | 
				
			||||||
 | 
					from feincms3 import plugins
 | 
				
			||||||
 | 
					from feincms3.admin import TreeAdmin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aircox import models as aircox
 | 
				
			||||||
 | 
					from . import models
 | 
				
			||||||
 | 
					from aircox.admin.playlist import TracksInline
 | 
				
			||||||
 | 
					from aircox.admin.mixins import UnrelatedInlineMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(models.SiteSettings)
 | 
				
			||||||
 | 
					class SettingsAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PageDiffusionPlaylist(UnrelatedInlineMixin, TracksInline):
 | 
				
			||||||
 | 
					    parent_model = aircox.Diffusion
 | 
				
			||||||
 | 
					    fields = list(TracksInline.fields)
 | 
				
			||||||
 | 
					    fields.remove('timestamp')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_parent(self, view_obj):
 | 
				
			||||||
 | 
					        return view_obj and view_obj.diffusion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def save_parent(self, parent, view_obj):
 | 
				
			||||||
 | 
					        parent.save()
 | 
				
			||||||
 | 
					        view_obj.diffusion = parent
 | 
				
			||||||
 | 
					        view_obj.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@admin.register(models.Page)
 | 
				
			||||||
 | 
					class PageAdmin(ContentEditor, TreeAdmin):
 | 
				
			||||||
 | 
					    list_display = ["indented_title", "move_column", "is_active"]
 | 
				
			||||||
 | 
					    prepopulated_fields = {"slug": ("title",)}
 | 
				
			||||||
 | 
					    # readonly_fields = ('diffusion',)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fieldsets = (
 | 
				
			||||||
 | 
					        (_('Main'), {
 | 
				
			||||||
 | 
					            'fields': ['title', 'slug', 'by_program', 'summary'],
 | 
				
			||||||
 | 
					            'classes': ('tabbed', 'uncollapse')
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        (_('Settings'), {
 | 
				
			||||||
 | 
					            'fields': ['show_author', 'featured', 'allow_comments',
 | 
				
			||||||
 | 
					                       'status', 'static_path', 'path'],
 | 
				
			||||||
 | 
					            'classes': ('tabbed',)
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        (_('Infos'), {
 | 
				
			||||||
 | 
					            'fields': ['diffusion'],
 | 
				
			||||||
 | 
					            'classes': ('tabbed',)
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    inlines = [
 | 
				
			||||||
 | 
					        plugins.richtext.RichTextInline.create(models.RichText),
 | 
				
			||||||
 | 
					        plugins.image.ImageInline.create(models.Image),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_inline_instances(self, request, obj=None):
 | 
				
			||||||
 | 
					        inlines = super().get_inline_instances(request, obj)
 | 
				
			||||||
 | 
					        if obj and obj.diffusion:
 | 
				
			||||||
 | 
					            inlines.insert(0, PageDiffusionPlaylist(self.model, self.admin_site))
 | 
				
			||||||
 | 
					        return inlines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								aircox_web/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								aircox_web/apps.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					from django.apps import AppConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AircoxWebConfig(AppConfig):
 | 
				
			||||||
 | 
					    name = 'aircox_web'
 | 
				
			||||||
							
								
								
									
										1
									
								
								aircox_web/assets/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								aircox_web/assets/index.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					import './js';
 | 
				
			||||||
							
								
								
									
										12
									
								
								aircox_web/assets/js/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								aircox_web/assets/js/index.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import Buefy from 'buefy';
 | 
				
			||||||
 | 
					import 'buefy/dist/buefy.css';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Vue.use(Buefy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var app = new Vue({
 | 
				
			||||||
 | 
					  el: '#app',
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										117
									
								
								aircox_web/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								aircox_web/models.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					from django.db import models
 | 
				
			||||||
 | 
					from django.utils.translation import ugettext_lazy as _
 | 
				
			||||||
 | 
					from django.contrib.auth import models as auth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from content_editor.models import Region, create_plugin_base
 | 
				
			||||||
 | 
					from feincms3 import plugins
 | 
				
			||||||
 | 
					from feincms3.pages import AbstractPage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from model_utils.models import TimeStampedModel, StatusModel
 | 
				
			||||||
 | 
					from model_utils import Choices
 | 
				
			||||||
 | 
					from filer.fields.image import FilerImageField
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aircox import models as aircox
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SiteSettings(models.Model):
 | 
				
			||||||
 | 
					    station = models.ForeignKey(
 | 
				
			||||||
 | 
					        aircox.Station, on_delete=models.SET_NULL, null=True,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # main settings
 | 
				
			||||||
 | 
					    title = models.CharField(
 | 
				
			||||||
 | 
					        _('Title'), max_length=32,
 | 
				
			||||||
 | 
					        help_text=_('Website title used at various places'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    logo = FilerImageField(
 | 
				
			||||||
 | 
					        on_delete=models.SET_NULL, null=True, blank=True,
 | 
				
			||||||
 | 
					        verbose_name=_('Logo'),
 | 
				
			||||||
 | 
					        related_name='+',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    favicon = FilerImageField(
 | 
				
			||||||
 | 
					        on_delete=models.SET_NULL, null=True, blank=True,
 | 
				
			||||||
 | 
					        verbose_name=_('Favicon'),
 | 
				
			||||||
 | 
					        related_name='+',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # meta descriptors
 | 
				
			||||||
 | 
					    description = models.CharField(
 | 
				
			||||||
 | 
					        _('Description'), max_length=128,
 | 
				
			||||||
 | 
					        blank=True, null=True,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    tags = models.CharField(
 | 
				
			||||||
 | 
					        _('Tags'), max_length=128,
 | 
				
			||||||
 | 
					        blank=True, null=True,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Page(AbstractPage, TimeStampedModel, StatusModel):
 | 
				
			||||||
 | 
					    STATUS = Choices('draft', 'published')
 | 
				
			||||||
 | 
					    regions = [
 | 
				
			||||||
 | 
					        Region(key="main", title=_("Content")),
 | 
				
			||||||
 | 
					        Region(key="sidebar", title=_("Sidebar")),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # metadata
 | 
				
			||||||
 | 
					    by = models.ForeignKey(
 | 
				
			||||||
 | 
					        auth.User, models.SET_NULL, blank=True, null=True,
 | 
				
			||||||
 | 
					        verbose_name=_('Author'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    by_program = models.ForeignKey(
 | 
				
			||||||
 | 
					        aircox.Program, models.SET_NULL, blank=True, null=True,
 | 
				
			||||||
 | 
					        related_name='authored_pages',
 | 
				
			||||||
 | 
					        limit_choices_to={'schedule__isnull': False},
 | 
				
			||||||
 | 
					        verbose_name=_('Show program as author'),
 | 
				
			||||||
 | 
					        help_text=_("If nothing is selected, display user's name"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # options
 | 
				
			||||||
 | 
					    show_author = models.BooleanField(
 | 
				
			||||||
 | 
					        _('Show author'), default=True,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    featured = models.BooleanField(
 | 
				
			||||||
 | 
					        _('featured'), default=False,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    allow_comments = models.BooleanField(
 | 
				
			||||||
 | 
					        _('allow comments'), default=True,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # content
 | 
				
			||||||
 | 
					    title = models.CharField(
 | 
				
			||||||
 | 
					        _('title'), max_length=64,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    summary = models.TextField(
 | 
				
			||||||
 | 
					        _('Summary'),
 | 
				
			||||||
 | 
					        max_length=128, blank=True, null=True,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    cover = FilerImageField(
 | 
				
			||||||
 | 
					        on_delete=models.SET_NULL, null=True, blank=True,
 | 
				
			||||||
 | 
					        verbose_name=_('Cover'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    diffusion = models.OneToOneField(
 | 
				
			||||||
 | 
					        aircox.Diffusion, models.CASCADE,
 | 
				
			||||||
 | 
					        blank=True, null=True,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PagePlugin = create_plugin_base(Page)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RichText(plugins.richtext.RichText, PagePlugin):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Image(plugins.image.Image, PagePlugin):
 | 
				
			||||||
 | 
					    caption = models.CharField(_("caption"), max_length=200, blank=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProgramPage(Page):
 | 
				
			||||||
 | 
					    program = models.OneToOneField(
 | 
				
			||||||
 | 
					        aircox.Program, models.CASCADE,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										26
									
								
								aircox_web/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								aircox_web/package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "aircox-web-assets",
 | 
				
			||||||
 | 
					  "version": "0.0.0",
 | 
				
			||||||
 | 
					  "description": "Assets for Aircox Web",
 | 
				
			||||||
 | 
					  "main": "index.js",
 | 
				
			||||||
 | 
					  "author": "bkfox",
 | 
				
			||||||
 | 
					  "license": "AGPL",
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@fortawesome/fontawesome-free": "^5.8.2",
 | 
				
			||||||
 | 
					    "mini-css-extract-plugin": "^0.5.0",
 | 
				
			||||||
 | 
					    "css-loader": "^2.1.1",
 | 
				
			||||||
 | 
					    "file-loader": "^3.0.1",
 | 
				
			||||||
 | 
					    "ttf-loader": "^1.0.2",
 | 
				
			||||||
 | 
					    "vue-loader": "^15.7.0",
 | 
				
			||||||
 | 
					    "vue-style-loader": "^4.1.2",
 | 
				
			||||||
 | 
					    "webpack": "^4.32.2",
 | 
				
			||||||
 | 
					    "webpack-bundle-analyzer": "^3.3.2",
 | 
				
			||||||
 | 
					    "webpack-bundle-tracker": "^0.4.2-beta",
 | 
				
			||||||
 | 
					    "webpack-cli": "^3.3.2"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "bootstrap": "^4.3.1",
 | 
				
			||||||
 | 
					    "buefy": "^0.7.8",
 | 
				
			||||||
 | 
					    "vue": "^2.6.10"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								aircox_web/renderer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								aircox_web/renderer.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					from django.utils.html import format_html, mark_safe
 | 
				
			||||||
 | 
					from feincms3.renderer import TemplatePluginRenderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .models import Page, RichText, Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					renderer = TemplatePluginRenderer()
 | 
				
			||||||
 | 
					renderer.register_string_renderer(
 | 
				
			||||||
 | 
					    RichText,
 | 
				
			||||||
 | 
					    lambda plugin: mark_safe(plugin.text),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					renderer.register_string_renderer(
 | 
				
			||||||
 | 
					    Image,
 | 
				
			||||||
 | 
					    lambda plugin: format_html(
 | 
				
			||||||
 | 
					        '<figure><img src="{}" alt=""/><figcaption>{}</figcaption></figure>',
 | 
				
			||||||
 | 
					        plugin.image.url,
 | 
				
			||||||
 | 
					        plugin.caption,
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										34
									
								
								aircox_web/templates/aircox_web/base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								aircox_web/templates/aircox_web/base.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					{% load static thumbnail %}
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					    <head>
 | 
				
			||||||
 | 
					        <meta charset="utf-8">
 | 
				
			||||||
 | 
					        <meta name="application-name" content="aircox">
 | 
				
			||||||
 | 
					        <meta name="description" content="{{ site_settings.description }}">
 | 
				
			||||||
 | 
					        <meta name="keywords" content="{{ site_settings.tags }}">
 | 
				
			||||||
 | 
					        <link rel="icon" href="{% thumbnail site_settings.favicon 32x32 crop %}" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {% block assets %}
 | 
				
			||||||
 | 
					        <link rel="stylesheet" type="text/css" href="{% static "aircox_web/assets/main.css" %}"/>
 | 
				
			||||||
 | 
					        <link rel="stylesheet" type="text/css" href="{% static "aircox_web/assets/vendor.css" %}"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <script src="{% static "aircox_web/assets/main.js" %}"></script>
 | 
				
			||||||
 | 
					        <script src="{% static "aircox_web/assets/vendor.js" %}"></script>
 | 
				
			||||||
 | 
					        {% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <title>{% block title %}{{ site_settings.title }}{% endblock %}</title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {% block extra_head %}{% endblock %}
 | 
				
			||||||
 | 
					    </head>
 | 
				
			||||||
 | 
					    <body id="app">
 | 
				
			||||||
 | 
					        <nav class="navbar" role="navigation" aria-label="main navigation">
 | 
				
			||||||
 | 
					        </nav>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <main>
 | 
				
			||||||
 | 
					            {% block main %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {% endblock main %}
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					    </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								aircox_web/templates/aircox_web/page.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								aircox_web/templates/aircox_web/page.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					{% extends "aircox_web/base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}{{ page.title }} -- {{ block.super }}{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block main %}
 | 
				
			||||||
 | 
					<h1 class="title">{{ page.title }}</h1>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										3
									
								
								aircox_web/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								aircox_web/tests.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					from django.test import TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create your tests here.
 | 
				
			||||||
							
								
								
									
										9
									
								
								aircox_web/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								aircox_web/urls.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					from django.conf.urls import url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from . import views
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					urlpatterns = [
 | 
				
			||||||
 | 
					    url(r"^(?P<path>[-\w/]+)/$", views.page_detail, name="page"),
 | 
				
			||||||
 | 
					    url(r"^$", views.page_detail, name="root"),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								aircox_web/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								aircox_web/views.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					from django.shortcuts import get_object_or_404, render
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from feincms3.regions import Regions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .models import SiteSettings, Page
 | 
				
			||||||
 | 
					from .renderer import renderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def page_detail(request, path=None):
 | 
				
			||||||
 | 
					    page = get_object_or_404(
 | 
				
			||||||
 | 
					        # TODO: published
 | 
				
			||||||
 | 
					        Page.objects.all(),
 | 
				
			||||||
 | 
					        path="/{}/".format(path) if path else "/",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    return render(request, "aircox_web/page.html", {
 | 
				
			||||||
 | 
					        'site_settings': SiteSettings.objects.all().first(),
 | 
				
			||||||
 | 
					        "page": page,
 | 
				
			||||||
 | 
					        "regions": Regions.from_item(
 | 
				
			||||||
 | 
					            page, renderer=renderer, timeout=60
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										87
									
								
								aircox_web/webpack.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								aircox_web/webpack.config.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					const path = require('path');
 | 
				
			||||||
 | 
					const webpack = require('webpack');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MiniCssExtractPlugin = require("mini-css-extract-plugin");
 | 
				
			||||||
 | 
					// const { createLodashAliases } = require('lodash-loader');
 | 
				
			||||||
 | 
					const { VueLoaderPlugin } = require('vue-loader');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = (env, argv) => Object({
 | 
				
			||||||
 | 
					    context: __dirname,
 | 
				
			||||||
 | 
					    entry: './assets/index',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    output: {
 | 
				
			||||||
 | 
					        path: path.resolve('static/aircox_web/assets'),
 | 
				
			||||||
 | 
					        filename: '[name].js',
 | 
				
			||||||
 | 
					        chunkFilename: '[name].js',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    optimization: {
 | 
				
			||||||
 | 
					        usedExports: true,
 | 
				
			||||||
 | 
					        concatenateModules: argv.mode == 'production' ? true : false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        splitChunks: {
 | 
				
			||||||
 | 
					            cacheGroups: {
 | 
				
			||||||
 | 
					                vendor: {
 | 
				
			||||||
 | 
					                    name: 'vendor',
 | 
				
			||||||
 | 
					                    chunks: 'initial',
 | 
				
			||||||
 | 
					                    enforce: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    test: /[\\/]node_modules[\\/]/,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    plugins: [
 | 
				
			||||||
 | 
					        new MiniCssExtractPlugin({
 | 
				
			||||||
 | 
					            filename: "[name].css",
 | 
				
			||||||
 | 
					            chunkFilename: "[id].css"
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        new VueLoaderPlugin(),
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    module: {
 | 
				
			||||||
 | 
					        rules: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                test: /\/node_modules\//,
 | 
				
			||||||
 | 
					                sideEffects: false
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                test: /\.css$/,
 | 
				
			||||||
 | 
					                use: [ { loader: MiniCssExtractPlugin.loader },
 | 
				
			||||||
 | 
					                       'css-loader' ]
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // TODO: remove ttf eot svg
 | 
				
			||||||
 | 
					                test: /\.(ttf|eot|svg|woff2?)$/,
 | 
				
			||||||
 | 
					                use: [{
 | 
				
			||||||
 | 
					                    loader: 'file-loader',
 | 
				
			||||||
 | 
					                    options: {
 | 
				
			||||||
 | 
					                        name: '[name].[ext]',
 | 
				
			||||||
 | 
					                        outputPath: 'fonts/',
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            { test: /\.vue$/, use: 'vue-loader' },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resolve: {
 | 
				
			||||||
 | 
					        alias: {
 | 
				
			||||||
 | 
					            js: path.resolve(__dirname, 'assets/js'),
 | 
				
			||||||
 | 
					            vue: path.resolve(__dirname, 'assets/vue'),
 | 
				
			||||||
 | 
					            css: path.resolve(__dirname, 'assets/css'),
 | 
				
			||||||
 | 
					            vue: 'vue/dist/vue.esm.browser.js',
 | 
				
			||||||
 | 
					            // buefy: 'buefy/dist/buefy.js',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        modules: [
 | 
				
			||||||
 | 
					            'assets/css',
 | 
				
			||||||
 | 
					            'assets/js',
 | 
				
			||||||
 | 
					            'assets/vue',
 | 
				
			||||||
 | 
					            './node_modules',
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        extensions: ['.js', '.vue', '.css', '.styl', '.ttf']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -152,7 +152,6 @@ MIDDLEWARE = (
 | 
				
			|||||||
    'django.middleware.common.CommonMiddleware',
 | 
					    'django.middleware.common.CommonMiddleware',
 | 
				
			||||||
    'django.middleware.csrf.CsrfViewMiddleware',
 | 
					    'django.middleware.csrf.CsrfViewMiddleware',
 | 
				
			||||||
    'django.contrib.auth.middleware.AuthenticationMiddleware',
 | 
					    'django.contrib.auth.middleware.AuthenticationMiddleware',
 | 
				
			||||||
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
 | 
					 | 
				
			||||||
    'django.contrib.messages.middleware.MessageMiddleware',
 | 
					    'django.contrib.messages.middleware.MessageMiddleware',
 | 
				
			||||||
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
 | 
					    'django.middleware.clickjacking.XFrameOptionsMiddleware',
 | 
				
			||||||
    'django.middleware.security.SecurityMiddleware',
 | 
					    'django.middleware.security.SecurityMiddleware',
 | 
				
			||||||
 | 
				
			|||||||
@ -17,24 +17,18 @@ from django.conf import settings
 | 
				
			|||||||
from django.urls import include, path, re_path
 | 
					from django.urls import include, path, re_path
 | 
				
			||||||
from django.contrib import admin
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from wagtail.admin import urls as wagtailadmin_urls
 | 
					#from wagtail.admin import urls as wagtailadmin_urls
 | 
				
			||||||
from wagtail.documents import urls as wagtaildocs_urls
 | 
					#from wagtail.documents import urls as wagtaildocs_urls
 | 
				
			||||||
from wagtail.core import urls as wagtail_urls
 | 
					#from wagtail.core import urls as wagtail_urls
 | 
				
			||||||
from wagtail.images.views.serve import ServeView
 | 
					#from wagtail.images.views.serve import ServeView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import aircox.urls
 | 
					import aircox.urls
 | 
				
			||||||
 | 
					import aircox_web.urls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    urlpatterns = [
 | 
					    urlpatterns = [
 | 
				
			||||||
        path('jet/', include('jet.urls', 'jet')),
 | 
					 | 
				
			||||||
        path('admin/', admin.site.urls),
 | 
					        path('admin/', admin.site.urls),
 | 
				
			||||||
        path('aircox/', include(aircox.urls.urls)),
 | 
					        path('aircox/', include(aircox.urls.urls)),
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # cms
 | 
					 | 
				
			||||||
        path('cms/', include(wagtailadmin_urls)),
 | 
					 | 
				
			||||||
        path('documents/', include(wagtaildocs_urls)),
 | 
					 | 
				
			||||||
        re_path( r'^images/([^/]*)/(\d*)/([^/]*)/[^/]*$', ServeView.as_view(),
 | 
					 | 
				
			||||||
            name='wagtailimages_serve'),
 | 
					 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if settings.DEBUG:
 | 
					    if settings.DEBUG:
 | 
				
			||||||
@ -45,7 +39,8 @@ try:
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    urlpatterns.append(re_path(r'', include(wagtail_urls)))
 | 
					    urlpatterns.append(path('filer/', include('filer.urls')))
 | 
				
			||||||
 | 
					    urlpatterns += aircox_web.urls.urlpatterns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
except Exception as e:
 | 
					except Exception as e:
 | 
				
			||||||
    import traceback
 | 
					    import traceback
 | 
				
			||||||
 | 
				
			|||||||
@ -1,14 +1,20 @@
 | 
				
			|||||||
gunicorn>=19.6.0
 | 
					 | 
				
			||||||
Django>=2.2.0
 | 
					Django>=2.2.0
 | 
				
			||||||
wagtail>=2.4
 | 
					djangorestframework>=3.9.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dateutils>=0.6.6
 | 
				
			||||||
watchdog>=0.8.3
 | 
					watchdog>=0.8.3
 | 
				
			||||||
psutil>=5.0.1
 | 
					psutil>=5.0.1
 | 
				
			||||||
pyyaml>=3.12
 | 
					 | 
				
			||||||
dateutils>=0.6.6
 | 
					 | 
				
			||||||
bleach>=1.4.3
 | 
					 | 
				
			||||||
django-auth-ldap>=1.7.0
 | 
					 | 
				
			||||||
django-honeypot>=0.5.0
 | 
					 | 
				
			||||||
django-jet>=1.0.3
 | 
					 | 
				
			||||||
mutagen>=1.37
 | 
					 | 
				
			||||||
tzlocal>=1.4
 | 
					tzlocal>=1.4
 | 
				
			||||||
 | 
					mutagen>=1.37
 | 
				
			||||||
 | 
					pyyaml>=3.12
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					django-filer>=1.5.0
 | 
				
			||||||
 | 
					django-admin-sortable2>=0.7.2
 | 
				
			||||||
 | 
					django-content-editor>=1.4.2
 | 
				
			||||||
 | 
					feincms3[all]>=0.31.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bleach>=1.4.3
 | 
				
			||||||
 | 
					django-honeypot>=0.5.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gunicorn>=19.6.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user