work on website + page becomes concrete
This commit is contained in:
parent
595af5a69d
commit
c46f006379
|
@ -1,24 +1,10 @@
|
||||||
# Aircox Programs
|
# Aircox
|
||||||
|
Aircox application aims to provide basis of a radio management system.
|
||||||
This application defines all base models and basic control of them. We have:
|
|
||||||
* **Nameable**: generic class used in any class needing to be named. Includes some utility functions;
|
|
||||||
* **Station**: a station
|
|
||||||
* **Program**: the program itself;
|
|
||||||
* **Diffusion**: occurrence of a program planified in the timetable. For rerun, informations are bound to the initial diffusion;
|
|
||||||
* **Schedule**: describes diffusions frequencies for each program;
|
|
||||||
* **Track**: track informations in a playlist of a diffusion;
|
|
||||||
* **Sound**: information about a sound that can be used for podcast or rerun;
|
|
||||||
* **Log**: logs
|
|
||||||
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
A Station is basically an object that represent a radio station. On each station, we use the Program object, that is declined in two different types:
|
A Station contains programs that can be scheduled or streamed. A *Scheduled Program* is a regular show that has planified diffusions of its occurences (episodes). A *Streamed Program* is a program used to play randoms musics between the shows.
|
||||||
* **Scheduled**: the diffusion is based on a timetable and planified through one Schedule or more; Diffusion object represent the occurrence of these programs;
|
|
||||||
* **Streamed**: the diffusion is based on random playlist, used to fill gaps between the programs;
|
|
||||||
|
|
||||||
Each program has a directory in **AIRCOX_PROGRAMS_DIR**; For each, subdir:
|
Each program has a directory on the server where user puts its podcasts (in **AIRCOX_PROGRAM_DIR**). It contains the directories **archives** (complete show's podcasts) and **excerpts** (partial or whatever podcasts).
|
||||||
* **archives**: complete episode record, can be used for diffusions or as a podcast
|
|
||||||
* **excerpts**: excerpt of an episode, or other elements, can be used as a podcast
|
|
||||||
|
|
||||||
|
|
||||||
## manage.py's commands
|
## manage.py's commands
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -25,6 +25,8 @@ class PageAdmin(admin.ModelAdmin):
|
||||||
list_editable = ('status', 'category')
|
list_editable = ('status', 'category')
|
||||||
prepopulated_fields = {"slug": ("title",)}
|
prepopulated_fields = {"slug": ("title",)}
|
||||||
|
|
||||||
|
change_form_template = 'admin/aircox/page_change_form.html'
|
||||||
|
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
('', {
|
('', {
|
||||||
'fields': ['title', 'slug', 'category', 'cover', 'content'],
|
'fields': ['title', 'slug', 'category', 'cover', 'content'],
|
||||||
|
|
|
@ -78,18 +78,14 @@ class Streamer:
|
||||||
@property
|
@property
|
||||||
def inputs(self):
|
def inputs(self):
|
||||||
""" Return input ports of the station """
|
""" Return input ports of the station """
|
||||||
return self.station.port_set.filter(
|
return self.station.port_set.filter(direction=Port.DIRECTION_INPUT,
|
||||||
direction=Port.Direction.input,
|
active=True)
|
||||||
active=True
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def outputs(self):
|
def outputs(self):
|
||||||
""" Return output ports of the station """
|
""" Return output ports of the station """
|
||||||
return self.station.port_set.filter(
|
return self.station.port_set.filter(direction=Port.DIRECTION_OUTPUT,
|
||||||
direction=Port.Direction.output,
|
active=True)
|
||||||
active=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Sources and config ###############################################
|
# Sources and config ###############################################
|
||||||
def send(self, *args, **kwargs):
|
def send(self, *args, **kwargs):
|
||||||
|
|
|
@ -57,14 +57,14 @@ class Actions:
|
||||||
diffusion.save()
|
diffusion.save()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
qs = Diffusion.objects.filter(type=Diffusion.Type.unconfirmed,
|
qs = Diffusion.objects.filter(type=Diffusion.TYPE_UNCONFIRMED,
|
||||||
start__lt=self.date)
|
start__lt=self.date)
|
||||||
logger.info('[clean] %d diffusions will be removed', qs.count())
|
logger.info('[clean] %d diffusions will be removed', qs.count())
|
||||||
qs.delete()
|
qs.delete()
|
||||||
|
|
||||||
def check(self):
|
def check(self):
|
||||||
# TODO: redo
|
# TODO: redo
|
||||||
qs = Diffusion.objects.filter(type=Diffusion.Type.unconfirmed,
|
qs = Diffusion.objects.filter(type=Diffusion.TYPE_UNCONFIRMED,
|
||||||
start__gt=self.date)
|
start__gt=self.date)
|
||||||
items = []
|
items = []
|
||||||
for diffusion in qs:
|
for diffusion in qs:
|
||||||
|
|
|
@ -184,9 +184,9 @@ class MonitorHandler(PatternMatchingEventHandler):
|
||||||
"""
|
"""
|
||||||
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]
|
||||||
|
@ -213,7 +213,7 @@ class MonitorHandler(PatternMatchingEventHandler):
|
||||||
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
|
||||||
sound.save()
|
sound.save()
|
||||||
|
|
||||||
def on_moved(self, event):
|
def on_moved(self, event):
|
||||||
|
@ -259,11 +259,11 @@ class Command(BaseCommand):
|
||||||
logger.info('#%d %s', program.id, program.title)
|
logger.info('#%d %s', program.id, program.title)
|
||||||
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))
|
||||||
|
|
||||||
|
@ -317,7 +317,7 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
# get available sound files
|
# get available sound files
|
||||||
sounds = Sound.objects.filter(is_good_quality=False) \
|
sounds = Sound.objects.filter(is_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)
|
||||||
|
|
||||||
|
|
|
@ -152,7 +152,7 @@ class Monitor:
|
||||||
.now(air_time).first()
|
.now(air_time).first()
|
||||||
|
|
||||||
# log sound on air
|
# log sound on air
|
||||||
return self.log(type=Log.Type.on_air, date=source.air_time,
|
return self.log(type=Log.TYPE_ON_AIR, date=source.air_time,
|
||||||
source=source.id, sound=sound, diffusion=diff,
|
source=source.id, sound=sound, diffusion=diff,
|
||||||
comment=air_uri)
|
comment=air_uri)
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ class Monitor:
|
||||||
if pos > now:
|
if pos > now:
|
||||||
break
|
break
|
||||||
# log track on air
|
# log track on air
|
||||||
self.log(type=Log.Type.on_air, date=pos, source=log.source,
|
self.log(type=Log.TYPE_ON_AIR, date=pos, source=log.source,
|
||||||
track=track, comment=track)
|
track=track, comment=track)
|
||||||
|
|
||||||
def handle_diffusions(self):
|
def handle_diffusions(self):
|
||||||
|
@ -208,7 +208,7 @@ class Monitor:
|
||||||
#
|
#
|
||||||
now = tz.now()
|
now = tz.now()
|
||||||
diff = Diffusion.objects.station(self.station).on_air().now(now) \
|
diff = Diffusion.objects.station(self.station).on_air().now(now) \
|
||||||
.filter(episode__sound__type=Sound.Type.archive) \
|
.filter(episode__sound__type=Sound.TYPE_ARCHIVE) \
|
||||||
.first()
|
.first()
|
||||||
# Can't use delay: diffusion may start later than its assigned start.
|
# Can't use delay: diffusion may start later than its assigned start.
|
||||||
log = None if not diff else self.logs.start().filter(diffusion=diff)
|
log = None if not diff else self.logs.start().filter(diffusion=diff)
|
||||||
|
@ -228,13 +228,13 @@ class Monitor:
|
||||||
def start_diff(self, source, diff):
|
def start_diff(self, source, diff):
|
||||||
playlist = Sound.objects.episode(id=diff.episode_id).paths()
|
playlist = Sound.objects.episode(id=diff.episode_id).paths()
|
||||||
source.append(*playlist)
|
source.append(*playlist)
|
||||||
self.log(type=Log.Type.start, source=source.id, diffusion=diff,
|
self.log(type=Log.TYPE_START, source=source.id, diffusion=diff,
|
||||||
comment=str(diff))
|
comment=str(diff))
|
||||||
|
|
||||||
def cancel_diff(self, source, diff):
|
def cancel_diff(self, source, diff):
|
||||||
diff.type = Diffusion.Type.cancel
|
diff.type = Diffusion.TYPE_CANCEL
|
||||||
diff.save()
|
diff.save()
|
||||||
self.log(type=Log.Type.cancel, source=source.id, diffusion=diff,
|
self.log(type=Log.TYPE_CANCEL, source=source.id, diffusion=diff,
|
||||||
comment=str(diff))
|
comment=str(diff))
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,11 +1,17 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from .page import Page
|
from .page import Page, PageQuerySet
|
||||||
from .program import Program, InProgramQuerySet
|
from .program import Program, InProgramQuerySet
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleQuerySet(InProgramQuerySet, PageQuerySet):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Article(Page):
|
class Article(Page):
|
||||||
|
detail_url_name = 'article-detail'
|
||||||
|
|
||||||
program = models.ForeignKey(
|
program = models.ForeignKey(
|
||||||
Program, models.SET_NULL,
|
Program, models.SET_NULL,
|
||||||
verbose_name=_('program'), blank=True, null=True,
|
verbose_name=_('program'), blank=True, null=True,
|
||||||
|
@ -17,7 +23,7 @@ class Article(Page):
|
||||||
'instead of a blog article'),
|
'instead of a blog article'),
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = InProgramQuerySet.as_manager()
|
objects = ArticleQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Article')
|
verbose_name = _('Article')
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import datetime
|
import datetime
|
||||||
from enum import IntEnum
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F, Q
|
from django.db.models import Q
|
||||||
from django.db.models.functions import Concat, Substr
|
|
||||||
from django.utils import timezone as tz
|
from django.utils import timezone as tz
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
@ -64,7 +62,7 @@ class DiffusionQuerySet(BaseRerunQuerySet):
|
||||||
|
|
||||||
def on_air(self):
|
def on_air(self):
|
||||||
""" On air diffusions """
|
""" On air diffusions """
|
||||||
return self.filter(type=Diffusion.Type.on_air)
|
return self.filter(type=Diffusion.TYPE_ON_AIR)
|
||||||
|
|
||||||
def now(self, now=None, order=True):
|
def now(self, now=None, order=True):
|
||||||
""" Diffusions occuring now """
|
""" Diffusions occuring now """
|
||||||
|
@ -132,20 +130,20 @@ class Diffusion(BaseRerun):
|
||||||
"""
|
"""
|
||||||
objects = DiffusionQuerySet.as_manager()
|
objects = DiffusionQuerySet.as_manager()
|
||||||
|
|
||||||
class Type(IntEnum):
|
TYPE_ON_AIR = 0x00
|
||||||
on_air = 0x00
|
TYPE_UNCONFIRMED = 0x01
|
||||||
unconfirmed = 0x01
|
TYPE_CANCEL = 0x02
|
||||||
cancel = 0x02
|
TYPE_CHOICES = (
|
||||||
|
(TYPE_ON_AIR, _('on air')),
|
||||||
|
(TYPE_UNCONFIRMED, _('not confirmed')),
|
||||||
|
(TYPE_CANCEL, _('cancelled')),
|
||||||
|
)
|
||||||
|
|
||||||
episode = models.ForeignKey(
|
episode = models.ForeignKey(
|
||||||
Episode, models.CASCADE,
|
Episode, models.CASCADE, verbose_name=_('episode'),
|
||||||
verbose_name=_('episode'),
|
|
||||||
)
|
)
|
||||||
type = models.SmallIntegerField(
|
type = models.SmallIntegerField(
|
||||||
verbose_name=_('type'),
|
verbose_name=_('type'), default=TYPE_ON_AIR, choices=TYPE_CHOICES,
|
||||||
default=Type.on_air,
|
|
||||||
choices=[(int(y), _(x.replace('_', ' ')))
|
|
||||||
for x, y in Type.__members__.items()],
|
|
||||||
)
|
)
|
||||||
start = models.DateTimeField(_('start'))
|
start = models.DateTimeField(_('start'))
|
||||||
end = models.DateTimeField(_('end'))
|
end = models.DateTimeField(_('end'))
|
||||||
|
@ -222,7 +220,7 @@ class Diffusion(BaseRerun):
|
||||||
# TODO: property?
|
# TODO: property?
|
||||||
def is_live(self):
|
def is_live(self):
|
||||||
""" True if Diffusion is live (False if there are sounds files). """
|
""" True if Diffusion is live (False if there are sounds files). """
|
||||||
return self.type == self.Type.on_air and \
|
return self.type == self.TYPE_ON_AIR and \
|
||||||
not self.episode.sound_set.archive().count()
|
not self.episode.sound_set.archive().count()
|
||||||
|
|
||||||
def get_playlist(self, **types):
|
def get_playlist(self, **types):
|
||||||
|
@ -232,7 +230,7 @@ class Diffusion(BaseRerun):
|
||||||
"""
|
"""
|
||||||
from .sound import Sound
|
from .sound import Sound
|
||||||
return list(self.get_sounds(**types)
|
return list(self.get_sounds(**types)
|
||||||
.filter(path__isnull=False, type=Sound.Type.archive)
|
.filter(path__isnull=False, type=Sound.TYPE_ARCHIVE)
|
||||||
.values_list('path', flat=True))
|
.values_list('path', flat=True))
|
||||||
|
|
||||||
def get_sounds(self, **types):
|
def get_sounds(self, **types):
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
from collections import deque
|
from collections import deque
|
||||||
import datetime
|
|
||||||
from enum import IntEnum
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
@ -9,7 +7,7 @@ from django.utils import timezone as tz
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
from aircox import settings, utils
|
from aircox import settings
|
||||||
from .episode import Diffusion
|
from .episode import Diffusion
|
||||||
from .sound import Sound, Track
|
from .sound import Sound, Track
|
||||||
from .station import Station
|
from .station import Station
|
||||||
|
@ -35,10 +33,10 @@ class LogQuerySet(models.QuerySet):
|
||||||
self.filter(date__date__gte=date)
|
self.filter(date__date__gte=date)
|
||||||
|
|
||||||
def on_air(self):
|
def on_air(self):
|
||||||
return self.filter(type=Log.Type.on_air)
|
return self.filter(type=Log.TYPE_ON_AIR)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
return self.filter(type=Log.Type.start)
|
return self.filter(type=Log.TYPE_START)
|
||||||
|
|
||||||
def with_diff(self, with_it=True):
|
def with_diff(self, with_it=True):
|
||||||
return self.filter(diffusion__isnull=not with_it)
|
return self.filter(diffusion__isnull=not with_it)
|
||||||
|
@ -163,43 +161,33 @@ class Log(models.Model):
|
||||||
This only remember what has been played on the outputs, not on each
|
This only remember what has been played on the outputs, not on each
|
||||||
source; Source designate here which source is responsible of that.
|
source; Source designate here which source is responsible of that.
|
||||||
"""
|
"""
|
||||||
class Type(IntEnum):
|
|
||||||
stop = 0x00
|
TYPE_STOP = 0x00
|
||||||
"""
|
""" Source has been stopped, e.g. manually """
|
||||||
Source has been stopped, e.g. manually
|
|
||||||
"""
|
|
||||||
# Rule: \/ diffusion != null \/ sound != null
|
# Rule: \/ diffusion != null \/ sound != null
|
||||||
start = 0x01
|
TYPE_START = 0x01
|
||||||
""" Diffusion or sound has been request to be played. """
|
""" Diffusion or sound has been request to be played. """
|
||||||
cancel = 0x02
|
TYPE_CANCEL = 0x02
|
||||||
""" Diffusion has been canceled. """
|
""" Diffusion has been canceled. """
|
||||||
# Rule: \/ sound != null /\ track == null
|
# Rule: \/ sound != null /\ track == null
|
||||||
# \/ sound == null /\ track != null
|
# \/ sound == null /\ track != null
|
||||||
# \/ sound == null /\ track == null /\ comment = sound_path
|
# \/ sound == null /\ track == null /\ comment = sound_path
|
||||||
on_air = 0x03
|
TYPE_ON_AIR = 0x03
|
||||||
"""
|
""" Sound or diffusion occured on air """
|
||||||
The sound or diffusion has been detected occurring on air. Can
|
TYPE_OTHER = 0x04
|
||||||
also designate live diffusion, although Liquidsoap did not play
|
|
||||||
them since they don't have an attached sound archive.
|
|
||||||
"""
|
|
||||||
other = 0x04
|
|
||||||
""" Other log """
|
""" Other log """
|
||||||
|
TYPE_CHOICES = (
|
||||||
|
(TYPE_STOP, _('stop')), (TYPE_START, _('start')),
|
||||||
|
(TYPE_CANCEL, _('cancelled')), (TYPE_ON_AIR, _('on air')),
|
||||||
|
(TYPE_OTHER, _('other'))
|
||||||
|
)
|
||||||
|
|
||||||
station = models.ForeignKey(
|
station = models.ForeignKey(
|
||||||
Station, models.CASCADE,
|
Station, models.CASCADE,
|
||||||
verbose_name=_('station'),
|
verbose_name=_('station'), help_text=_('related station'),
|
||||||
help_text=_('related station'),
|
|
||||||
)
|
|
||||||
type = models.SmallIntegerField(
|
|
||||||
choices=[(int(y), _(x.replace('_', ' ')))
|
|
||||||
for x, y in Type.__members__.items()],
|
|
||||||
blank=True, null=True,
|
|
||||||
verbose_name=_('type'),
|
|
||||||
)
|
|
||||||
date = models.DateTimeField(
|
|
||||||
default=tz.now, db_index=True,
|
|
||||||
verbose_name=_('date'),
|
|
||||||
)
|
)
|
||||||
|
type = models.SmallIntegerField(_('type'), choices=TYPE_CHOICES)
|
||||||
|
date = models.DateTimeField(_('date'), default=tz.now, db_index=True)
|
||||||
source = models.CharField(
|
source = models.CharField(
|
||||||
# we use a CharField to avoid loosing logs information if the
|
# we use a CharField to avoid loosing logs information if the
|
||||||
# source is removed
|
# source is removed
|
||||||
|
|
|
@ -38,28 +38,30 @@ class Category(models.Model):
|
||||||
|
|
||||||
class PageQuerySet(InheritanceQuerySet):
|
class PageQuerySet(InheritanceQuerySet):
|
||||||
def draft(self):
|
def draft(self):
|
||||||
return self.filter(status=Page.STATUS.draft)
|
return self.filter(status=Page.STATUS_DRAFT)
|
||||||
|
|
||||||
def published(self):
|
def published(self):
|
||||||
return self.filter(status=Page.STATUS.published)
|
return self.filter(status=Page.STATUS_PUBLISHED)
|
||||||
|
|
||||||
def trash(self):
|
def trash(self):
|
||||||
return self.filter(status=Page.STATUS.trash)
|
return self.filter(status=Page.STATUS_TRASH)
|
||||||
|
|
||||||
|
|
||||||
class Page(models.Model):
|
class Page(models.Model):
|
||||||
""" Base class for publishable content """
|
""" Base class for publishable content """
|
||||||
class STATUS(IntEnum):
|
STATUS_DRAFT = 0x00
|
||||||
draft = 0x00
|
STATUS_PUBLISHED = 0x10
|
||||||
published = 0x10
|
STATUS_TRASH = 0x20
|
||||||
trash = 0x20
|
STATUS_CHOICES = (
|
||||||
|
(STATUS_DRAFT, _('draft')),
|
||||||
|
(STATUS_PUBLISHED, _('published')),
|
||||||
|
(STATUS_TRASH, _('trash')),
|
||||||
|
)
|
||||||
|
|
||||||
title = models.CharField(max_length=128)
|
title = models.CharField(max_length=128)
|
||||||
slug = models.SlugField(_('slug'), blank=True, unique=True)
|
slug = models.SlugField(_('slug'), blank=True, unique=True)
|
||||||
status = models.PositiveSmallIntegerField(
|
status = models.PositiveSmallIntegerField(
|
||||||
_('status'),
|
_('status'), default=STATUS_DRAFT, choices=STATUS_CHOICES,
|
||||||
default=STATUS.draft,
|
|
||||||
choices=[(int(y), _(x)) for x, y in STATUS.__members__.items()],
|
|
||||||
)
|
)
|
||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
Category, models.SET_NULL,
|
Category, models.SET_NULL,
|
||||||
|
@ -84,8 +86,6 @@ class Page(models.Model):
|
||||||
|
|
||||||
detail_url_name = None
|
detail_url_name = None
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}: {}'.format(self._meta.verbose_name,
|
return '{}: {}'.format(self._meta.verbose_name,
|
||||||
|
@ -104,15 +104,15 @@ class Page(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_draft(self):
|
def is_draft(self):
|
||||||
return self.status == self.STATUS.draft
|
return self.status == self.STATUS_DRAFT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_published(self):
|
def is_published(self):
|
||||||
return self.status == self.STATUS.published
|
return self.status == self.STATUS_PUBLISHED
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_trash(self):
|
def is_trash(self):
|
||||||
return self.status == self.STATUS.trash
|
return self.status == self.STATUS_TRASH
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def headline(self):
|
def headline(self):
|
||||||
|
@ -132,6 +132,16 @@ class Page(models.Model):
|
||||||
return cls(**cls.get_init_kwargs_from(page, **kwargs))
|
return cls(**cls.get_init_kwargs_from(page, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
class Comment(models.Model):
|
||||||
|
page = models.ForeignKey(
|
||||||
|
Page, models.CASCADE, verbose_name=_('related page'),
|
||||||
|
)
|
||||||
|
nickname = models.CharField(_('nickname'), max_length=32)
|
||||||
|
email = models.EmailField(_('email'), max_length=32)
|
||||||
|
date = models.DateTimeField(auto_now_add=True)
|
||||||
|
content = models.TextField(_('content'), max_length=1024)
|
||||||
|
|
||||||
|
|
||||||
class NavItem(models.Model):
|
class NavItem(models.Model):
|
||||||
""" Navigation menu items """
|
""" Navigation menu items """
|
||||||
station = models.ForeignKey(
|
station = models.ForeignKey(
|
||||||
|
|
|
@ -45,6 +45,11 @@ class Program(Page):
|
||||||
Renaming a Program rename the corresponding directory to matches the new
|
Renaming a Program rename the corresponding directory to matches the new
|
||||||
name if it does not exists.
|
name if it does not exists.
|
||||||
"""
|
"""
|
||||||
|
# explicit foreign key in order to avoid related name clashes
|
||||||
|
page = models.OneToOneField(
|
||||||
|
Page, models.CASCADE,
|
||||||
|
parent_link=True, related_name='program_page'
|
||||||
|
)
|
||||||
station = models.ForeignKey(
|
station = models.ForeignKey(
|
||||||
Station,
|
Station,
|
||||||
verbose_name=_('station'),
|
verbose_name=_('station'),
|
||||||
|
@ -478,7 +483,7 @@ class Schedule(BaseRerun):
|
||||||
initial = diffusions[initial]
|
initial = diffusions[initial]
|
||||||
|
|
||||||
diffusions[date] = Diffusion(
|
diffusions[date] = Diffusion(
|
||||||
episode=episode, type=Diffusion.Type.on_air,
|
episode=episode, type=Diffusion.TYPE_ON_AIR,
|
||||||
initial=initial, start=date, end=date+duration
|
initial=initial, start=date, end=date+duration
|
||||||
)
|
)
|
||||||
return episodes.values(), diffusions.values()
|
return episodes.values(), diffusions.values()
|
||||||
|
|
|
@ -36,7 +36,7 @@ class SoundQuerySet(models.QuerySet):
|
||||||
|
|
||||||
def archive(self):
|
def archive(self):
|
||||||
""" Return sounds that are archives """
|
""" Return sounds that are archives """
|
||||||
return self.filter(type=Sound.Type.archive)
|
return self.filter(type=Sound.TYPE_ARCHIVE)
|
||||||
|
|
||||||
def paths(self, archive=True, order_by=True):
|
def paths(self, archive=True, order_by=True):
|
||||||
"""
|
"""
|
||||||
|
@ -55,11 +55,14 @@ class Sound(models.Model):
|
||||||
A Sound is the representation of a sound file that can be either an excerpt
|
A Sound is the representation of a sound file that can be either an excerpt
|
||||||
or a complete archive of the related diffusion.
|
or a complete archive of the related diffusion.
|
||||||
"""
|
"""
|
||||||
class Type(IntEnum):
|
TYPE_OTHER = 0x00
|
||||||
other = 0x00,
|
TYPE_ARCHIVE = 0x01
|
||||||
archive = 0x01,
|
TYPE_EXCERPT = 0x02
|
||||||
excerpt = 0x02,
|
TYPE_REMOVED = 0x03
|
||||||
removed = 0x03,
|
TYPE_CHOICES = (
|
||||||
|
(TYPE_OTHER, _('other')), (TYPE_ARCHIVE, _('archive')),
|
||||||
|
(TYPE_EXCERPT, _('excerpt')), (TYPE_REMOVED, _('removed'))
|
||||||
|
)
|
||||||
|
|
||||||
name = models.CharField(_('name'), max_length=64)
|
name = models.CharField(_('name'), max_length=64)
|
||||||
program = models.ForeignKey(
|
program = models.ForeignKey(
|
||||||
|
@ -72,11 +75,7 @@ class Sound(models.Model):
|
||||||
Episode, models.SET_NULL, blank=True, null=True,
|
Episode, models.SET_NULL, blank=True, null=True,
|
||||||
verbose_name=_('episode'),
|
verbose_name=_('episode'),
|
||||||
)
|
)
|
||||||
type = models.SmallIntegerField(
|
type = models.SmallIntegerField(_('type'), choices=TYPE_CHOICES)
|
||||||
verbose_name=_('type'),
|
|
||||||
choices=[(int(y), _(x)) for x, y in Type.__members__.items()],
|
|
||||||
blank=True, null=True
|
|
||||||
)
|
|
||||||
# FIXME: url() does not use the same directory than here
|
# FIXME: url() does not use the same directory than here
|
||||||
# should we use FileField for more reliability?
|
# should we use FileField for more reliability?
|
||||||
path = models.FilePathField(
|
path = models.FilePathField(
|
||||||
|
@ -196,21 +195,21 @@ class Sound(models.Model):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.file_exists():
|
if not self.file_exists():
|
||||||
if self.type == self.Type.removed:
|
if self.type == self.TYPE_REMOVED:
|
||||||
return
|
return
|
||||||
logger.info('sound %s: has been removed', self.path)
|
logger.info('sound %s: has been removed', self.path)
|
||||||
self.type = self.Type.removed
|
self.type = self.TYPE_REMOVED
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# not anymore removed
|
# not anymore removed
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
if self.type == self.Type.removed and self.program:
|
if self.type == self.TYPE_REMOVED and self.program:
|
||||||
changed = True
|
changed = True
|
||||||
self.type = self.Type.archive \
|
self.type = self.TYPE_ARCHIVE \
|
||||||
if self.path.startswith(self.program.archives_path) else \
|
if self.path.startswith(self.program.archives_path) else \
|
||||||
self.Type.excerpt
|
self.TYPE_EXCERPT
|
||||||
|
|
||||||
# check mtime -> reset quality if changed (assume file changed)
|
# check mtime -> reset quality if changed (assume file changed)
|
||||||
mtime = self.get_mtime()
|
mtime = self.get_mtime()
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
from enum import IntEnum
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -91,36 +90,32 @@ class Port(models.Model):
|
||||||
Some port types may be not available depending on the
|
Some port types may be not available depending on the
|
||||||
direction of the port.
|
direction of the port.
|
||||||
"""
|
"""
|
||||||
class Direction(IntEnum):
|
DIRECTION_INPUT = 0x00
|
||||||
input = 0x00
|
DIRECTION_OUTPUT = 0x01
|
||||||
output = 0x01
|
DIRECTION_CHOICES = ((DIRECTION_INPUT, _('input')),
|
||||||
|
(DIRECTION_OUTPUT, _('output')))
|
||||||
|
|
||||||
class Type(IntEnum):
|
TYPE_JACK = 0x00
|
||||||
jack = 0x00
|
TYPE_ALSA = 0x01
|
||||||
alsa = 0x01
|
TYPE_PULSEAUDIO = 0x02
|
||||||
pulseaudio = 0x02
|
TYPE_ICECAST = 0x03
|
||||||
icecast = 0x03
|
TYPE_HTTP = 0x04
|
||||||
http = 0x04
|
TYPE_HTTPS = 0x05
|
||||||
https = 0x05
|
TYPE_FILE = 0x06
|
||||||
file = 0x06
|
TYPE_CHOICES = (
|
||||||
|
(TYPE_JACK, 'jack'), (TYPE_ALSA, 'alsa'),
|
||||||
|
(TYPE_PULSEAUDIO, 'pulseaudio'), (TYPE_ICECAST, 'icecast'),
|
||||||
|
(TYPE_HTTP, 'http'), (TYPE_HTTPS, 'https'),
|
||||||
|
(TYPE_FILE, _('file'))
|
||||||
|
)
|
||||||
|
|
||||||
station = models.ForeignKey(
|
station = models.ForeignKey(
|
||||||
Station,
|
Station, models.CASCADE, verbose_name=_('station'))
|
||||||
verbose_name=_('station'),
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
)
|
|
||||||
direction = models.SmallIntegerField(
|
direction = models.SmallIntegerField(
|
||||||
_('direction'),
|
_('direction'), choices=DIRECTION_CHOICES)
|
||||||
choices=[(int(y), _(x)) for x, y in Direction.__members__.items()],
|
type = models.SmallIntegerField(_('type'), choices=TYPE_CHOICES)
|
||||||
)
|
|
||||||
type = models.SmallIntegerField(
|
|
||||||
_('type'),
|
|
||||||
# we don't translate the names since it is project names.
|
|
||||||
choices=[(int(y), x) for x, y in Type.__members__.items()],
|
|
||||||
)
|
|
||||||
active = models.BooleanField(
|
active = models.BooleanField(
|
||||||
_('active'),
|
_('active'), default=True,
|
||||||
default=True,
|
|
||||||
help_text=_('this port is active')
|
help_text=_('this port is active')
|
||||||
)
|
)
|
||||||
settings = models.TextField(
|
settings = models.TextField(
|
||||||
|
@ -136,13 +131,13 @@ class Port(models.Model):
|
||||||
Return True if the type is available for the given direction.
|
Return True if the type is available for the given direction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.direction == self.Direction.input:
|
if self.direction == self.DIRECTION_INPUT:
|
||||||
return self.type not in (
|
return self.type not in (
|
||||||
self.Type.icecast, self.Type.file
|
self.TYPE_ICECAST, self.TYPE_FILE
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.type not in (
|
return self.type not in (
|
||||||
self.Type.http, self.Type.https
|
self.TYPE_HTTP, self.TYPE_HTTPS
|
||||||
)
|
)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
|
@ -7159,12 +7159,18 @@ label.panel-block {
|
||||||
.is-borderless {
|
.is-borderless {
|
||||||
border: none; }
|
border: none; }
|
||||||
|
|
||||||
|
.has-background-transparent {
|
||||||
|
background-color: transparent; }
|
||||||
|
|
||||||
.navbar + .container {
|
.navbar + .container {
|
||||||
margin-top: 1em; }
|
margin-top: 1em; }
|
||||||
|
|
||||||
.navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow {
|
.navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow {
|
||||||
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.1); }
|
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.1); }
|
||||||
|
|
||||||
|
a.navbar-item.is-active {
|
||||||
|
border-bottom: 1px grey solid; }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
.navbar-brand img {
|
.navbar-brand img {
|
||||||
min-height: 6em;
|
min-height: 6em;
|
||||||
|
|
|
@ -419,7 +419,7 @@ eval("/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__,
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
eval("/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"b\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n [\n _c(\"div\", { staticClass: \"tabs is-centered\" }, [\n _c(\"ul\", [_vm._t(\"tabs\", null, { value: _vm.value })], 2)\n ]),\n _vm._v(\" \"),\n _vm._t(\"default\", null, { value: _vm.value })\n ],\n 2\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/vue/tabs.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options");
|
eval("/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"b\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n [\n _c(\"div\", { staticClass: \"tabs is-centered is-medium\" }, [\n _c(\"ul\", [_vm._t(\"tabs\", null, { value: _vm.value })], 2)\n ]),\n _vm._v(\" \"),\n _vm._t(\"default\", null, { value: _vm.value })\n ],\n 2\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/vue/tabs.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options");
|
||||||
|
|
||||||
/***/ })
|
/***/ })
|
||||||
|
|
||||||
|
|
32
aircox/templates/admin/aircox/page_change_form.html
Normal file
32
aircox/templates/admin/aircox/page_change_form.html
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{% extends "admin/change_form.html" %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block extrahead %}{{ block.super }}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "aircox/vendor.css" %}"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "aircox/main.css" %}"/>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block submit_buttons_bottom %}
|
||||||
|
{% if has_change_permission %}
|
||||||
|
<div class="columns is-size-5">
|
||||||
|
<div class="column has-text-left">
|
||||||
|
{% if original and not original.is_trash %}
|
||||||
|
<button type="submit" name="status" value="32" class="button is-danger is-size-6">{% trans "Move to trash" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if original and not original.is_draft %}
|
||||||
|
<button type="submit" name="status" value="0" class="button is-warning is-size-6">{% trans "Mark as draft" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column has-text-right">
|
||||||
|
<button type="submit" class="button is-secondary is-size-6">{% trans "Save" %}</button>
|
||||||
|
<button type="submit" name="_continue" class="button is-secondary is-size-6">{% trans "Save and continue" %}</button>
|
||||||
|
{% if not original.is_published %}
|
||||||
|
<button type="submit" name="status" value="16" class="button is-primary is-size-6">{% trans "Publish" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
29
aircox/templates/aircox/article_detail.html
Normal file
29
aircox/templates/aircox/article_detail.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{% extends "aircox/page.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block side_nav %}
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
{% if side_items %}
|
||||||
|
<section>
|
||||||
|
<h4 class="title is-4">{% trans "Latest news" %}</h4>
|
||||||
|
|
||||||
|
{% for object in side_items %}
|
||||||
|
{% include "aircox/page_item.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<nav class="pagination is-centered">
|
||||||
|
<ul class="pagination-list">
|
||||||
|
<li>
|
||||||
|
<a href="{% url "article-list" %}" class="pagination-link"
|
||||||
|
aria-label="{% trans "Show all news" %}">
|
||||||
|
{% trans "More news" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
12
aircox/templates/aircox/article_list.html
Normal file
12
aircox/templates/aircox/article_list.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends "aircox/page_list.html" %}
|
||||||
|
{% load i18n aircox %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% if parent %}
|
||||||
|
{% with parent.title as parent %}
|
||||||
|
{% blocktrans %}Articles of {{ parent }}{% endblocktrans %}
|
||||||
|
{% endwith %}
|
||||||
|
{% else %}{{ block.super }}{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ Context:
|
||||||
|
|
||||||
{% block main %}{% endblock main %}
|
{% block main %}{% endblock main %}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{% if show_side_nav %}
|
{% if show_side_nav %}
|
||||||
<aside class="column is-one-third-desktop">
|
<aside class="column is-one-third-desktop">
|
||||||
{% block cover %}
|
{% block cover %}
|
||||||
|
|
|
@ -1,20 +1,28 @@
|
||||||
{% extends "aircox/page.html" %}
|
{% extends "aircox/page.html" %}
|
||||||
{% load i18n aircox %}
|
{% load i18n aircox %}
|
||||||
|
|
||||||
{% block title %}{% trans "Timetable" %}{% endblock %}
|
{% block title %}
|
||||||
|
{% with station.name as station %}
|
||||||
|
{% blocktrans %}This week's shows... {% endblocktrans %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
<div class="column">
|
||||||
|
{% blocktrans %}From <b>{{ start }}</b> to <b>{{ end }}</b>{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
<section class="section">
|
|
||||||
<h3 class="subtitle size-3">
|
|
||||||
{% blocktrans %}From <b>{{ start }}</b> to <b>{{ end }}</b>{% endblocktrans %}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
|
{% with True as hide_schedule %}
|
||||||
|
<section>
|
||||||
{% unique_id "timetable" as timetable_id %}
|
{% unique_id "timetable" as timetable_id %}
|
||||||
<a-tabs default="{{ date }}">
|
<a-tabs default="{{ date }}">
|
||||||
<template v-slot:tabs="scope" noscript="hidden">
|
<template v-slot:tabs="scope" noscript="hidden">
|
||||||
<li><a href="{% url "timetable" date=prev_date %}"><</a></li>
|
<li><a href="{% url "timetable" date=prev_date %}">❬ {% trans "Before" %}</a></li>
|
||||||
|
|
||||||
{% for day in by_date.keys %}
|
{% for day in by_date.keys %}
|
||||||
<a-tab value="{{ day }}">
|
<a-tab value="{{ day }}">
|
||||||
|
@ -25,11 +33,10 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "timetable" date=next_date %}">></a>
|
<a href="{% url "timetable" date=next_date %}">{% trans "After" %} ❭</a>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
{% with True as hide_schedule %}
|
|
||||||
<template v-slot:default="{value}">
|
<template v-slot:default="{value}">
|
||||||
{% for day, diffusions in by_date.items %}
|
{% for day, diffusions in by_date.items %}
|
||||||
<noscript><h4 class="subtitle is-4">{{ day|date:"l d F Y" }}</h4></noscript>
|
<noscript><h4 class="subtitle is-4">{{ day|date:"l d F Y" }}</h4></noscript>
|
||||||
|
@ -51,8 +58,8 @@
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</template>
|
</template>
|
||||||
{% endwith %}
|
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</section>
|
</section>
|
||||||
|
{% endwith %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,9 @@
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
{% if podcasts or tracks %}
|
{% if podcasts or tracks %}
|
||||||
<section class="columns is-desktop">
|
<div class="columns is-desktop">
|
||||||
{% if tracks %}
|
{% if tracks %}
|
||||||
<div class="column">
|
<section class="column">
|
||||||
<h4 class="title is-4">{% trans "Playlist" %}</h4>
|
<h4 class="title is-4">{% trans "Playlist" %}</h4>
|
||||||
<ol>
|
<ol>
|
||||||
{% for track in tracks %}
|
{% for track in tracks %}
|
||||||
|
@ -46,17 +46,17 @@
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if podcasts %}
|
{% if podcasts %}
|
||||||
<div class="column">
|
<section class="column">
|
||||||
<h4 class="title is-4">{% trans "Podcasts" %}</h4>
|
<h4 class="title is-4">{% trans "Podcasts" %}</h4>
|
||||||
{% for object in podcasts %}
|
{% for object in podcasts %}
|
||||||
{% include "aircox/podcast_item.html" %}
|
{% include "aircox/podcast_item.html" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,11 @@
|
||||||
{% load i18n aircox %}
|
{% load i18n aircox %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if program %}
|
{% if parent %}
|
||||||
{% with program.title as program %}
|
{% with parent.title as parent %}
|
||||||
{% blocktrans %}Episodes of {{ program }}{% endblocktrans %}
|
{% blocktrans %}Episodes of {{ parent }}{% endblocktrans %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}{{ block.super }}{% endif %}
|
||||||
{% trans "Episodes" %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
{% if dates %}
|
{% if dates %}
|
||||||
<nav class="tabs is-centered" aria-label="{% trans "Other days' logs" %}">
|
<nav class="tabs is-medium is-centered" aria-label="{% trans "Other days' logs" %}">
|
||||||
<ul>
|
<ul>
|
||||||
{% for day in dates %}
|
{% for day in dates %}
|
||||||
<li {% if day == date %}class="is-active"{% endif %}>
|
<li {% if day == date %}class="is-active"{% endif %}>
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
{# <h4 class="subtitle size-4">{{ date }}</h4> #}
|
{# <h4 class="subtitle size-4">{{ date }}</h4> #}
|
||||||
{% with True as hide_schedule %}
|
{% with True as hide_schedule %}
|
||||||
<table class="table is-striped is-hoverable is-fullwidth">
|
<table class="table is-striped is-hoverable is-fullwidth has-background-transparent">
|
||||||
{% for object in object_list %}
|
{% for object in object_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -16,7 +16,7 @@ Context:
|
||||||
|
|
||||||
{% block head_title %}
|
{% block head_title %}
|
||||||
{% block title %}{{ title }}{% endblock %}
|
{% block title %}{{ title }}{% endblock %}
|
||||||
{% if title %} ‐ {% endif %}
|
—
|
||||||
{{ station.name }}
|
{{ station.name }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
{% extends "aircox/page.html" %}
|
{% extends "aircox/page.html" %}
|
||||||
{% load i18n aircox %}
|
{% load i18n aircox %}
|
||||||
|
|
||||||
{% with view.model|verbose_name:True as model_name_plural %}
|
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ model_name_plural }}
|
{{ view.model|verbose_name:True|title }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block side_nav %}
|
{% block side_nav %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
|
{% if filter_categories|length != 1 %}
|
||||||
<section class="toolbar">
|
<section class="toolbar">
|
||||||
<h4 class="subtitle is-5">{% trans "Filters" %}</h4>
|
<h4 class="subtitle is-5">{% trans "Filters" %}</h4>
|
||||||
<form method="GET" action="">
|
<form method="GET" action="">
|
||||||
|
@ -51,6 +50,7 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,5 +97,3 @@
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|
||||||
<a-player ref="player" src="{{ audio_streams.0 }}"
|
<a-player ref="player" src="{{ audio_streams.0 }}"
|
||||||
live-info-url="{% url "api-live" %}" live-info-timeout="15"
|
live-info-url="{% url "api-live" %}" :live-info-timeout="15"
|
||||||
button-title="{% trans "Play/pause audio" %}">
|
button-title="{% trans "Play/pause audio" %}">
|
||||||
<template v-slot:sources>
|
<template v-slot:sources>
|
||||||
{% for stream in audio_streams %}
|
{% for stream in audio_streams %}
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
{% block side_nav %}
|
{% block side_nav %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
{% if episodes %}
|
{% if side_items %}
|
||||||
<section>
|
<section>
|
||||||
<h4 class="title is-4">{% trans "Last shows" %}</h4>
|
<h4 class="title is-4">{% trans "Last shows" %}</h4>
|
||||||
|
|
||||||
{% for object in episodes %}
|
{% for object in side_items %}
|
||||||
{% include "aircox/episode_item.html" %}
|
{% include "aircox/episode_item.html" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
@ -16,13 +16,14 @@
|
||||||
<nav class="pagination is-centered">
|
<nav class="pagination is-centered">
|
||||||
<ul class="pagination-list">
|
<ul class="pagination-list">
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url "diffusion-list" program_slug=program.slug %}"
|
<a href="{% url "diffusion-list" parent_slug=program.slug %}"
|
||||||
class="pagination-link"
|
class="pagination-link"
|
||||||
aria-label="{% trans "Show all diffusions" %}">
|
aria-label="{% trans "Show all program's diffusions" %}">
|
||||||
{% trans "All shows" %}
|
{% trans "More shows" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</nav>
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -6,3 +6,39 @@
|
||||||
{% include "aircox/program_header.html" %}
|
{% include "aircox/program_header.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
{% with show_headline=False %}
|
||||||
|
<div class="columns is-desktop">
|
||||||
|
{% if articles %}
|
||||||
|
<section class="column">
|
||||||
|
<h4 class="title is-4">{% trans "Articles" %}</h4>
|
||||||
|
|
||||||
|
{% for object in articles %}
|
||||||
|
{% include "aircox/page_item.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<nav class="pagination is-centered">
|
||||||
|
<ul class="pagination-list">
|
||||||
|
<li>
|
||||||
|
<a href="{% url "article-list" parent_slug=program.slug %}"
|
||||||
|
class="pagination-link"
|
||||||
|
aria-label="{% trans "Show all program's articles" %}">
|
||||||
|
{% trans "More articles" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,26 +30,26 @@ urls = [
|
||||||
views.ArticleListView.as_view(model=models.Article, is_static=False),
|
views.ArticleListView.as_view(model=models.Article, is_static=False),
|
||||||
name='article-list'),
|
name='article-list'),
|
||||||
path(_('articles/<slug:slug>/'),
|
path(_('articles/<slug:slug>/'),
|
||||||
views.PageDetailView.as_view(model=models.Article),
|
views.ArticleDetailView.as_view(),
|
||||||
name='article-detail'),
|
name='article-detail'),
|
||||||
|
|
||||||
path(_('programs/'), views.PageListView.as_view(model=models.Program),
|
path(_('programs/'), views.PageListView.as_view(model=models.Program),
|
||||||
name='program-list'),
|
name='program-list'),
|
||||||
path(_('programs/<slug:slug>/'),
|
path(_('programs/<slug:slug>/'),
|
||||||
views.ProgramDetailView.as_view(), name='program-detail'),
|
views.ProgramDetailView.as_view(), name='program-detail'),
|
||||||
path(_('programs/<slug:program_slug>/episodes/'),
|
path(_('programs/<slug:parent_slug>/episodes/'),
|
||||||
views.EpisodeListView.as_view(), name='diffusion-list'),
|
views.EpisodeListView.as_view(), name='diffusion-list'),
|
||||||
path(_('programs/<slug:program_slug>/articles/'),
|
path(_('programs/<slug:parent_slug>/articles/'),
|
||||||
views.ArticleListView.as_view(), name='article-list'),
|
views.ArticleListView.as_view(), name='article-list'),
|
||||||
|
|
||||||
path(_('episodes/'),
|
path(_('episodes/'),
|
||||||
views.EpisodeListView.as_view(), name='diffusion-list'),
|
views.EpisodeListView.as_view(), name='diffusion-list'),
|
||||||
path(_('episodes/week/'),
|
|
||||||
views.TimetableView.as_view(), name='timetable'),
|
|
||||||
path(_('episodes/week/<week:date>/'),
|
|
||||||
views.TimetableView.as_view(), name='timetable'),
|
|
||||||
path(_('episodes/<slug:slug>/'),
|
path(_('episodes/<slug:slug>/'),
|
||||||
views.EpisodeDetailView.as_view(), name='episode-detail'),
|
views.EpisodeDetailView.as_view(), name='episode-detail'),
|
||||||
|
path(_('week/'),
|
||||||
|
views.TimetableView.as_view(), name='timetable'),
|
||||||
|
path(_('week/<week:date>/'),
|
||||||
|
views.TimetableView.as_view(), name='timetable'),
|
||||||
|
|
||||||
path(_('logs/'), views.LogListView.as_view(), name='logs'),
|
path(_('logs/'), views.LogListView.as_view(), name='logs'),
|
||||||
path(_('logs/<date:date>/'), views.LogListView.as_view(), name='logs'),
|
path(_('logs/<date:date>/'), views.LogListView.as_view(), name='logs'),
|
||||||
|
|
257
aircox/views.py
257
aircox/views.py
|
@ -1,257 +0,0 @@
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from django.views.generic.base import View, TemplateResponseMixin
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
||||||
from django.http import HttpResponse, Http404
|
|
||||||
from django.shortcuts import render
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from django.utils import timezone as tz
|
|
||||||
from django.views.decorators.cache import cache_page
|
|
||||||
|
|
||||||
import aircox.models as models
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME usefull?
|
|
||||||
class Stations:
|
|
||||||
stations = models.Station.objects.all()
|
|
||||||
update_timeout = None
|
|
||||||
fetch_timeout = None
|
|
||||||
|
|
||||||
def fetch(self):
|
|
||||||
if self.fetch_timeout and self.fetch_timeout > tz.now():
|
|
||||||
return
|
|
||||||
|
|
||||||
self.fetch_timeout = tz.now() + tz.timedelta(seconds=5)
|
|
||||||
for station in self.stations:
|
|
||||||
station.streamer.fetch()
|
|
||||||
|
|
||||||
|
|
||||||
stations = Stations()
|
|
||||||
|
|
||||||
|
|
||||||
@cache_page(10)
|
|
||||||
def on_air(request):
|
|
||||||
try:
|
|
||||||
import aircox_cms.models as cms
|
|
||||||
except:
|
|
||||||
cms = None
|
|
||||||
|
|
||||||
station = request.GET.get('station')
|
|
||||||
if station:
|
|
||||||
# FIXME: by name???
|
|
||||||
station = stations.stations.filter(name=station)
|
|
||||||
if not station.count():
|
|
||||||
return HttpResponse('{}')
|
|
||||||
else:
|
|
||||||
station = stations.stations
|
|
||||||
|
|
||||||
station = station.first()
|
|
||||||
on_air = station.on_air(count=10).select_related('track', 'diffusion')
|
|
||||||
if not on_air.count():
|
|
||||||
return HttpResponse('')
|
|
||||||
|
|
||||||
last = on_air.first()
|
|
||||||
if last.track:
|
|
||||||
last = {'date': last.date, 'type': 'track',
|
|
||||||
'artist': last.track.artist, 'title': last.track.title}
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
diff = last.diffusion
|
|
||||||
publication = None
|
|
||||||
# FIXME CMS
|
|
||||||
if cms:
|
|
||||||
publication = \
|
|
||||||
cms.DiffusionPage.objects.filter(
|
|
||||||
diffusion=diff.initial or diff).first() or \
|
|
||||||
cms.ProgramPage.objects.filter(
|
|
||||||
program=last.program).first()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
last = {'date': diff.start, 'type': 'diffusion',
|
|
||||||
'title': diff.program.name,
|
|
||||||
'url': publication.specific.url if publication else None}
|
|
||||||
last['date'] = str(last['date'])
|
|
||||||
return HttpResponse(json.dumps(last))
|
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
|
||||||
# - login url
|
|
||||||
class Monitor(View, TemplateResponseMixin, LoginRequiredMixin):
|
|
||||||
template_name = 'aircox/controllers/monitor.html'
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
stations.fetch()
|
|
||||||
return {'stations': stations.stations}
|
|
||||||
|
|
||||||
def get(self, request=None, **kwargs):
|
|
||||||
if not request.user.is_active:
|
|
||||||
return Http404()
|
|
||||||
|
|
||||||
self.request = request
|
|
||||||
context = self.get_context_data(**kwargs)
|
|
||||||
return render(request, self.template_name, context)
|
|
||||||
|
|
||||||
def post(self, request=None, **kwargs):
|
|
||||||
if not request.user.is_active:
|
|
||||||
return Http404()
|
|
||||||
|
|
||||||
if not ('action' or 'station') in request.POST:
|
|
||||||
return HttpResponse('')
|
|
||||||
|
|
||||||
POST = request.POST
|
|
||||||
POST.get('controller')
|
|
||||||
action = POST.get('action')
|
|
||||||
|
|
||||||
station = stations.stations.filter(name=POST.get('station')) \
|
|
||||||
.first()
|
|
||||||
if not station:
|
|
||||||
return Http404()
|
|
||||||
|
|
||||||
source = None
|
|
||||||
if 'source' in POST:
|
|
||||||
source = [s for s in station.sources
|
|
||||||
if s.name == POST['source']]
|
|
||||||
source = source[0]
|
|
||||||
if not source:
|
|
||||||
return Http404
|
|
||||||
|
|
||||||
station.streamer.fetch()
|
|
||||||
source = source or station.streamer.source
|
|
||||||
if action == 'skip':
|
|
||||||
self.actionSkip(request, station, source)
|
|
||||||
if action == 'restart':
|
|
||||||
self.actionRestart(request, station, source)
|
|
||||||
return HttpResponse('')
|
|
||||||
|
|
||||||
def actionSkip(self, request, station, source):
|
|
||||||
source.skip()
|
|
||||||
|
|
||||||
def actionRestart(self, request, station, source):
|
|
||||||
source.restart()
|
|
||||||
|
|
||||||
|
|
||||||
class StatisticsView(View, TemplateResponseMixin, LoginRequiredMixin):
|
|
||||||
"""
|
|
||||||
View for statistics.
|
|
||||||
"""
|
|
||||||
# we cannot manipulate queryset: we have to be able to read from archives
|
|
||||||
template_name = 'aircox/controllers/stats.html'
|
|
||||||
|
|
||||||
class Item:
|
|
||||||
date = None
|
|
||||||
end = None
|
|
||||||
name = None
|
|
||||||
related = None
|
|
||||||
tracks = None
|
|
||||||
tags = None
|
|
||||||
col = None
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self.__dict__.update(kwargs)
|
|
||||||
|
|
||||||
class Stats:
|
|
||||||
station = None
|
|
||||||
date = None
|
|
||||||
items = None
|
|
||||||
"""
|
|
||||||
Log or Diffusion object that has been diffused by date. These
|
|
||||||
objects have extra fields:
|
|
||||||
- tags: [ (tag_name, tag_count), ...]
|
|
||||||
- tracks_count: total count of tracks
|
|
||||||
"""
|
|
||||||
count = 0
|
|
||||||
#rows = None
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self.items = []
|
|
||||||
# self.rows = []
|
|
||||||
self.__dict__.update(kwargs)
|
|
||||||
|
|
||||||
# Note: one row contains a column for diffusions and one for streams
|
|
||||||
# def append(self, log):
|
|
||||||
# if log.col == 0:
|
|
||||||
# self.rows.append((log, []))
|
|
||||||
# return
|
|
||||||
#
|
|
||||||
# if self.rows:
|
|
||||||
# row = self.rows[len(self.rows)-1]
|
|
||||||
# last = row[0] or row[1][len(row[1])-1]
|
|
||||||
# if last.date < log.date < last.end:
|
|
||||||
# row[1].append(log)
|
|
||||||
# return
|
|
||||||
#
|
|
||||||
# # all other cases: new row
|
|
||||||
# self.rows.append((None, [log]))
|
|
||||||
|
|
||||||
def get_stats(self, station, date):
|
|
||||||
"""
|
|
||||||
Return statistics for the given station and date.
|
|
||||||
"""
|
|
||||||
stats = self.Stats(station=station, date=date,
|
|
||||||
items=[], tags={})
|
|
||||||
|
|
||||||
qs = Log.objects.station(station).on_air() \
|
|
||||||
.prefetch_related('diffusion', 'sound', 'track', 'track__tags')
|
|
||||||
if not qs.exists():
|
|
||||||
qs = models.Log.objects.load_archive(station, date)
|
|
||||||
|
|
||||||
sound_log = None
|
|
||||||
for log in qs:
|
|
||||||
rel, item = None, None
|
|
||||||
if log.diffusion:
|
|
||||||
rel, item = log.diffusion, self.Item(
|
|
||||||
name=rel.program.name, type=_('Diffusion'), col=0,
|
|
||||||
tracks=models.Track.objects.filter(diffusion=log.diffusion)
|
|
||||||
.prefetch_related('tags'),
|
|
||||||
)
|
|
||||||
sound_log = None
|
|
||||||
elif log.sound:
|
|
||||||
rel, item = log.sound, self.Item(
|
|
||||||
name=rel.program.name + ': ' + os.path.basename(rel.path),
|
|
||||||
type=_('Stream'), col=1, tracks=[],
|
|
||||||
)
|
|
||||||
sound_log = item
|
|
||||||
elif log.track:
|
|
||||||
# append to last sound log
|
|
||||||
if not sound_log:
|
|
||||||
continue
|
|
||||||
sound_log.tracks.append(log.track)
|
|
||||||
sound_log.end = log.end
|
|
||||||
continue
|
|
||||||
|
|
||||||
item.date = log.date
|
|
||||||
item.end = log.end
|
|
||||||
item.related = rel
|
|
||||||
# stats.append(item)
|
|
||||||
stats.items.append(item)
|
|
||||||
return stats
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = {}
|
|
||||||
date = datetime.date.today()
|
|
||||||
|
|
||||||
try:
|
|
||||||
GET = self.request.GET
|
|
||||||
year = int(GET["year"]) if 'year' in GET else date.year
|
|
||||||
month = int(GET["month"]) if 'month' in GET else date.month
|
|
||||||
day = int(GET["day"]) if 'day' in GET else date.day
|
|
||||||
date = datetime.date(year, month, day)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
context["statistics"] = [
|
|
||||||
self.get_stats(station, date)
|
|
||||||
for station in models.Station.objects.all()
|
|
||||||
]
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get(self, request=None, **kwargs):
|
|
||||||
if not request.user.is_active:
|
|
||||||
return Http404()
|
|
||||||
|
|
||||||
self.request = request
|
|
||||||
context = self.get_context_data(**kwargs)
|
|
||||||
return render(request, self.template_name, context)
|
|
|
@ -1,6 +1,6 @@
|
||||||
from . import api
|
from . import api
|
||||||
|
|
||||||
from .article import ArticleListView
|
from .article import ArticleDetailView, ArticleListView
|
||||||
from .base import BaseView
|
from .base import BaseView
|
||||||
from .episode import EpisodeDetailView, EpisodeListView, TimetableView
|
from .episode import EpisodeDetailView, EpisodeListView, TimetableView
|
||||||
from .log import LogListView
|
from .log import LogListView
|
||||||
|
|
|
@ -1,16 +1,36 @@
|
||||||
from ..models import Article
|
from ..models import Article, Program
|
||||||
from .program import ProgramPageListView
|
from .page import ParentMixin, PageDetailView, PageListView
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ArticleListView']
|
__all__ = ['ArticleDetailView', 'ArticleListView']
|
||||||
|
|
||||||
|
|
||||||
class ArticleListView(ProgramPageListView):
|
class ArticleDetailView(PageDetailView):
|
||||||
|
show_side_nav = True
|
||||||
|
model = Article
|
||||||
|
|
||||||
|
def get_side_queryset(self):
|
||||||
|
qs = Article.objects.select_related('cover') \
|
||||||
|
.filter(is_static=False) \
|
||||||
|
.order_by('-date')
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
if self.object.program is not None:
|
||||||
|
kwargs.setdefault('parent', self.object.program)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleListView(ParentMixin, PageListView):
|
||||||
model = Article
|
model = Article
|
||||||
template_name = 'aircox/article_list.html'
|
template_name = 'aircox/article_list.html'
|
||||||
show_headline = True
|
show_headline = True
|
||||||
is_static = False
|
is_static = False
|
||||||
|
|
||||||
def get_queryset(self):
|
parent_model = Program
|
||||||
return super().get_queryset(is_static=self.is_static)
|
fk_parent = 'program'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(is_static=self.is_static)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,17 +6,20 @@ from django.views.generic.base import TemplateResponseMixin, ContextMixin
|
||||||
from ..utils import Redirect
|
from ..utils import Redirect
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['BaseView', 'PageView']
|
__all__ = ['BaseView']
|
||||||
|
|
||||||
|
|
||||||
class BaseView(TemplateResponseMixin, ContextMixin):
|
class BaseView(TemplateResponseMixin, ContextMixin):
|
||||||
show_side_nav = False
|
|
||||||
""" Show side navigation """
|
|
||||||
title = None
|
title = None
|
||||||
""" Page title """
|
""" Page title """
|
||||||
cover = None
|
cover = None
|
||||||
""" Page cover """
|
""" Page cover """
|
||||||
|
|
||||||
|
show_side_nav = False
|
||||||
|
""" Show side navigation """
|
||||||
|
list_count = 5
|
||||||
|
""" Item count for small lists displayed on page. """
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def station(self):
|
def station(self):
|
||||||
return self.request.station
|
return self.request.station
|
||||||
|
@ -24,14 +27,24 @@ class BaseView(TemplateResponseMixin, ContextMixin):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().station(self.station)
|
return super().get_queryset().station(self.station)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_side_queryset(self):
|
||||||
|
""" Return a queryset of items to render on the side nav. """
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_context_data(self, side_items=None, **kwargs):
|
||||||
kwargs.setdefault('station', self.station)
|
kwargs.setdefault('station', self.station)
|
||||||
kwargs.setdefault('cover', self.cover)
|
kwargs.setdefault('cover', self.cover)
|
||||||
kwargs.setdefault('show_side_nav', self.show_side_nav)
|
|
||||||
|
show_side_nav = kwargs.setdefault('show_side_nav', self.show_side_nav)
|
||||||
|
if show_side_nav and side_items is None:
|
||||||
|
side_items = self.get_side_queryset()
|
||||||
|
side_items = None if side_items is None else \
|
||||||
|
side_items[:self.list_count]
|
||||||
|
|
||||||
if not 'audio_streams' in kwargs:
|
if not 'audio_streams' in kwargs:
|
||||||
streams = self.station.audio_streams
|
streams = self.station.audio_streams
|
||||||
streams = streams and streams.split('\n')
|
streams = streams and streams.split('\n')
|
||||||
kwargs['audio_streams'] = streams
|
kwargs['audio_streams'] = streams
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
|
return super().get_context_data(side_items=side_items, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.db.models import OuterRef, Subquery
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
|
|
||||||
from ..models import Diffusion, Episode, Page, Program, Sound
|
from ..converters import WeekConverter
|
||||||
|
from ..models import Diffusion, Episode, Program, Sound
|
||||||
from .base import BaseView
|
from .base import BaseView
|
||||||
from .program import ProgramPageDetailView, ProgramPageListView
|
from .program import ProgramPageDetailView
|
||||||
|
from .page import ParentMixin, PageListView
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['EpisodeDetailView', 'DiffusionListView', 'TimetableView']
|
__all__ = ['EpisodeDetailView', 'EpisodeListView', 'TimetableView']
|
||||||
|
|
||||||
|
|
||||||
class EpisodeDetailView(ProgramPageDetailView):
|
class EpisodeDetailView(ProgramPageDetailView):
|
||||||
|
@ -20,8 +20,9 @@ class EpisodeDetailView(ProgramPageDetailView):
|
||||||
return Sound.objects.diffusion(diffusion).podcasts()
|
return Sound.objects.diffusion(diffusion).podcasts()
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs.setdefault('program', self.object.program)
|
self.program = kwargs.setdefault('program', self.object.program)
|
||||||
kwargs.setdefault('parent', kwargs['program'])
|
|
||||||
|
kwargs.setdefault('parent', self.program)
|
||||||
if not 'tracks' in kwargs:
|
if not 'tracks' in kwargs:
|
||||||
kwargs['tracks'] = self.object.track_set.order_by('position')
|
kwargs['tracks'] = self.object.track_set.order_by('position')
|
||||||
if not 'podcasts' in kwargs:
|
if not 'podcasts' in kwargs:
|
||||||
|
@ -29,12 +30,15 @@ class EpisodeDetailView(ProgramPageDetailView):
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class EpisodeListView(ProgramPageListView):
|
class EpisodeListView(ParentMixin, PageListView):
|
||||||
model = Episode
|
model = Episode
|
||||||
template_name = 'aircox/diffusion_list.html'
|
template_name = 'aircox/diffusion_list.html'
|
||||||
item_template_name = 'aircox/episode_item.html'
|
item_template_name = 'aircox/episode_item.html'
|
||||||
show_headline = True
|
show_headline = True
|
||||||
|
|
||||||
|
parent_model = Program
|
||||||
|
fk_parent = 'program'
|
||||||
|
|
||||||
|
|
||||||
class TimetableView(BaseView, ListView):
|
class TimetableView(BaseView, ListView):
|
||||||
""" View for timetables """
|
""" View for timetables """
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
|
||||||
from django.core.exceptions import FieldDoesNotExist
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django.views.generic import DetailView, ListView
|
from django.views.generic import DetailView, ListView
|
||||||
|
|
||||||
from ..models import Category
|
from ..models import Category
|
||||||
|
@ -8,41 +9,48 @@ from ..utils import Redirect
|
||||||
from .base import BaseView
|
from .base import BaseView
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['PageDetailView', 'PageListView']
|
__all__ = ['ParentMixin', 'PageDetailView', 'PageListView']
|
||||||
|
|
||||||
|
|
||||||
class PageDetailView(BaseView, DetailView):
|
class ParentMixin:
|
||||||
""" Base view class for pages. """
|
"""
|
||||||
context_object_name = 'page'
|
Optional parent page for a list view. Parent is fetched and passed to the
|
||||||
|
template context when `parent_model` is provided (queryset is filtered by
|
||||||
|
parent page in such case).
|
||||||
|
"""
|
||||||
|
parent_model = None
|
||||||
|
""" Parent model """
|
||||||
|
parent_url_kwarg = 'parent_slug'
|
||||||
|
""" Url lookup argument """
|
||||||
|
parent_field = 'slug'
|
||||||
|
""" Parent field for url lookup """
|
||||||
|
fk_parent = 'page'
|
||||||
|
""" Page foreign key to the parent """
|
||||||
|
parent = None
|
||||||
|
""" Parent page object """
|
||||||
|
|
||||||
|
def get_parent(self, request, *args, **kwargs):
|
||||||
|
if self.parent_model is None or self.parent_url_kwarg not in kwargs:
|
||||||
|
return
|
||||||
|
|
||||||
|
lookup = {self.parent_field: kwargs[self.parent_url_kwarg]}
|
||||||
|
return get_object_or_404(
|
||||||
|
self.parent_model.objects.select_related('cover'), **lookup)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.parent = self.get_parent(request, *args, **kwargs)
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().select_related('cover', 'category')
|
if self.parent is not None:
|
||||||
|
lookup = {self.fk_parent: self.parent}
|
||||||
# This should not exists: it allows mapping not published pages
|
return super().get_queryset().filter(**lookup)
|
||||||
# or it should be only used for trashed pages.
|
return super().get_queryset()
|
||||||
def not_published_redirect(self, page):
|
|
||||||
"""
|
|
||||||
When a page is not published, redirect to the returned url instead
|
|
||||||
of an HTTP 404 code. """
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
obj = super().get_object()
|
|
||||||
if not obj.is_published:
|
|
||||||
redirect_url = self.not_published_redirect(obj)
|
|
||||||
if redirect_url:
|
|
||||||
raise Redirect(redirect_url)
|
|
||||||
raise Http404('%s not found' % self.model._meta.verbose_name)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
#if kwargs.get('regions') is None:
|
parent = kwargs.setdefault('parent', self.parent)
|
||||||
# contents = contents_for_item(
|
if parent is not None:
|
||||||
# page, page_renderer._renderers.keys())
|
kwargs.setdefault('cover', parent.cover)
|
||||||
# kwargs['regions'] = contents.render_regions(page_renderer)
|
|
||||||
page = kwargs.setdefault('page', self.object)
|
|
||||||
kwargs.setdefault('title', page.title)
|
|
||||||
kwargs.setdefault('cover', page.cover)
|
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,4 +92,35 @@ class PageListView(BaseView, ListView):
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class PageDetailView(BaseView, DetailView):
|
||||||
|
""" Base view class for pages. """
|
||||||
|
context_object_name = 'page'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().select_related('cover', 'category')
|
||||||
|
|
||||||
|
# This should not exists: it allows mapping not published pages
|
||||||
|
# or it should be only used for trashed pages.
|
||||||
|
def not_published_redirect(self, page):
|
||||||
|
"""
|
||||||
|
When a page is not published, redirect to the returned url instead
|
||||||
|
of an HTTP 404 code. """
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
obj = super().get_object()
|
||||||
|
if not obj.is_published:
|
||||||
|
redirect_url = self.not_published_redirect(obj)
|
||||||
|
if redirect_url:
|
||||||
|
raise Redirect(redirect_url)
|
||||||
|
raise Http404('%s not found' % self.model._meta.verbose_name)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
page = kwargs.setdefault('page', self.object)
|
||||||
|
kwargs.setdefault('title', page.title)
|
||||||
|
kwargs.setdefault('cover', page.cover)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,53 +12,25 @@ class ProgramPageDetailView(PageDetailView):
|
||||||
"""
|
"""
|
||||||
Base view class for a page that is displayed as a program's child page.
|
Base view class for a page that is displayed as a program's child page.
|
||||||
"""
|
"""
|
||||||
|
program = None
|
||||||
show_side_nav = True
|
show_side_nav = True
|
||||||
list_count = 5
|
list_count = 5
|
||||||
|
|
||||||
def get_episodes_queryset(self, program):
|
def get_side_queryset(self):
|
||||||
return program.episode_set.published().order_by('-date')
|
return self.program.episode_set.published().order_by('-date')
|
||||||
|
|
||||||
def get_context_data(self, program, episodes=None, **kwargs):
|
|
||||||
if episodes is None:
|
|
||||||
episodes = self.get_episodes_queryset(program)
|
|
||||||
return super().get_context_data(
|
|
||||||
program=program, episodes=episodes[:self.list_count], **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ProgramPageListView(PageListView):
|
|
||||||
"""
|
|
||||||
Base list view class rendering pages as a program's child page.
|
|
||||||
Retrieved program from it slug provided by `kwargs['program_slug']`.
|
|
||||||
|
|
||||||
This view class can be used with or without providing a program.
|
|
||||||
"""
|
|
||||||
program = None
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
slug = kwargs.get('program_slug', None)
|
|
||||||
if slug is not None:
|
|
||||||
self.program = get_object_or_404(
|
|
||||||
Program.objects.select_related('cover'), slug=slug)
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return super().get_queryset().filter(program=self.program) \
|
|
||||||
if self.program else super().get_queryset()
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
program = kwargs.setdefault('program', self.program)
|
|
||||||
if program is not None:
|
|
||||||
kwargs.setdefault('cover', program.cover)
|
|
||||||
kwargs.setdefault('parent', program)
|
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ProgramDetailView(ProgramPageDetailView):
|
class ProgramDetailView(ProgramPageDetailView):
|
||||||
model = Program
|
model = Program
|
||||||
|
|
||||||
def get_articles_queryset(self, program):
|
def get_articles_queryset(self):
|
||||||
return program.article_set.published().order_by('-date')
|
return self.program.article_set.published().order_by('-date')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs.setdefault('program', self.object)
|
self.program = kwargs.setdefault('program', self.object)
|
||||||
|
if 'articles' not in kwargs:
|
||||||
|
kwargs['articles'] = \
|
||||||
|
self.get_articles_queryset()[:self.list_count]
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
import copy
|
|
||||||
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from content_editor.admin import ContentEditor, ContentEditorInline
|
|
||||||
|
|
||||||
from aircox import models as aircox
|
|
||||||
from . import models
|
|
||||||
from aircox.admin.playlist import TracksInline
|
|
||||||
from aircox.admin.mixins import UnrelatedInlineMixin
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Site)
|
|
||||||
class SiteAdmin(ContentEditor):
|
|
||||||
list_display = ['title', 'station']
|
|
||||||
|
|
||||||
inlines = [
|
|
||||||
ContentEditorInline.create(models.SiteRichText),
|
|
||||||
ContentEditorInline.create(models.SiteImage),
|
|
||||||
ContentEditorInline.create(models.SiteLink),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
|
||||||
fieldsets = (
|
|
||||||
(_('Main'), {
|
|
||||||
'fields': ['title', 'slug', 'cover', 'headline'],
|
|
||||||
'classes': ('tabbed', 'uncollapse')
|
|
||||||
}),
|
|
||||||
(_('Settings'), {
|
|
||||||
'fields': ['featured', 'as_program', 'allow_comments', 'status'],
|
|
||||||
'classes': ('tabbed',)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
list_display = ["title", "status", "slug"]
|
|
||||||
list_editable = ['status']
|
|
||||||
prepopulated_fields = {"slug": ("title",)}
|
|
||||||
|
|
||||||
inlines = [
|
|
||||||
ContentEditorInline.create(models.PageRichText),
|
|
||||||
ContentEditorInline.create(models.PageImage),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.DiffusionPage)
|
|
||||||
class DiffusionPageAdmin(PageAdmin):
|
|
||||||
fieldsets = copy.deepcopy(PageAdmin.fieldsets)
|
|
||||||
fieldsets[1][1]['fields'].insert(0, 'diffusion')
|
|
||||||
|
|
||||||
# TODO: permissions
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.ProgramPage)
|
|
||||||
class ProgramPageAdmin(PageAdmin):
|
|
||||||
fieldsets = copy.deepcopy(PageAdmin.fieldsets)
|
|
||||||
fieldsets[1][1]['fields'].insert(0, 'program')
|
|
||||||
prepopulated_fields = {}
|
|
||||||
readonly_fields = ['slug']
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class AircoxWebConfig(AppConfig):
|
|
||||||
name = 'aircox_web'
|
|
|
@ -1,5 +0,0 @@
|
||||||
import './js';
|
|
||||||
import './styles.scss';
|
|
||||||
import './noscript.scss';
|
|
||||||
import './vue';
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import Vue from 'vue';
|
|
||||||
import Buefy from 'buefy';
|
|
||||||
|
|
||||||
Vue.use(Buefy);
|
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
var app = new Vue({
|
|
||||||
el: '#app',
|
|
||||||
delimiters: [ '[[', ']]' ],
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
@charset "utf-8";
|
|
||||||
@import "~bulma/sass/utilities/_all.sass";
|
|
||||||
|
|
||||||
$body-background-color: $light;
|
|
||||||
|
|
||||||
@import "~bulma/bulma";
|
|
||||||
|
|
||||||
.navbar {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar.has-shadow {
|
|
||||||
box-shadow: 0em 0.05em 0.5em rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
|
|
||||||
.navbar-brand img {
|
|
||||||
min-height: 6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-menu .navbar-item:not(:last-child) {
|
|
||||||
border-right: 1px $grey solid;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/** page **/
|
|
||||||
.page {
|
|
||||||
& > .cover {
|
|
||||||
float: right;
|
|
||||||
max-width: 45%;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .header {
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.headline {
|
|
||||||
font-size: 1.4em;
|
|
||||||
padding: 0.2em 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
padding: 0.4em 0em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.cover {
|
|
||||||
margin: 1em 0em;
|
|
||||||
border: 0.2em black solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-cover {
|
|
||||||
width: 10em;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside {
|
|
||||||
.small-cover {
|
|
||||||
width: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media .subtitle {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media .content {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
import Tab from './tab.vue';
|
|
||||||
import Tabs from './tabs.vue';
|
|
||||||
|
|
||||||
Vue.component('a-tab', Tab);
|
|
||||||
Vue.component('a-tabs', Tabs);
|
|
||||||
|
|
||||||
export {Tab, Tabs};
|
|
||||||
|
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
<template>
|
|
||||||
<li @click.prevent="onclick"
|
|
||||||
:class="{'is-active': $parent.value == value}">
|
|
||||||
<slot></slot>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
value: { default: undefined },
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
select() {
|
|
||||||
this.$parent.selectTab(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
onclick(event) {
|
|
||||||
this.select();
|
|
||||||
/*if(event.target.href != document.location)
|
|
||||||
window.history.pushState(
|
|
||||||
{ url: event.target.href },
|
|
||||||
event.target.innerText + ' - ' + document.title,
|
|
||||||
event.target.href
|
|
||||||
) */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="tabs is-centered">
|
|
||||||
<ul><slot name="tabs" :value="value" /></ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<slot :value="value"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
default: { default: null },
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
value: this.default,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
tab() {
|
|
||||||
const vnode = this.$slots.default && this.$slots.default.find(
|
|
||||||
elm => elm.child && elm.child.value == this.value
|
|
||||||
);
|
|
||||||
return vnode && vnode.child;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
selectTab(tab) {
|
|
||||||
const value = tab.value;
|
|
||||||
if(this.value === value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.value = value;
|
|
||||||
this.$emit('select', {target: this, value: value, tab: tab});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMinMaxField:
|
|
||||||
def __init__(self, verbose_name=None, name=None, min=None, max=None,
|
|
||||||
**kwargs):
|
|
||||||
super().__init__(verbose_name, name, **kwargs)
|
|
||||||
self.min_value = min
|
|
||||||
self.max_value = max
|
|
||||||
|
|
||||||
def minmax(self, value):
|
|
||||||
return min(self.max_value, max(self.min_value, value))
|
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
return self.minmax(super().to_python(value))
|
|
||||||
|
|
||||||
def get_prep_value(self, value):
|
|
||||||
return super().get_prep_value(self.minmax(value))
|
|
||||||
|
|
||||||
|
|
||||||
class MinMaxField(BaseMinMaxField, models.IntegerField):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class SmallMinMaxField(BaseMinMaxField, models.SmallIntegerField):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class PositiveMinMaxField(BaseMinMaxField, models.PositiveIntegerField):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class PositiveSmallMinMaxField(BaseMinMaxField, models.PositiveSmallIntegerField):
|
|
||||||
pass
|
|
||||||
|
|
|
@ -1,214 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.db.models import F, Q
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from content_editor.models import Region, create_plugin_base
|
|
||||||
|
|
||||||
from model_utils.models import TimeStampedModel, StatusModel
|
|
||||||
from model_utils.managers import InheritanceQuerySet
|
|
||||||
from model_utils import Choices
|
|
||||||
from filer.fields.image import FilerImageField
|
|
||||||
|
|
||||||
from aircox import models as aircox
|
|
||||||
from . import plugins
|
|
||||||
|
|
||||||
|
|
||||||
class Site(models.Model):
|
|
||||||
station = models.ForeignKey(
|
|
||||||
aircox.Station, on_delete=models.SET_NULL, null=True,
|
|
||||||
)
|
|
||||||
#hosts = models.TextField(
|
|
||||||
# _('hosts'),
|
|
||||||
# help_text=_('website addresses (one per line)'),
|
|
||||||
#)
|
|
||||||
|
|
||||||
# main settings
|
|
||||||
title = models.CharField(
|
|
||||||
_('title'), max_length=32,
|
|
||||||
help_text=_('website title displayed to users'),
|
|
||||||
)
|
|
||||||
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='+',
|
|
||||||
)
|
|
||||||
default = models.BooleanField(_('is default'),
|
|
||||||
default=False,
|
|
||||||
help_text=_('use this website by default'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# meta descriptors
|
|
||||||
description = models.CharField(
|
|
||||||
_('description'), max_length=128,
|
|
||||||
blank=True, null=True,
|
|
||||||
)
|
|
||||||
tags = models.CharField(
|
|
||||||
_('tags'), max_length=128,
|
|
||||||
blank=True, null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
regions = [
|
|
||||||
Region(key='topnav', title=_('Navigation'), inherited=True),
|
|
||||||
Region(key='sidenav', title=_('Side Navigation'), inherited=True),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
SitePlugin = create_plugin_base(Site)
|
|
||||||
|
|
||||||
class SiteRichText(plugins.RichText, SitePlugin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class SiteImage(plugins.Image, SitePlugin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class SiteLink(plugins.Link, SitePlugin):
|
|
||||||
css_class="navbar-item"
|
|
||||||
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------
|
|
||||||
class PageQueryset(InheritanceQuerySet):
|
|
||||||
def live(self):
|
|
||||||
return self.filter(status=Page.STATUS.published)
|
|
||||||
|
|
||||||
def descendants(self, page, direct=True, inclusive=True):
|
|
||||||
qs = self.filter(parent=page) if direct else \
|
|
||||||
self.filter(path__startswith=page.path)
|
|
||||||
if not inclusive:
|
|
||||||
qs = qs.exclude(pk=page.pk)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
def ancestors(self, page, inclusive=True):
|
|
||||||
path, paths = page.path, []
|
|
||||||
index = path.find('/')
|
|
||||||
while index != -1 and index+1 < len(path):
|
|
||||||
paths.append(path[0:index+1])
|
|
||||||
index = path.find('/', index+1)
|
|
||||||
return self.filter(path__in=paths)
|
|
||||||
|
|
||||||
|
|
||||||
class Page(StatusModel):
|
|
||||||
"""
|
|
||||||
Base class for views whose url path can be defined by users.
|
|
||||||
Page parenting is based on foreignkey to parent and page path.
|
|
||||||
"""
|
|
||||||
STATUS = Choices('draft', 'published', 'trash')
|
|
||||||
regions = [
|
|
||||||
Region(key="content", title=_("Content")),
|
|
||||||
]
|
|
||||||
|
|
||||||
title = models.CharField(max_length=128)
|
|
||||||
slug = models.SlugField(_('slug'), blank=True, unique=True)
|
|
||||||
headline = models.TextField(
|
|
||||||
_('headline'), max_length=128, blank=True, null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# content
|
|
||||||
as_program = models.ForeignKey(
|
|
||||||
aircox.Program, models.SET_NULL, blank=True, null=True,
|
|
||||||
related_name='+',
|
|
||||||
# SO#51948640
|
|
||||||
# limit_choices_to={'schedule__isnull': False},
|
|
||||||
verbose_name=_('Show program as author'),
|
|
||||||
help_text=_("Show program as author"),
|
|
||||||
)
|
|
||||||
cover = FilerImageField(
|
|
||||||
on_delete=models.SET_NULL, null=True, blank=True,
|
|
||||||
verbose_name=_('Cover'),
|
|
||||||
related_name='+',
|
|
||||||
)
|
|
||||||
|
|
||||||
# options
|
|
||||||
featured = models.BooleanField(
|
|
||||||
_('featured'), default=False,
|
|
||||||
)
|
|
||||||
allow_comments = models.BooleanField(
|
|
||||||
_('allow comments'), default=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
objects = PageQueryset.as_manager()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_published(self):
|
|
||||||
return self.status == self.STATUS.published
|
|
||||||
|
|
||||||
@property
|
|
||||||
def path(self):
|
|
||||||
return reverse(self.detail_url_name, kwargs={'slug': self.slug})
|
|
||||||
|
|
||||||
def get_view_class(self):
|
|
||||||
""" Page view class"""
|
|
||||||
raise NotImplementedError('not implemented')
|
|
||||||
|
|
||||||
def view(self, request, *args, site=None, **kwargs):
|
|
||||||
""" Page view function """
|
|
||||||
view = self.get_view_class().as_view(site=site, page=self)
|
|
||||||
return view(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '{}: {}'.format(self._meta.verbose_name,
|
|
||||||
self.title or self.pk)
|
|
||||||
|
|
||||||
|
|
||||||
class DiffusionPage(Page):
|
|
||||||
detail_url_name = 'diffusion-page'
|
|
||||||
|
|
||||||
diffusion = models.OneToOneField(
|
|
||||||
aircox.Diffusion, models.CASCADE,
|
|
||||||
related_name='page',
|
|
||||||
limit_choices_to={'initial__isnull': True}
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def path(self):
|
|
||||||
return reverse('diffusion-page', kwargs={'slug': self.slug})
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
program = self.diffusion.program
|
|
||||||
self.as_program = self.diffusion.program
|
|
||||||
if not self.slug.startswith(program.slug + '-'):
|
|
||||||
self.slug = '{}-{}'.format(program.slug, self.slug)
|
|
||||||
return super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def get_diffusions_with_page(queryset=aircox.Diffusion.objects,
|
|
||||||
status=Page.STATUS.published):
|
|
||||||
return queryset.filter(Q(page__isnull=True) |
|
|
||||||
Q(initial__page__isnull=True),
|
|
||||||
Q(page__status=status) |
|
|
||||||
Q(initial__page__status=status))
|
|
||||||
|
|
||||||
|
|
||||||
class ProgramPage(Page):
|
|
||||||
detail_url_name = 'program-page'
|
|
||||||
|
|
||||||
program = models.OneToOneField(
|
|
||||||
aircox.Program, models.CASCADE,
|
|
||||||
related_name='page',
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def path(self):
|
|
||||||
return reverse('program-page', kwargs={'slug': self.slug})
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
self.slug = self.program.slug
|
|
||||||
return super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------
|
|
||||||
PagePlugin = create_plugin_base(Page)
|
|
||||||
|
|
||||||
class PageRichText(plugins.RichText, PagePlugin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class PageImage(plugins.Image, PagePlugin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
"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",
|
|
||||||
"bulma": "^0.7.5",
|
|
||||||
"css-loader": "^2.1.1",
|
|
||||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
|
||||||
"file-loader": "^3.0.1",
|
|
||||||
"mini-css-extract-plugin": "^0.5.0",
|
|
||||||
"node-sass": "^4.12.0",
|
|
||||||
"sass-loader": "^7.1.0",
|
|
||||||
"style-loader": "^0.23.1",
|
|
||||||
"ttf-loader": "^1.0.2",
|
|
||||||
"vue-loader": "^15.7.0",
|
|
||||||
"vue-style-loader": "^4.1.2",
|
|
||||||
"vue-template-compiler": "^2.6.10",
|
|
||||||
"webpack": "^4.32.2",
|
|
||||||
"webpack-cli": "^3.3.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"buefy": "^0.7.8",
|
|
||||||
"vue": "^2.6.10"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.html import escape, format_html, mark_safe
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from .image import ImageBase, Image
|
|
||||||
from .richtext import RichText
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ImageBase', 'Image', 'RichText']
|
|
||||||
|
|
||||||
|
|
||||||
class Link(models.Model):
|
|
||||||
url = models.CharField(
|
|
||||||
_('url'), max_length=128, null=True, blank=True,
|
|
||||||
)
|
|
||||||
page = models.ForeignKey(
|
|
||||||
'Page', models.SET_NULL, null=True, blank=True,
|
|
||||||
verbose_name=_('Link to a page')
|
|
||||||
)
|
|
||||||
text = models.CharField(_('text'), max_length=64, null=True, blank=True)
|
|
||||||
info = models.CharField(_('info'), max_length=128, null=True, blank=True,
|
|
||||||
help_text=_('link description displayed as tooltip'))
|
|
||||||
blank = models.BooleanField(_('new window'), default=False,
|
|
||||||
help_text=_('open in a new window'))
|
|
||||||
css_class=""
|
|
||||||
|
|
||||||
def get_url(self):
|
|
||||||
if self.page:
|
|
||||||
return self.page.path #reverse('page', args=[self.page.path])
|
|
||||||
return self.url or ''
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
# FIXME: quote
|
|
||||||
return format_html(
|
|
||||||
'<a href="{}" title="{}"{}>{}</a>',
|
|
||||||
self.get_url(), escape(self.info),
|
|
||||||
' class=' + escape(self.css_class) + ''
|
|
||||||
if self.css_class else '',
|
|
||||||
self.text or (self.page and self.page.title) or '',
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class Search(models.Model):
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.templatetags.static import static
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.html import format_html, mark_safe
|
|
||||||
|
|
||||||
from easy_thumbnails.files import get_thumbnailer
|
|
||||||
from filer.fields.image import FilerImageField
|
|
||||||
|
|
||||||
__all__ = ['ImageBase', 'Image']
|
|
||||||
|
|
||||||
|
|
||||||
class ImageBase(models.Model):
|
|
||||||
image = FilerImageField(
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_('image'),
|
|
||||||
)
|
|
||||||
width = None
|
|
||||||
height = None
|
|
||||||
crop = False
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def thumbnail(self):
|
|
||||||
if self.width == None and self.height == None:
|
|
||||||
return self.image
|
|
||||||
opts = {}
|
|
||||||
if self.crop:
|
|
||||||
opts['crop'] = 'smart'
|
|
||||||
opts['size'] = (self.width or 0, self.height or 0)
|
|
||||||
thumbnailer = get_thumbnailer(self.image)
|
|
||||||
return thumbnailer.get_thumbnail(opts)
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
return format_html('<img src="{}" alt=""/>', self.thumbnail.url)
|
|
||||||
|
|
||||||
|
|
||||||
class Image(ImageBase):
|
|
||||||
width = models.PositiveSmallIntegerField(blank=True,null=True)
|
|
||||||
height = models.PositiveSmallIntegerField(blank=True,null=True)
|
|
||||||
crop = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from ckeditor.fields import RichTextField
|
|
||||||
|
|
||||||
|
|
||||||
class RichText(models.Model):
|
|
||||||
text = RichTextField(_('text'))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
import datetime
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.templatetags.static import static
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from aircox import models as aircox
|
|
||||||
from aircox_web.fields import PositiveSmallMinMaxField
|
|
||||||
|
|
||||||
|
|
||||||
class Timetable(models.Model):
|
|
||||||
station = models.ForeignKey(
|
|
||||||
aircox.Station, models.CASCADE, verbose_name=_('station'),
|
|
||||||
)
|
|
||||||
days_before = models.PositiveSmallMinMaxField(
|
|
||||||
_('days before'), min=0, max=6,
|
|
||||||
help_text=_('Count of days displayed current date'),
|
|
||||||
)
|
|
||||||
days_after = models.PositiveSmallMinMaxField(
|
|
||||||
_('days after'), min=0, max=6,
|
|
||||||
help_text=_('Count of days displayed current date'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_queryset(self, date=None):
|
|
||||||
date = date if date is not None else datetime.date.today()
|
|
||||||
qs = aircox.Diffusion.objects.station(self.station)
|
|
||||||
if self.days_before is None and self.days_after is None:
|
|
||||||
return qs.at(date)
|
|
||||||
|
|
||||||
start = date - datetime.timedelta(days=self.days_before) \
|
|
||||||
if self.days_before else date
|
|
||||||
stop = date + datetime.timedelta(days=self.days_after) \
|
|
||||||
if self.days_after else date
|
|
||||||
return aircox.Diffusion.objects.station(self.station) \
|
|
||||||
.after(start).before(stop)
|
|
||||||
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
from django.utils.html import format_html, mark_safe
|
|
||||||
from content_editor.renderer import PluginRenderer
|
|
||||||
|
|
||||||
from .models import *
|
|
||||||
|
|
||||||
|
|
||||||
site_renderer = PluginRenderer()
|
|
||||||
site_renderer._renderers.clear()
|
|
||||||
site_renderer.register(SiteRichText, lambda plugin: mark_safe(plugin.text))
|
|
||||||
site_renderer.register(SiteImage, lambda plugin: plugin.render())
|
|
||||||
site_renderer.register(SiteLink, lambda plugin: plugin.render())
|
|
||||||
|
|
||||||
|
|
||||||
page_renderer = PluginRenderer()
|
|
||||||
page_renderer._renderers.clear()
|
|
||||||
page_renderer.register(PageRichText, lambda plugin: mark_safe(plugin.text))
|
|
||||||
page_renderer.register(PageImage, lambda plugin: plugin.render())
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,347 +0,0 @@
|
||||||
/******/ (function(modules) { // webpackBootstrap
|
|
||||||
/******/ // install a JSONP callback for chunk loading
|
|
||||||
/******/ function webpackJsonpCallback(data) {
|
|
||||||
/******/ var chunkIds = data[0];
|
|
||||||
/******/ var moreModules = data[1];
|
|
||||||
/******/ var executeModules = data[2];
|
|
||||||
/******/
|
|
||||||
/******/ // add "moreModules" to the modules object,
|
|
||||||
/******/ // then flag all "chunkIds" as loaded and fire callback
|
|
||||||
/******/ var moduleId, chunkId, i = 0, resolves = [];
|
|
||||||
/******/ for(;i < chunkIds.length; i++) {
|
|
||||||
/******/ chunkId = chunkIds[i];
|
|
||||||
/******/ if(installedChunks[chunkId]) {
|
|
||||||
/******/ resolves.push(installedChunks[chunkId][0]);
|
|
||||||
/******/ }
|
|
||||||
/******/ installedChunks[chunkId] = 0;
|
|
||||||
/******/ }
|
|
||||||
/******/ for(moduleId in moreModules) {
|
|
||||||
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
|
|
||||||
/******/ modules[moduleId] = moreModules[moduleId];
|
|
||||||
/******/ }
|
|
||||||
/******/ }
|
|
||||||
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
|
|
||||||
/******/
|
|
||||||
/******/ while(resolves.length) {
|
|
||||||
/******/ resolves.shift()();
|
|
||||||
/******/ }
|
|
||||||
/******/
|
|
||||||
/******/ // add entry modules from loaded chunk to deferred list
|
|
||||||
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
|
|
||||||
/******/
|
|
||||||
/******/ // run deferred modules when all chunks ready
|
|
||||||
/******/ return checkDeferredModules();
|
|
||||||
/******/ };
|
|
||||||
/******/ function checkDeferredModules() {
|
|
||||||
/******/ var result;
|
|
||||||
/******/ for(var i = 0; i < deferredModules.length; i++) {
|
|
||||||
/******/ var deferredModule = deferredModules[i];
|
|
||||||
/******/ var fulfilled = true;
|
|
||||||
/******/ for(var j = 1; j < deferredModule.length; j++) {
|
|
||||||
/******/ var depId = deferredModule[j];
|
|
||||||
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
|
|
||||||
/******/ }
|
|
||||||
/******/ if(fulfilled) {
|
|
||||||
/******/ deferredModules.splice(i--, 1);
|
|
||||||
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
|
|
||||||
/******/ }
|
|
||||||
/******/ }
|
|
||||||
/******/
|
|
||||||
/******/ return result;
|
|
||||||
/******/ }
|
|
||||||
/******/
|
|
||||||
/******/ // The module cache
|
|
||||||
/******/ var installedModules = {};
|
|
||||||
/******/
|
|
||||||
/******/ // object to store loaded and loading chunks
|
|
||||||
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
|
|
||||||
/******/ // Promise = chunk loading, 0 = chunk loaded
|
|
||||||
/******/ var installedChunks = {
|
|
||||||
/******/ "main": 0
|
|
||||||
/******/ };
|
|
||||||
/******/
|
|
||||||
/******/ var deferredModules = [];
|
|
||||||
/******/
|
|
||||||
/******/ // The require function
|
|
||||||
/******/ function __webpack_require__(moduleId) {
|
|
||||||
/******/
|
|
||||||
/******/ // Check if module is in cache
|
|
||||||
/******/ if(installedModules[moduleId]) {
|
|
||||||
/******/ return installedModules[moduleId].exports;
|
|
||||||
/******/ }
|
|
||||||
/******/ // Create a new module (and put it into the cache)
|
|
||||||
/******/ var module = installedModules[moduleId] = {
|
|
||||||
/******/ i: moduleId,
|
|
||||||
/******/ l: false,
|
|
||||||
/******/ exports: {}
|
|
||||||
/******/ };
|
|
||||||
/******/
|
|
||||||
/******/ // Execute the module function
|
|
||||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
||||||
/******/
|
|
||||||
/******/ // Flag the module as loaded
|
|
||||||
/******/ module.l = true;
|
|
||||||
/******/
|
|
||||||
/******/ // Return the exports of the module
|
|
||||||
/******/ return module.exports;
|
|
||||||
/******/ }
|
|
||||||
/******/
|
|
||||||
/******/
|
|
||||||
/******/ // expose the modules object (__webpack_modules__)
|
|
||||||
/******/ __webpack_require__.m = modules;
|
|
||||||
/******/
|
|
||||||
/******/ // expose the module cache
|
|
||||||
/******/ __webpack_require__.c = installedModules;
|
|
||||||
/******/
|
|
||||||
/******/ // define getter function for harmony exports
|
|
||||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
|
||||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
|
||||||
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
|
||||||
/******/ }
|
|
||||||
/******/ };
|
|
||||||
/******/
|
|
||||||
/******/ // define __esModule on exports
|
|
||||||
/******/ __webpack_require__.r = function(exports) {
|
|
||||||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
|
||||||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
||||||
/******/ }
|
|
||||||
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
|
||||||
/******/ };
|
|
||||||
/******/
|
|
||||||
/******/ // create a fake namespace object
|
|
||||||
/******/ // mode & 1: value is a module id, require it
|
|
||||||
/******/ // mode & 2: merge all properties of value into the ns
|
|
||||||
/******/ // mode & 4: return value when already ns object
|
|
||||||
/******/ // mode & 8|1: behave like require
|
|
||||||
/******/ __webpack_require__.t = function(value, mode) {
|
|
||||||
/******/ if(mode & 1) value = __webpack_require__(value);
|
|
||||||
/******/ if(mode & 8) return value;
|
|
||||||
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
|
||||||
/******/ var ns = Object.create(null);
|
|
||||||
/******/ __webpack_require__.r(ns);
|
|
||||||
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
|
||||||
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
|
||||||
/******/ return ns;
|
|
||||||
/******/ };
|
|
||||||
/******/
|
|
||||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
|
||||||
/******/ __webpack_require__.n = function(module) {
|
|
||||||
/******/ var getter = module && module.__esModule ?
|
|
||||||
/******/ function getDefault() { return module['default']; } :
|
|
||||||
/******/ function getModuleExports() { return module; };
|
|
||||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
|
||||||
/******/ return getter;
|
|
||||||
/******/ };
|
|
||||||
/******/
|
|
||||||
/******/ // Object.prototype.hasOwnProperty.call
|
|
||||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
|
||||||
/******/
|
|
||||||
/******/ // __webpack_public_path__
|
|
||||||
/******/ __webpack_require__.p = "";
|
|
||||||
/******/
|
|
||||||
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
|
|
||||||
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
|
|
||||||
/******/ jsonpArray.push = webpackJsonpCallback;
|
|
||||||
/******/ jsonpArray = jsonpArray.slice();
|
|
||||||
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
|
|
||||||
/******/ var parentJsonpFunction = oldJsonpFunction;
|
|
||||||
/******/
|
|
||||||
/******/
|
|
||||||
/******/ // add entry module to deferred list
|
|
||||||
/******/ deferredModules.push(["./assets/index.js","vendor"]);
|
|
||||||
/******/ // run deferred modules when ready
|
|
||||||
/******/ return checkDeferredModules();
|
|
||||||
/******/ })
|
|
||||||
/************************************************************************/
|
|
||||||
/******/ ({
|
|
||||||
|
|
||||||
/***/ "./assets/index.js":
|
|
||||||
/*!*************************!*\
|
|
||||||
!*** ./assets/index.js ***!
|
|
||||||
\*************************/
|
|
||||||
/*! no exports provided */
|
|
||||||
/*! all exports used */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./js */ \"./assets/js/index.js\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./styles.scss */ \"./assets/styles.scss\");\n/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_styles_scss__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _noscript_scss__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./noscript.scss */ \"./assets/noscript.scss\");\n/* harmony import */ var _noscript_scss__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_noscript_scss__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./vue */ \"./assets/vue/index.js\");\n\n\n\n\n\n\n\n//# sourceURL=webpack:///./assets/index.js?");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./assets/js/index.js":
|
|
||||||
/*!****************************!*\
|
|
||||||
!*** ./assets/js/index.js ***!
|
|
||||||
\****************************/
|
|
||||||
/*! no exports provided */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var buefy__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! buefy */ \"./node_modules/buefy/dist/buefy.js\");\n/* harmony import */ var buefy__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(buefy__WEBPACK_IMPORTED_MODULE_1__);\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].use(buefy__WEBPACK_IMPORTED_MODULE_1___default.a);\n\nwindow.addEventListener('load', () => {\n var app = new vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"]({\n el: '#app',\n delimiters: [ '[[', ']]' ],\n })\n});\n\n\n\n\n\n//# sourceURL=webpack:///./assets/js/index.js?");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./assets/noscript.scss":
|
|
||||||
/*!******************************!*\
|
|
||||||
!*** ./assets/noscript.scss ***!
|
|
||||||
\******************************/
|
|
||||||
/*! no static exports found */
|
|
||||||
/***/ (function(module, exports, __webpack_require__) {
|
|
||||||
|
|
||||||
eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./assets/noscript.scss?");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./assets/styles.scss":
|
|
||||||
/*!****************************!*\
|
|
||||||
!*** ./assets/styles.scss ***!
|
|
||||||
\****************************/
|
|
||||||
/*! no static exports found */
|
|
||||||
/***/ (function(module, exports, __webpack_require__) {
|
|
||||||
|
|
||||||
eval("// extracted by mini-css-extract-plugin\n\n//# sourceURL=webpack:///./assets/styles.scss?");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./assets/vue/index.js":
|
|
||||||
/*!*****************************!*\
|
|
||||||
!*** ./assets/vue/index.js ***!
|
|
||||||
\*****************************/
|
|
||||||
/*! exports provided: Tab, Tabs */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm.browser.js\");\n/* harmony import */ var _tab_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./tab.vue */ \"./assets/vue/tab.vue\");\n/* harmony import */ var _tabs_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./tabs.vue */ \"./assets/vue/tabs.vue\");\n\n\n\n\n\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-tab', _tab_vue__WEBPACK_IMPORTED_MODULE_1__[/* default */ \"a\"]);\nvue__WEBPACK_IMPORTED_MODULE_0__[\"default\"].component('a-tabs', _tabs_vue__WEBPACK_IMPORTED_MODULE_2__[/* default */ \"a\"]);\n\n\n\n\n\n\n//# sourceURL=webpack:///./assets/vue/index.js?");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./assets/vue/tab.vue":
|
|
||||||
/*!****************************!*\
|
|
||||||
!*** ./assets/vue/tab.vue ***!
|
|
||||||
\****************************/
|
|
||||||
/*! exports provided: default */
|
|
||||||
/*! exports used: default */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("/* harmony import */ var _tab_vue_vue_type_template_id_65401e0e___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tab.vue?vue&type=template&id=65401e0e& */ \"./assets/vue/tab.vue?vue&type=template&id=65401e0e&\");\n/* harmony import */ var _tab_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./tab.vue?vue&type=script&lang=js& */ \"./assets/vue/tab.vue?vue&type=script&lang=js&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[/* default */ \"a\"])(\n _tab_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[/* default */ \"a\"],\n _tab_vue_vue_type_template_id_65401e0e___WEBPACK_IMPORTED_MODULE_0__[/* render */ \"a\"],\n _tab_vue_vue_type_template_id_65401e0e___WEBPACK_IMPORTED_MODULE_0__[/* staticRenderFns */ \"b\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"assets/vue/tab.vue\"\n/* harmony default export */ __webpack_exports__[\"a\"] = (component.exports);\n\n//# sourceURL=webpack:///./assets/vue/tab.vue?");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./assets/vue/tab.vue?vue&type=script&lang=js&":
|
|
||||||
/*!*****************************************************!*\
|
|
||||||
!*** ./assets/vue/tab.vue?vue&type=script&lang=js& ***!
|
|
||||||
\*****************************************************/
|
|
||||||
/*! exports provided: default */
|
|
||||||
/*! exports used: default */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("/* harmony import */ var _node_modules_vue_loader_lib_index_js_vue_loader_options_tab_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/vue-loader/lib??vue-loader-options!./tab.vue?vue&type=script&lang=js& */ \"./node_modules/vue-loader/lib/index.js?!./assets/vue/tab.vue?vue&type=script&lang=js&\");\n /* harmony default export */ __webpack_exports__[\"a\"] = (_node_modules_vue_loader_lib_index_js_vue_loader_options_tab_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[/* default */ \"a\"]); \n\n//# sourceURL=webpack:///./assets/vue/tab.vue?");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./assets/vue/tab.vue?vue&type=template&id=65401e0e&":
|
|
||||||
/*!***********************************************************!*\
|
|
||||||
!*** ./assets/vue/tab.vue?vue&type=template&id=65401e0e& ***!
|
|
||||||
\***********************************************************/
|
|
||||||
/*! exports provided: render, staticRenderFns */
|
|
||||||
/*! exports used: render, staticRenderFns */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("/* harmony import */ var _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_tab_vue_vue_type_template_id_65401e0e___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../node_modules/vue-loader/lib??vue-loader-options!./tab.vue?vue&type=template&id=65401e0e& */ \"./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./assets/vue/tab.vue?vue&type=template&id=65401e0e&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_tab_vue_vue_type_template_id_65401e0e___WEBPACK_IMPORTED_MODULE_0__[\"a\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"b\", function() { return _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_tab_vue_vue_type_template_id_65401e0e___WEBPACK_IMPORTED_MODULE_0__[\"b\"]; });\n\n\n\n//# sourceURL=webpack:///./assets/vue/tab.vue?");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./assets/vue/tabs.vue":
|
|
||||||
/*!*****************************!*\
|
|
||||||
!*** ./assets/vue/tabs.vue ***!
|
|
||||||
\*****************************/
|
|
||||||
/*! exports provided: default */
|
|
||||||
/*! exports used: default */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("/* harmony import */ var _tabs_vue_vue_type_template_id_466f44d5___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tabs.vue?vue&type=template&id=466f44d5& */ \"./assets/vue/tabs.vue?vue&type=template&id=466f44d5&\");\n/* harmony import */ var _tabs_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./tabs.vue?vue&type=script&lang=js& */ \"./assets/vue/tabs.vue?vue&type=script&lang=js&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__[/* default */ \"a\"])(\n _tabs_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[/* default */ \"a\"],\n _tabs_vue_vue_type_template_id_466f44d5___WEBPACK_IMPORTED_MODULE_0__[/* render */ \"a\"],\n _tabs_vue_vue_type_template_id_466f44d5___WEBPACK_IMPORTED_MODULE_0__[/* staticRenderFns */ \"b\"],\n false,\n null,\n null,\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"assets/vue/tabs.vue\"\n/* harmony default export */ __webpack_exports__[\"a\"] = (component.exports);\n\n//# sourceURL=webpack:///./assets/vue/tabs.vue?");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./assets/vue/tabs.vue?vue&type=script&lang=js&":
|
|
||||||
/*!******************************************************!*\
|
|
||||||
!*** ./assets/vue/tabs.vue?vue&type=script&lang=js& ***!
|
|
||||||
\******************************************************/
|
|
||||||
/*! exports provided: default */
|
|
||||||
/*! exports used: default */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("/* harmony import */ var _node_modules_vue_loader_lib_index_js_vue_loader_options_tabs_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/vue-loader/lib??vue-loader-options!./tabs.vue?vue&type=script&lang=js& */ \"./node_modules/vue-loader/lib/index.js?!./assets/vue/tabs.vue?vue&type=script&lang=js&\");\n /* harmony default export */ __webpack_exports__[\"a\"] = (_node_modules_vue_loader_lib_index_js_vue_loader_options_tabs_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__[/* default */ \"a\"]); \n\n//# sourceURL=webpack:///./assets/vue/tabs.vue?");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./assets/vue/tabs.vue?vue&type=template&id=466f44d5&":
|
|
||||||
/*!************************************************************!*\
|
|
||||||
!*** ./assets/vue/tabs.vue?vue&type=template&id=466f44d5& ***!
|
|
||||||
\************************************************************/
|
|
||||||
/*! exports provided: render, staticRenderFns */
|
|
||||||
/*! exports used: render, staticRenderFns */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("/* harmony import */ var _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_tabs_vue_vue_type_template_id_466f44d5___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../node_modules/vue-loader/lib??vue-loader-options!./tabs.vue?vue&type=template&id=466f44d5& */ \"./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./assets/vue/tabs.vue?vue&type=template&id=466f44d5&\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_tabs_vue_vue_type_template_id_466f44d5___WEBPACK_IMPORTED_MODULE_0__[\"a\"]; });\n\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"b\", function() { return _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_tabs_vue_vue_type_template_id_466f44d5___WEBPACK_IMPORTED_MODULE_0__[\"b\"]; });\n\n\n\n//# sourceURL=webpack:///./assets/vue/tabs.vue?");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./node_modules/vue-loader/lib/index.js?!./assets/vue/tab.vue?vue&type=script&lang=js&":
|
|
||||||
/*!*******************************************************************************************************!*\
|
|
||||||
!*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/tab.vue?vue&type=script&lang=js& ***!
|
|
||||||
\*******************************************************************************************************/
|
|
||||||
/*! exports provided: default */
|
|
||||||
/*! exports used: default */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("//\n//\n//\n//\n//\n//\n//\n\n/* harmony default export */ __webpack_exports__[\"a\"] = ({\n props: {\n value: { default: undefined },\n },\n\n methods: {\n select() {\n this.$parent.selectTab(this);\n },\n\n onclick(event) {\n this.select();\n /*if(event.target.href != document.location)\n window.history.pushState(\n { url: event.target.href },\n event.target.innerText + ' - ' + document.title,\n event.target.href\n ) */\n }\n }\n});\n\n\n//# sourceURL=webpack:///./assets/vue/tab.vue?./node_modules/vue-loader/lib??vue-loader-options");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./node_modules/vue-loader/lib/index.js?!./assets/vue/tabs.vue?vue&type=script&lang=js&":
|
|
||||||
/*!********************************************************************************************************!*\
|
|
||||||
!*** ./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/tabs.vue?vue&type=script&lang=js& ***!
|
|
||||||
\********************************************************************************************************/
|
|
||||||
/*! exports provided: default */
|
|
||||||
/*! exports used: default */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n/* harmony default export */ __webpack_exports__[\"a\"] = ({\n props: {\n default: { default: null },\n },\n\n data() {\n return {\n value: this.default,\n }\n },\n\n computed: {\n tab() {\n const vnode = this.$slots.default && this.$slots.default.find(\n elm => elm.child && elm.child.value == this.value\n );\n return vnode && vnode.child;\n }\n },\n\n methods: {\n selectTab(tab) {\n const value = tab.value;\n if(this.value === value)\n return;\n\n this.value = value;\n this.$emit('select', {target: this, value: value, tab: tab});\n },\n },\n});\n\n\n//# sourceURL=webpack:///./assets/vue/tabs.vue?./node_modules/vue-loader/lib??vue-loader-options");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./assets/vue/tab.vue?vue&type=template&id=65401e0e&":
|
|
||||||
/*!*****************************************************************************************************************************************************************************************!*\
|
|
||||||
!*** ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/tab.vue?vue&type=template&id=65401e0e& ***!
|
|
||||||
\*****************************************************************************************************************************************************************************************/
|
|
||||||
/*! exports provided: render, staticRenderFns */
|
|
||||||
/*! exports used: render, staticRenderFns */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"b\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"li\",\n {\n class: { \"is-active\": _vm.$parent.value == _vm.value },\n on: {\n click: function($event) {\n $event.preventDefault()\n return _vm.onclick($event)\n }\n }\n },\n [_vm._t(\"default\")],\n 2\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/vue/tab.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options");
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./assets/vue/tabs.vue?vue&type=template&id=466f44d5&":
|
|
||||||
/*!******************************************************************************************************************************************************************************************!*\
|
|
||||||
!*** ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./assets/vue/tabs.vue?vue&type=template&id=466f44d5& ***!
|
|
||||||
\******************************************************************************************************************************************************************************************/
|
|
||||||
/*! exports provided: render, staticRenderFns */
|
|
||||||
/*! exports used: render, staticRenderFns */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
eval("/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"a\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"b\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n [\n _c(\"div\", { staticClass: \"tabs is-centered\" }, [\n _c(\"ul\", [_vm._t(\"tabs\", null, { value: _vm.value })], 2)\n ]),\n _vm._v(\" \"),\n _vm._t(\"default\", null, { value: _vm.value })\n ],\n 2\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack:///./assets/vue/tabs.vue?./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options");
|
|
||||||
|
|
||||||
/***/ })
|
|
||||||
|
|
||||||
/******/ });
|
|
File diff suppressed because one or more lines are too long
|
@ -1,79 +0,0 @@
|
||||||
{% load static i18n thumbnail %}
|
|
||||||
{% comment %}
|
|
||||||
Context:
|
|
||||||
- site: current website
|
|
||||||
{% endcomment %}
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="application-name" content="aircox">
|
|
||||||
<meta name="description" content="{{ site.description }}">
|
|
||||||
<meta name="keywords" content="{{ site.tags }}">
|
|
||||||
<link rel="icon" href="{% thumbnail site.favicon 32x32 crop %}" />
|
|
||||||
|
|
||||||
{% block assets %}
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "aircox_web/assets/main.css" %}"/>
|
|
||||||
<script src="{% static "aircox_web/assets/main.js" %}"></script>
|
|
||||||
<script src="{% static "aircox_web/assets/vendor.js" %}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<title>
|
|
||||||
{% block head_title %}{{ site.title }}{% endblock %}
|
|
||||||
</title>
|
|
||||||
|
|
||||||
{% block head_extra %}{% endblock %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app">
|
|
||||||
<nav class="navbar has-shadow" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="container">
|
|
||||||
<div class="navbar-brand">
|
|
||||||
<a href="/" title="{% trans "Home" %}" class="navbar-item">
|
|
||||||
<img src="{{ site.logo.url }}" class="logo"/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-menu">
|
|
||||||
<div class="navbar-start">
|
|
||||||
{{ site_regions.topnav }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="columns is-desktop">
|
|
||||||
<main class="column page">
|
|
||||||
<header class="header">
|
|
||||||
{% block header %}
|
|
||||||
<h1 class="title is-1">{% block title %}{% endblock %}</h1>
|
|
||||||
|
|
||||||
{% if parent %}
|
|
||||||
<h4 class="subtitle is-size-3">
|
|
||||||
<a href="{{ parent.path }}">
|
|
||||||
❬ {{ parent.title }}</a></li>
|
|
||||||
</h4>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{% block main %}{% endblock main %}
|
|
||||||
</main>
|
|
||||||
{% if nav_side %}
|
|
||||||
<aside class="column is-one-third-desktop">
|
|
||||||
{% block cover %}
|
|
||||||
{% if cover is not None %}
|
|
||||||
<img class="cover" src="{{ cover.url }}" class="cover"/>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block side_nav %}
|
|
||||||
{% endblock %}
|
|
||||||
</aside>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
{% load i18n easy_thumbnails_tags aircox_web %}
|
|
||||||
{% comment %}
|
|
||||||
Context variables:
|
|
||||||
- object: the actual diffusion
|
|
||||||
- page: current parent page in which item is rendered
|
|
||||||
- hide_schedule: if True, do not display start time
|
|
||||||
- hide_headline: if True, do not display headline
|
|
||||||
{% endcomment %}
|
|
||||||
|
|
||||||
{% with object.initial|default:object as initial %}
|
|
||||||
{% with initial.program as program %}
|
|
||||||
{% with initial.page as d_page %}
|
|
||||||
{% with program.page as p_page %}
|
|
||||||
{% with d_page|default:p_page as c_page %}
|
|
||||||
<article class="media">
|
|
||||||
<div class="media-left">
|
|
||||||
<img src="{% thumbnail c_page.cover|default:site.logo 128x128 crop=scale %}"
|
|
||||||
class="small-cover">
|
|
||||||
</div>
|
|
||||||
<div class="media-content">
|
|
||||||
<h5 class="subtitle is-size-5">
|
|
||||||
{% if d_page %}
|
|
||||||
<a href="{{ d_page.path }}">{{ d_page.title }}</a>
|
|
||||||
{% endif %}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="">
|
|
||||||
{% if not page or p_page != page %}
|
|
||||||
{% if p_page %}
|
|
||||||
<a href="{{ p_page.path }}" class="has-text-grey-dark">
|
|
||||||
{{ p_page.title }}</a>
|
|
||||||
{% else %}
|
|
||||||
{{ program.name }}
|
|
||||||
{% endif %}
|
|
||||||
{% if not hide_schedule %} — {% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not hide_schedule %}
|
|
||||||
<time datetime="{{ object.start|date:"c" }}" title="{{ object.start }}"
|
|
||||||
class="has-text-weight-light is-size-6">
|
|
||||||
{{ object.start|date:"d M, H:i" }}
|
|
||||||
</time>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if object.initial %}
|
|
||||||
{% with object.initial.date as date %}
|
|
||||||
<span class="tag is-info" title="{% blocktrans %}Rerun of {{ date }}{% endblocktrans %}">
|
|
||||||
{% trans "rerun" %}
|
|
||||||
</span>
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{% if not hide_headline %}
|
|
||||||
<div class="content">
|
|
||||||
{{ c_page.headline }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
{% endwith %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
{% extends "aircox_web/program_base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{{ block.super }}
|
|
||||||
|
|
||||||
{% if podcasts %}
|
|
||||||
{% for object in podcasts %}
|
|
||||||
{% include "aircox_web/podcast_item.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endwith %}
|
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
{% extends "aircox_web/page.html" %}
|
|
||||||
{% load i18n aircox_web %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{% if program %}
|
|
||||||
{% with program.name as program %}
|
|
||||||
{% blocktrans %}Diffusions of {{ program }}{% endblocktrans %}
|
|
||||||
{% endwith %}
|
|
||||||
{% else %}
|
|
||||||
{% trans "All diffusions" %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section>
|
|
||||||
{% for object in object_list %}
|
|
||||||
{% include "aircox_web/diffusion_item.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
{% if is_paginated %}
|
|
||||||
<nav class="pagination is-centered" role="pagination" aria-label="{% trans "pagination" %}">
|
|
||||||
{% if page_obj.has_previous %}
|
|
||||||
<a href="?page={{ page_obj.previous_page_number }}" class="pagination-previous">
|
|
||||||
{% else %}
|
|
||||||
<a class="pagination-previous" disabled>
|
|
||||||
{% endif %}
|
|
||||||
{% trans "Previous" %}</a>
|
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
|
||||||
<a href="?page={{ page_obj.next_page_number }}" class="pagination-next">
|
|
||||||
{% else %}
|
|
||||||
<a class="pagination-next" disabled>
|
|
||||||
{% endif %}
|
|
||||||
{% trans "Next" %}</a>
|
|
||||||
|
|
||||||
<ul class="pagination-list">
|
|
||||||
{% for i in paginator.page_range %}
|
|
||||||
<li>
|
|
||||||
<a class="pagination-link {% if page_obj.number == i %}is-current{% endif %}"
|
|
||||||
href="?page={{ i }}">{{ i }}</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% with object.track as track %}
|
|
||||||
<span class="has-text-info is-size-5">♬</span>
|
|
||||||
<span>{{ track.title }}</span>
|
|
||||||
{% with track.artist as artist %}
|
|
||||||
{% with track.info as info %}
|
|
||||||
<span class="has-text-grey-dark has-text-weight-light">
|
|
||||||
{% blocktrans %}
|
|
||||||
by {{ artist }}
|
|
||||||
{% endblocktrans %}
|
|
||||||
{% if info %}
|
|
||||||
({% blocktrans %}<i>{{ info }}</i>{% endblocktrans %})
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
{% endwith %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
{% extends "aircox_web/page.html" %}
|
|
||||||
{% load i18n aircox_web %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{{ block.super }}
|
|
||||||
|
|
||||||
<section class="section">
|
|
||||||
{% if dates %}
|
|
||||||
<nav class="tabs is-centered" aria-label="{% trans "Other days' logs" %}">
|
|
||||||
<ul>
|
|
||||||
{% for day in dates %}
|
|
||||||
<li {% if day == date %}class="is-active"{% endif %}>
|
|
||||||
<a href="{% url "logs" date=day %}">
|
|
||||||
{{ day|date:"d b" }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
{% if forloop.last and day > min_date %}
|
|
||||||
<li>...</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# <h4 class="subtitle size-4">{{ date }}</h4> #}
|
|
||||||
{% with True as hide_schedule %}
|
|
||||||
<table class="table is-striped is-hoverable is-fullwidth">
|
|
||||||
{% for object in object_list reversed %}
|
|
||||||
<tr>
|
|
||||||
{% if object|is_diffusion %}
|
|
||||||
<td>
|
|
||||||
<time datetime="{{ object.start }}" title="{{ object.start }}">
|
|
||||||
{{ object.start|date:"H:i" }} - {{ object.end|date:"H:i" }}
|
|
||||||
</time>
|
|
||||||
</td>
|
|
||||||
<td>{% include "aircox_web/diffusion_item.html" %}</td>
|
|
||||||
{% else %}
|
|
||||||
<td>
|
|
||||||
<time datetime="{{ object.date }}" title="{{ object.date }}">
|
|
||||||
{{ object.date|date:"H:i" }}
|
|
||||||
</time>
|
|
||||||
</td>
|
|
||||||
<td>{% include "aircox_web/log_item.html" %}</td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% endwith %}
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
{% extends "aircox_web/base.html" %}
|
|
||||||
{% load static i18n thumbnail %}
|
|
||||||
{% comment %}
|
|
||||||
Context:
|
|
||||||
- cover: cover image
|
|
||||||
- title: title
|
|
||||||
- page: page
|
|
||||||
{% endcomment %}
|
|
||||||
|
|
||||||
{% block head_title %}
|
|
||||||
{% block title %}{{ title }}{% endblock %}
|
|
||||||
{% if title %} — {% endif %}
|
|
||||||
{{ site.title }}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% block headline %}
|
|
||||||
{% if page and page.headline %}
|
|
||||||
<p class="headline">{{ page.headline }}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{{ regions.content }}
|
|
||||||
{% endblock %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
<div class="podcast">
|
|
||||||
{% if object.embed %}
|
|
||||||
{{ object.embed }}
|
|
||||||
{% else %}
|
|
||||||
<audio src="{{ object.url }}" controls>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
{% extends "aircox_web/page.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block side_nav %}
|
|
||||||
{{ block.super }}
|
|
||||||
|
|
||||||
{% if diffusions %}
|
|
||||||
<section>
|
|
||||||
<h4 class="subtitle is-size-4">{% trans "Last shows" %}</h4>
|
|
||||||
|
|
||||||
{% for object in diffusions %}
|
|
||||||
{% include "aircox_web/diffusion_item.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<nav class="pagination is-centered">
|
|
||||||
<ul class="pagination-list">
|
|
||||||
<li>
|
|
||||||
<a href="{% url "diffusion-list" program_slug=page.slug %}"
|
|
||||||
class="pagination-link"
|
|
||||||
aria-label="{% trans "Show all diffusions" %}">
|
|
||||||
{% trans "All diffusions" %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
<section class="is-size-5">
|
|
||||||
{% for schedule in program.schedule_set.all %}
|
|
||||||
<p>
|
|
||||||
{{ schedule.get_frequency_verbose }}
|
|
||||||
{% with schedule.start|date:"H:i" as start %}
|
|
||||||
{% with schedule.end|date:"H:i" as end %}
|
|
||||||
<time datetime="{{ start }}">{{ start }}</time>
|
|
||||||
—
|
|
||||||
<time datetime="{{ end }}">{{ end }}</time>
|
|
||||||
{% endwith %}
|
|
||||||
{% endwith %}
|
|
||||||
<small>
|
|
||||||
{% if schedule.initial %}
|
|
||||||
{% with schedule.initial.date as date %}
|
|
||||||
<span title="{% blocktrans %}Rerun of {{ date }}{% endblocktrans %}">
|
|
||||||
({% trans "rerun" %})
|
|
||||||
</span>
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
|
||||||
</small>
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
</section>
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
{% extends "aircox_web/program_base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block header %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% include "aircox_web/program_header.html" %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
{% extends "aircox_web/page.html" %}
|
|
||||||
{% load i18n aircox_web %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{% trans "Timetable" %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{{ block.super }}
|
|
||||||
|
|
||||||
<section class="section">
|
|
||||||
<h3 class="subtitle size-3">
|
|
||||||
{% blocktrans %}From <b>{{ start }}</b> to <b>{{ end }}</b>{% endblocktrans %}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
{% unique_id "timetable" as timetable_id %}
|
|
||||||
<a-tabs default="{{ date }}">
|
|
||||||
<template v-slot:tabs="scope" noscript="hidden">
|
|
||||||
<li><a href="{% url "timetable" date=prev_date %}"><</a></li>
|
|
||||||
|
|
||||||
{% for day in by_date.keys %}
|
|
||||||
<a-tab value="{{ day }}">
|
|
||||||
<a href="{% url "timetable" date=day %}">
|
|
||||||
{{ day|date:"D. d" }}
|
|
||||||
</a>
|
|
||||||
</a-tab>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<a href="{% url "timetable" date=next_date %}">></a>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
{% with True as hide_schedule %}
|
|
||||||
<template v-slot:default="{value}">
|
|
||||||
{% for day, diffusions in by_date.items %}
|
|
||||||
<noscript><h4 class="subtitle is-4">{{ day|date:"l d F Y" }}</h4></noscript>
|
|
||||||
<div id="{{timetable_id}}-{{ day|date:"Y-m-d" }}" v-if="value == '{{ day }}'">
|
|
||||||
{% for object in diffusions %}
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-one-fifth has-text-right">
|
|
||||||
<time datetime="{{ object.start|date:"c" }}">
|
|
||||||
{{ object.start|date:"H:i" }} - {{ object.end|date:"H:i" }}
|
|
||||||
</time>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
{% include "aircox_web/diffusion_item.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</template>
|
|
||||||
{% endwith %}
|
|
||||||
</a-tabs>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
import random
|
|
||||||
|
|
||||||
from django import template
|
|
||||||
|
|
||||||
from aircox.models import Page,
|
|
||||||
from aircox_web.models import Page
|
|
||||||
|
|
||||||
random.seed()
|
|
||||||
register = template.Library()
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(name='diffusion_page')
|
|
||||||
def do_diffusion_page(diffusion):
|
|
||||||
""" Return page for diffusion. """
|
|
||||||
episode = diffusion.episode
|
|
||||||
if episode.is_publihed:
|
|
||||||
return diff.episode
|
|
||||||
program = episode.program
|
|
||||||
return program if program.is_published else None
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(name='unique_id')
|
|
||||||
def do_unique_id(prefix=''):
|
|
||||||
value = str(random.random()).replace('.', '')
|
|
||||||
return prefix + '_' + value if prefix else value
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name='is_diffusion')
|
|
||||||
def do_is_diffusion(obj):
|
|
||||||
return isinstance(obj, aircox.Diffusion)
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
|
@ -1,29 +0,0 @@
|
||||||
from django.conf.urls import url
|
|
||||||
from django.urls import path, register_converter
|
|
||||||
|
|
||||||
from . import views, models
|
|
||||||
from .converters import PagePathConverter, DateConverter, WeekConverter
|
|
||||||
|
|
||||||
register_converter(PagePathConverter, 'page_path')
|
|
||||||
register_converter(DateConverter, 'date')
|
|
||||||
register_converter(WeekConverter, 'week')
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('programs/<slug:slug>/',
|
|
||||||
views.ProgramPageView.as_view(), name='program-page'),
|
|
||||||
path('diffusion/<slug:slug>/',
|
|
||||||
views.DiffusionPageView.as_view(), name='diffusion-page'),
|
|
||||||
path('programs/<slug:program_slug>/diffusions/',
|
|
||||||
views.DiffusionsView.as_view(), name='diffusion-list'),
|
|
||||||
|
|
||||||
path('diffusions/',
|
|
||||||
views.TimetableView.as_view(), name='timetable'),
|
|
||||||
path('diffusions/<week:date>/',
|
|
||||||
views.TimetableView.as_view(), name='timetable'),
|
|
||||||
path('diffusions/all',
|
|
||||||
views.DiffusionsView.as_view(), name='diffusion-list'),
|
|
||||||
path('logs/', views.LogsView.as_view(), name='logs'),
|
|
||||||
path('logs/<date:date>/', views.LogsView.as_view(), name='logs'),
|
|
||||||
# path('<page_path:path>', views.route_page, name='page'),
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,296 +0,0 @@
|
||||||
from collections import OrderedDict, deque
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from django.http import Http404
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.views.generic import DetailView, ListView
|
|
||||||
from django.views.generic.base import TemplateResponseMixin, ContextMixin
|
|
||||||
|
|
||||||
from content_editor.contents import contents_for_item
|
|
||||||
|
|
||||||
from aircox import models as aircox
|
|
||||||
from .models import Site, Page, DiffusionPage, ProgramPage, \
|
|
||||||
get_diffusions_with_page
|
|
||||||
from .renderer import site_renderer, page_renderer
|
|
||||||
|
|
||||||
|
|
||||||
def route_page(request, path=None, *args, model=None, site=None, **kwargs):
|
|
||||||
"""
|
|
||||||
Route request to page of the provided path. If model is provided, uses
|
|
||||||
it.
|
|
||||||
"""
|
|
||||||
# TODO/FIXME: django site framework | site from request host
|
|
||||||
# TODO: extra page kwargs (as in pepr)
|
|
||||||
site = Site.objects.all().order_by('-default').first() \
|
|
||||||
if site is None else site
|
|
||||||
|
|
||||||
model = model if model is not None else Page
|
|
||||||
page = get_object_or_404(
|
|
||||||
model.objects.select_subclasses().live(),
|
|
||||||
path=path
|
|
||||||
)
|
|
||||||
kwargs['page'] = page
|
|
||||||
return page.view(request, *args, site=site, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseView(TemplateResponseMixin, ContextMixin):
|
|
||||||
site = None
|
|
||||||
""" Current website """
|
|
||||||
nav_side = False
|
|
||||||
""" Show side navigation """
|
|
||||||
title = None
|
|
||||||
""" Page title """
|
|
||||||
cover = None
|
|
||||||
""" Page cover """
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, site=None, **kwargs):
|
|
||||||
self.site = site if site is not None else \
|
|
||||||
Site.objects.all().order_by('-default').first()
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
if kwargs.get('site_regions') is None:
|
|
||||||
contents = contents_for_item(
|
|
||||||
self.site, site_renderer._renderers.keys())
|
|
||||||
kwargs['site_regions'] = contents.render_regions(site_renderer)
|
|
||||||
|
|
||||||
kwargs.setdefault('site', self.site)
|
|
||||||
kwargs.setdefault('cover', self.cover)
|
|
||||||
kwargs.setdefault('nav_side', self.nav_side)
|
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class PageView(BaseView, DetailView):
|
|
||||||
""" Base view class for pages. """
|
|
||||||
template_name = 'aircox_web/page.html'
|
|
||||||
context_object_name = 'page'
|
|
||||||
page = None
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return super().get_queryset().live()
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
page = getattr(self, 'object', None)
|
|
||||||
if page is not None:
|
|
||||||
if kwargs.get('regions') is None:
|
|
||||||
contents = contents_for_item(
|
|
||||||
page, page_renderer._renderers.keys())
|
|
||||||
kwargs['regions'] = contents.render_regions(page_renderer)
|
|
||||||
|
|
||||||
kwargs.setdefault('title', page.title)
|
|
||||||
kwargs.setdefault('cover', page.cover)
|
|
||||||
kwargs.setdefault('page', page)
|
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseProgramView(PageView):
|
|
||||||
""" Base view class for programs and their sub-pages. """
|
|
||||||
nav_side = True
|
|
||||||
list_count=5
|
|
||||||
|
|
||||||
def get_diffusions_queryset(self, program, queryset=None):
|
|
||||||
qs = get_diffusions_with_page() if queryset is None else queryset
|
|
||||||
return qs.before().filter(program=program).order_by('-start')
|
|
||||||
|
|
||||||
def get_context_data(self, program, **kwargs):
|
|
||||||
if not hasattr(program, 'page') or not program.page.is_published:
|
|
||||||
raise Http404
|
|
||||||
|
|
||||||
if 'diffusions' not in kwargs:
|
|
||||||
diffs = self.get_diffusions_queryset(program)[:self.list_count]
|
|
||||||
kwargs['diffusions'] = diffs
|
|
||||||
return super().get_context_data(program=program, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class ProgramPageView(BaseProgramView):
|
|
||||||
template_name = 'aircox_web/program_page.html'
|
|
||||||
model = ProgramPage
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return super().get_queryset().select_related('program')
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
kwargs.setdefault('program', self.object.program)
|
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class DiffusionPageView(BaseProgramView):
|
|
||||||
template_name = 'aircox_web/program_base.html'
|
|
||||||
model = DiffusionPage
|
|
||||||
|
|
||||||
def get_podcasts(self, diffusion):
|
|
||||||
return aircox.Sound.objects.diffusion(diffusion).podcasts()
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
diffusion = self.object.diffusion
|
|
||||||
kwargs.setdefault('program', diffusion.program)
|
|
||||||
kwargs.setdefault('parent', getattr(kwargs['program'], 'page', None))
|
|
||||||
if not 'podcasts' in kwargs:
|
|
||||||
kwargs['podcasts'] = self.get_podcasts(diffusion)
|
|
||||||
print('get prodcasts...', kwargs['podcasts'], diffusion)
|
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: pagination: in template, only a limited number of pages displayed
|
|
||||||
# DiffusionsView use diffusion instead of diffusion page for different reasons:
|
|
||||||
# more straightforward, it handles reruns
|
|
||||||
class DiffusionsView(BaseView, ListView):
|
|
||||||
template_name = 'aircox_web/diffusions.html'
|
|
||||||
model = aircox.Diffusion
|
|
||||||
paginate_by = 30
|
|
||||||
program = None
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
program_slug = kwargs.get('program_slug')
|
|
||||||
if program_slug:
|
|
||||||
self.program = get_object_or_404(
|
|
||||||
aircox.Program, slug=kwargs.get('program_slug'))
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
qs = get_diffusions_with_page(super().get_queryset()) \
|
|
||||||
.select_related('page', 'program')
|
|
||||||
if self.program:
|
|
||||||
qs = qs.filter(program=self.program)
|
|
||||||
return qs.order_by('-start')
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
program = kwargs.setdefault('program', self.program)
|
|
||||||
if program is not None and hasattr(program, 'page'):
|
|
||||||
kwargs.setdefault('cover', program.page.cover)
|
|
||||||
kwargs.setdefault('parent', program.page)
|
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TimetableView(BaseView, ListView):
|
|
||||||
""" View for timetables """
|
|
||||||
template_name = 'aircox_web/timetable.html'
|
|
||||||
model = aircox.Diffusion
|
|
||||||
|
|
||||||
title = _('Timetable')
|
|
||||||
|
|
||||||
date = None
|
|
||||||
start = None
|
|
||||||
end = None
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
self.date = self.kwargs.get('date') or datetime.date.today()
|
|
||||||
self.start = self.date - datetime.timedelta(days=self.date.weekday())
|
|
||||||
self.end = self.start + datetime.timedelta(days=7)
|
|
||||||
return super().get_queryset().station(self.site.station) \
|
|
||||||
.range(self.start, self.end) \
|
|
||||||
.order_by('start')
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
# regoup by dates
|
|
||||||
by_date = OrderedDict()
|
|
||||||
date = self.start
|
|
||||||
while date < self.end:
|
|
||||||
by_date[date] = []
|
|
||||||
date += datetime.timedelta(days=1)
|
|
||||||
|
|
||||||
for diffusion in self.object_list:
|
|
||||||
if not diffusion.date in by_date:
|
|
||||||
continue
|
|
||||||
by_date[diffusion.date].append(diffusion)
|
|
||||||
|
|
||||||
return super().get_context_data(
|
|
||||||
by_date=by_date,
|
|
||||||
date=self.date,
|
|
||||||
start=self.start,
|
|
||||||
end=self.end - datetime.timedelta(days=1),
|
|
||||||
prev_date=self.start - datetime.timedelta(days=1),
|
|
||||||
next_date=self.end + datetime.timedelta(days=1),
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LogViewBase(ListView):
|
|
||||||
station = None
|
|
||||||
date = None
|
|
||||||
delta = None
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
# only get logs for tracks: log for diffusion will be retrieved
|
|
||||||
# by the diffusions' queryset.
|
|
||||||
return super().get_queryset().station(self.station).on_air() \
|
|
||||||
.at(self.date).filter(track__isnull=False)
|
|
||||||
|
|
||||||
def get_diffusions_queryset(self):
|
|
||||||
return aircox.Diffusion.objects.station(self.station).on_air() \
|
|
||||||
.at(self.date)
|
|
||||||
|
|
||||||
def get_object_list(self, queryset):
|
|
||||||
diffs = deque(self.get_diffusions_queryset().order_by('start'))
|
|
||||||
logs = list(queryset.order_by('date'))
|
|
||||||
if not len(diffs):
|
|
||||||
return logs
|
|
||||||
|
|
||||||
object_list = []
|
|
||||||
diff = diffs.popleft()
|
|
||||||
last_collision = None
|
|
||||||
|
|
||||||
# diff.start < log on first diff
|
|
||||||
# diff.end > log on last diff
|
|
||||||
|
|
||||||
for index, log in enumerate(logs):
|
|
||||||
# get next diff
|
|
||||||
if diff.end < log.date:
|
|
||||||
diff = diffs.popleft() if len(diffs) else None
|
|
||||||
|
|
||||||
# no more diff that can collide: return list
|
|
||||||
if diff is None:
|
|
||||||
return object_list + logs[index:]
|
|
||||||
|
|
||||||
# diff colliding with log
|
|
||||||
if diff.start <= log.date <= diff.end:
|
|
||||||
if object_list[-1] is not diff:
|
|
||||||
object_list.append(diff)
|
|
||||||
last_collision = log
|
|
||||||
else:
|
|
||||||
# add last colliding log: track
|
|
||||||
if last_collision is not None:
|
|
||||||
object_list.append(last_collision)
|
|
||||||
|
|
||||||
object_list.append(log)
|
|
||||||
last_collision = None
|
|
||||||
return object_list
|
|
||||||
|
|
||||||
|
|
||||||
class LogsView(BaseView, LogViewBase):
|
|
||||||
""" View for timetables """
|
|
||||||
template_name = 'aircox_web/logs.html'
|
|
||||||
model = aircox.Log
|
|
||||||
title = _('Logs')
|
|
||||||
|
|
||||||
date = None
|
|
||||||
max_age = 10
|
|
||||||
|
|
||||||
min_date = None
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
self.station = self.site.station
|
|
||||||
|
|
||||||
today = datetime.date.today()
|
|
||||||
self.min_date = today - datetime.timedelta(days=self.max_age)
|
|
||||||
self.date = min(max(self.min_date, self.kwargs['date']), today) \
|
|
||||||
if 'date' in self.kwargs else today
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
today = datetime.date.today()
|
|
||||||
max_date = min(max(self.date + datetime.timedelta(days=3),
|
|
||||||
self.min_date + datetime.timedelta(days=6)), today)
|
|
||||||
|
|
||||||
return super().get_context_data(
|
|
||||||
date=self.date,
|
|
||||||
min_date=self.min_date,
|
|
||||||
dates=(date for date in (
|
|
||||||
max_date - datetime.timedelta(days=i)
|
|
||||||
for i in range(0, 7)) if date >= self.min_date
|
|
||||||
),
|
|
||||||
object_list=self.get_object_list(self.object_list),
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
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/lib/plugin');
|
|
||||||
|
|
||||||
|
|
||||||
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[\\/]/,
|
|
||||||
},
|
|
||||||
|
|
||||||
/*noscript: {
|
|
||||||
name: 'noscript',
|
|
||||||
chunks: 'initial',
|
|
||||||
enforce: true,
|
|
||||||
test: /noscript/,
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
plugins: [
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: "[name].css",
|
|
||||||
chunkFilename: "[id].css"
|
|
||||||
}),
|
|
||||||
new VueLoaderPlugin(),
|
|
||||||
],
|
|
||||||
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{ test: /\.vue$/, loader: 'vue-loader' },
|
|
||||||
{
|
|
||||||
test: /\/node_modules\//,
|
|
||||||
sideEffects: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.scss$/,
|
|
||||||
use: [ { loader: MiniCssExtractPlugin.loader },
|
|
||||||
{ loader: 'css-loader' },
|
|
||||||
{ loader: 'sass-loader' , options: { sourceMap: true }} ],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// TODO: remove ttf eot svg
|
|
||||||
test: /\.(ttf|eot|svg|woff2?)$/,
|
|
||||||
use: [{
|
|
||||||
loader: 'file-loader',
|
|
||||||
options: {
|
|
||||||
name: '[name].[ext]',
|
|
||||||
outputPath: 'fonts/',
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
js: path.resolve(__dirname, 'assets/js'),
|
|
||||||
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']
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
|
@ -14,6 +14,9 @@ $body-background-color: $light;
|
||||||
}
|
}
|
||||||
.is-borderless { border: none; }
|
.is-borderless { border: none; }
|
||||||
|
|
||||||
|
.has-background-transparent {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.navbar + .container {
|
.navbar + .container {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
@ -23,6 +26,10 @@ $body-background-color: $light;
|
||||||
box-shadow: 0em 0em 1em rgba(0,0,0,0.1);
|
box-shadow: 0em 0em 1em rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.navbar-item.is-active {
|
||||||
|
border-bottom: 1px grey solid;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
.navbar-brand img {
|
.navbar-brand img {
|
||||||
min-height: 6em;
|
min-height: 6em;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="tabs is-centered">
|
<div class="tabs is-centered is-medium">
|
||||||
<ul><slot name="tabs" :value="value" /></ul>
|
<ul><slot name="tabs" :value="value" /></ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -47,13 +47,12 @@ MEDIA_ROOT = os.path.join(STATIC_ROOT, 'media')
|
||||||
########################################################################
|
########################################################################
|
||||||
|
|
||||||
# set current language code. e.g. 'fr_BE'
|
# set current language code. e.g. 'fr_BE'
|
||||||
LANGUAGE_CODE = os.environ.get('LANG') or 'en_US'
|
LANGUAGE_CODE = 'en_US'
|
||||||
|
# locale
|
||||||
|
LC_LOCALE = 'en_US.UTF-8'
|
||||||
# set current timezone. e.g. 'Europe/Brussels'
|
# set current timezone. e.g. 'Europe/Brussels'
|
||||||
TIME_ZONE = os.environ.get('TZ') or 'UTC'
|
TIME_ZONE = os.environ.get('TZ') or 'UTC'
|
||||||
|
|
||||||
# wagtail site name
|
|
||||||
WAGTAIL_SITE_NAME = 'Aircox'
|
|
||||||
|
|
||||||
# debug mode
|
# debug mode
|
||||||
DEBUG = (os.environ['AIRCOX_DEBUG'].lower() in ('true', 1)) \
|
DEBUG = (os.environ['AIRCOX_DEBUG'].lower() in ('true', 1)) \
|
||||||
if 'AIRCOX_DEBUG' in os.environ else \
|
if 'AIRCOX_DEBUG' in os.environ else \
|
||||||
|
@ -64,7 +63,7 @@ if DEBUG:
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
'NAME': os.path.join(PROJECT_ROOT, 'db.sqlite3'),
|
||||||
'TIMEZONE': TIME_ZONE,
|
'TIMEZONE': TIME_ZONE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,6 +79,13 @@ else:
|
||||||
'TIMEZONE': TIME_ZONE,
|
'TIMEZONE': TIME_ZONE,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
# caching uses memcache
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
||||||
|
'LOCATION': '127.0.0.1:11211',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# allowed hosts
|
# allowed hosts
|
||||||
ALLOWED_HOSTS = ('127.0.0.1',)
|
ALLOWED_HOSTS = ('127.0.0.1',)
|
||||||
|
@ -102,7 +108,7 @@ timezone.activate(pytz.timezone(TIME_ZONE))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import locale
|
import locale
|
||||||
locale.setlocale(locale.LC_ALL, LANGUAGE_CODE)
|
locale.setlocale(locale.LC_ALL, LC_LOCALE)
|
||||||
except:
|
except:
|
||||||
print(
|
print(
|
||||||
'Can not set locale {LC}. Is it available on you system? Hint: '
|
'Can not set locale {LC}. Is it available on you system? Hint: '
|
||||||
|
@ -115,28 +121,17 @@ except:
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
|
# aircox & dependencies
|
||||||
'aircox',
|
'aircox',
|
||||||
'aircox_cms',
|
'rest_framework',
|
||||||
|
"ckeditor",
|
||||||
'jet',
|
'easy_thumbnails',
|
||||||
'wagtail.contrib.forms',
|
'filer',
|
||||||
'wagtail.contrib.redirects',
|
|
||||||
'wagtail.embeds',
|
|
||||||
'wagtail.sites',
|
|
||||||
'wagtail.users',
|
|
||||||
'wagtail.snippets',
|
|
||||||
'wagtail.documents',
|
|
||||||
'wagtail.images',
|
|
||||||
'wagtail.search',
|
|
||||||
'wagtail.admin',
|
|
||||||
'wagtail.core',
|
|
||||||
'wagtail.contrib.settings',
|
|
||||||
'wagtail.contrib.modeladmin',
|
|
||||||
|
|
||||||
'modelcluster',
|
|
||||||
'taggit',
|
'taggit',
|
||||||
|
'adminsortable2',
|
||||||
'honeypot',
|
'honeypot',
|
||||||
|
|
||||||
|
# django
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
|
@ -147,7 +142,6 @@ INSTALLED_APPS = (
|
||||||
|
|
||||||
MIDDLEWARE = (
|
MIDDLEWARE = (
|
||||||
'django.middleware.gzip.GZipMiddleware',
|
'django.middleware.gzip.GZipMiddleware',
|
||||||
'htmlmin.middleware.HtmlMinifyMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
@ -156,9 +150,6 @@ MIDDLEWARE = (
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
|
||||||
'wagtail.core.middleware.SiteMiddleware',
|
|
||||||
'wagtail.contrib.redirects.middleware.RedirectMiddleware',
|
|
||||||
|
|
||||||
'aircox.middleware.AircoxMiddleware'
|
'aircox.middleware.AircoxMiddleware'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -180,12 +171,7 @@ TEMPLATES = [
|
||||||
"django.template.context_processors.static",
|
"django.template.context_processors.static",
|
||||||
"django.template.context_processors.tz",
|
"django.template.context_processors.tz",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
|
||||||
'wagtail.contrib.settings.context_processors.settings',
|
|
||||||
),
|
),
|
||||||
'builtins': [
|
|
||||||
'overextends.templatetags.overextends_tags'
|
|
||||||
],
|
|
||||||
'loaders': (
|
'loaders': (
|
||||||
'django.template.loaders.filesystem.Loader',
|
'django.template.loaders.filesystem.Loader',
|
||||||
'django.template.loaders.app_directories.Loader',
|
'django.template.loaders.app_directories.Loader',
|
||||||
|
@ -197,6 +183,7 @@ TEMPLATES = [
|
||||||
|
|
||||||
WSGI_APPLICATION = 'instance.wsgi.application'
|
WSGI_APPLICATION = 'instance.wsgi.application'
|
||||||
|
|
||||||
|
# FIXME: what about dev & prod modules?
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': False,
|
'disable_existing_loggers': False,
|
||||||
|
@ -206,7 +193,11 @@ LOGGING = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'loggers': {
|
'loggers': {
|
||||||
'aircox.core': {
|
'aircox': {
|
||||||
|
'handlers': ['console'],
|
||||||
|
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
|
||||||
|
},
|
||||||
|
'aircox.commands': {
|
||||||
'handlers': ['console'],
|
'handlers': ['console'],
|
||||||
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
|
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
|
||||||
},
|
},
|
||||||
|
@ -214,10 +205,6 @@ LOGGING = {
|
||||||
'handlers': ['console'],
|
'handlers': ['console'],
|
||||||
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
|
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
|
||||||
},
|
},
|
||||||
'aircox.tools': {
|
|
||||||
'handlers': ['console'],
|
|
||||||
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user