navigation & breadcrumbs
This commit is contained in:
		@ -18,6 +18,7 @@ class EpisodeQuerySet(ProgramChildQuerySet):
 | 
				
			|||||||
class Episode(Page):
 | 
					class Episode(Page):
 | 
				
			||||||
    objects = EpisodeQuerySet.as_manager()
 | 
					    objects = EpisodeQuerySet.as_manager()
 | 
				
			||||||
    detail_url_name = "episode-detail"
 | 
					    detail_url_name = "episode-detail"
 | 
				
			||||||
 | 
					    list_url_name = "episode-list"
 | 
				
			||||||
    template_prefix = "episode"
 | 
					    template_prefix = "episode"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
 | 
				
			|||||||
@ -139,7 +139,9 @@ class BasePage(Renderable, models.Model):
 | 
				
			|||||||
        super().save(*args, **kwargs)
 | 
					        super().save(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_absolute_url(self):
 | 
					    def get_absolute_url(self):
 | 
				
			||||||
        return reverse(self.detail_url_name, kwargs={"slug": self.slug}) if self.is_published else "#"
 | 
					        if self.is_published:
 | 
				
			||||||
 | 
					            return reverse(self.detail_url_name, kwargs={"slug": self.slug})
 | 
				
			||||||
 | 
					        return ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def is_draft(self):
 | 
					    def is_draft(self):
 | 
				
			||||||
@ -216,6 +218,8 @@ class Page(BasePage):
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    objects = PageQuerySet.as_manager()
 | 
					    objects = PageQuerySet.as_manager()
 | 
				
			||||||
 | 
					    detail_url_name = ""
 | 
				
			||||||
 | 
					    list_url_name = "page-list"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @cached_property
 | 
					    @cached_property
 | 
				
			||||||
    def parent_subclass(self):
 | 
					    def parent_subclass(self):
 | 
				
			||||||
@ -223,6 +227,15 @@ class Page(BasePage):
 | 
				
			|||||||
            return Page.objects.get_subclass(id=self.parent_id)
 | 
					            return Page.objects.get_subclass(id=self.parent_id)
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_absolute_url(self):
 | 
				
			||||||
 | 
					        if not self.is_published and self.parent_subclass:
 | 
				
			||||||
 | 
					            return self.parent_subclass.get_absolute_url()
 | 
				
			||||||
 | 
					        return super().get_absolute_url()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_list_url(cls, kwargs={}):
 | 
				
			||||||
 | 
					        return reverse(cls.list_url_name, kwargs=kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        verbose_name = _("Publication")
 | 
					        verbose_name = _("Publication")
 | 
				
			||||||
        verbose_name_plural = _("Publications")
 | 
					        verbose_name_plural = _("Publications")
 | 
				
			||||||
@ -243,46 +256,32 @@ class StaticPage(BasePage):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    detail_url_name = "static-page-detail"
 | 
					    detail_url_name = "static-page-detail"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ATTACH_TO_HOME = 0x00
 | 
					    class Target(models.TextChoices):
 | 
				
			||||||
    ATTACH_TO_DIFFUSIONS = 0x01
 | 
					        HOME = "", _("Home Page")
 | 
				
			||||||
    ATTACH_TO_LOGS = 0x02
 | 
					        TIMETABLE = "timetable-list", _("Timetable")
 | 
				
			||||||
    ATTACH_TO_PROGRAMS = 0x03
 | 
					        PROGRAMS = "program-list", _("Programs list")
 | 
				
			||||||
    ATTACH_TO_EPISODES = 0x04
 | 
					        EPISODES = "episode-list", _("Episodes list")
 | 
				
			||||||
    ATTACH_TO_ARTICLES = 0x05
 | 
					        ARTICLES = "article-list", _("Articles list")
 | 
				
			||||||
    ATTACH_TO_PAGES = 0x06
 | 
					        PAGES = "page-list", _("Publications list")
 | 
				
			||||||
    ATTACH_TO_PODCASTS = 0x07
 | 
					        PODCASTS = "podcast-list", _("Podcasts list")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ATTACH_TO_CHOICES = (
 | 
					    attach_to = models.CharField(
 | 
				
			||||||
        (ATTACH_TO_HOME, _("Home page")),
 | 
					 | 
				
			||||||
        (ATTACH_TO_DIFFUSIONS, _("Diffusions page")),
 | 
					 | 
				
			||||||
        (ATTACH_TO_LOGS, _("Logs page")),
 | 
					 | 
				
			||||||
        (ATTACH_TO_PROGRAMS, _("Programs list")),
 | 
					 | 
				
			||||||
        (ATTACH_TO_EPISODES, _("Episodes list")),
 | 
					 | 
				
			||||||
        (ATTACH_TO_ARTICLES, _("Articles list")),
 | 
					 | 
				
			||||||
        (ATTACH_TO_PAGES, _("Publications list")),
 | 
					 | 
				
			||||||
        (ATTACH_TO_PODCASTS, _("Podcasts list")),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    VIEWS = {
 | 
					 | 
				
			||||||
        ATTACH_TO_HOME: "home",
 | 
					 | 
				
			||||||
        ATTACH_TO_DIFFUSIONS: "diffusion-list",
 | 
					 | 
				
			||||||
        ATTACH_TO_LOGS: "log-list",
 | 
					 | 
				
			||||||
        ATTACH_TO_PROGRAMS: "program-list",
 | 
					 | 
				
			||||||
        ATTACH_TO_EPISODES: "episode-list",
 | 
					 | 
				
			||||||
        ATTACH_TO_ARTICLES: "article-list",
 | 
					 | 
				
			||||||
        ATTACH_TO_PAGES: "page-list",
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    attach_to = models.SmallIntegerField(
 | 
					 | 
				
			||||||
        _("attach to"),
 | 
					        _("attach to"),
 | 
				
			||||||
        choices=ATTACH_TO_CHOICES,
 | 
					        choices=Target.choices,
 | 
				
			||||||
 | 
					        max_length=32,
 | 
				
			||||||
        blank=True,
 | 
					        blank=True,
 | 
				
			||||||
        null=True,
 | 
					        null=True,
 | 
				
			||||||
        help_text=_("display this page content to related element"),
 | 
					        help_text=_("display this page content to related element"),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_related_view(self):
 | 
				
			||||||
 | 
					        from ..views import attached
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.attach_to and attached.get(self.attach_to) or None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_absolute_url(self):
 | 
					    def get_absolute_url(self):
 | 
				
			||||||
        if self.attach_to:
 | 
					        if self.attach_to:
 | 
				
			||||||
            return reverse(self.VIEWS[self.attach_to])
 | 
					            return reverse(self.attach_to)
 | 
				
			||||||
        return super().get_absolute_url()
 | 
					        return super().get_absolute_url()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -320,7 +319,7 @@ class NavItem(models.Model):
 | 
				
			|||||||
    station = models.ForeignKey(Station, models.CASCADE, verbose_name=_("station"))
 | 
					    station = models.ForeignKey(Station, models.CASCADE, verbose_name=_("station"))
 | 
				
			||||||
    menu = models.SlugField(_("menu"), max_length=24)
 | 
					    menu = models.SlugField(_("menu"), max_length=24)
 | 
				
			||||||
    order = models.PositiveSmallIntegerField(_("order"))
 | 
					    order = models.PositiveSmallIntegerField(_("order"))
 | 
				
			||||||
    text = models.CharField(_("title"), max_length=64)
 | 
					    text = models.CharField(_("title"), max_length=64, blank=True, null=True)
 | 
				
			||||||
    url = models.CharField(_("url"), max_length=256, blank=True, null=True)
 | 
					    url = models.CharField(_("url"), max_length=256, blank=True, null=True)
 | 
				
			||||||
    page = models.ForeignKey(
 | 
					    page = models.ForeignKey(
 | 
				
			||||||
        StaticPage,
 | 
					        StaticPage,
 | 
				
			||||||
@ -339,14 +338,21 @@ class NavItem(models.Model):
 | 
				
			|||||||
    def get_url(self):
 | 
					    def get_url(self):
 | 
				
			||||||
        return self.url if self.url else self.page.get_absolute_url() if self.page else None
 | 
					        return self.url if self.url else self.page.get_absolute_url() if self.page else None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_label(self):
 | 
				
			||||||
 | 
					        if self.text:
 | 
				
			||||||
 | 
					            return self.text
 | 
				
			||||||
 | 
					        elif self.page:
 | 
				
			||||||
 | 
					            return self.page.title
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def render(self, request, css_class="", active_class=""):
 | 
					    def render(self, request, css_class="", active_class=""):
 | 
				
			||||||
        url = self.get_url()
 | 
					        url = self.get_url()
 | 
				
			||||||
 | 
					        text = self.get_text()
 | 
				
			||||||
        if active_class and request.path.startswith(url):
 | 
					        if active_class and request.path.startswith(url):
 | 
				
			||||||
            css_class += " " + active_class
 | 
					            css_class += " " + active_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not url:
 | 
					        if not url:
 | 
				
			||||||
            return self.text
 | 
					            return text
 | 
				
			||||||
        elif not css_class:
 | 
					        elif not css_class:
 | 
				
			||||||
            return format_html('<a href="{}">{}</a>', url, self.text)
 | 
					            return format_html('<a href="{}">{}</a>', url, text)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return format_html('<a href="{}" class="{}">{}</a>', url, css_class, self.text)
 | 
					            return format_html('<a href="{}" class="{}">{}</a>', url, css_class, text)
 | 
				
			||||||
 | 
				
			|||||||
@ -61,6 +61,7 @@ class Program(Page):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    objects = ProgramQuerySet.as_manager()
 | 
					    objects = ProgramQuerySet.as_manager()
 | 
				
			||||||
    detail_url_name = "program-detail"
 | 
					    detail_url_name = "program-detail"
 | 
				
			||||||
 | 
					    list_url_name = "program-list"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def path(self):
 | 
					    def path(self):
 | 
				
			||||||
 | 
				
			|||||||
@ -33,9 +33,9 @@
 | 
				
			|||||||
  --a-player-panel-bg: var(--highlight-color);
 | 
					  --a-player-panel-bg: var(--highlight-color);
 | 
				
			||||||
  --a-player-bar-bg: var(--highlight-color);
 | 
					  --a-player-bar-bg: var(--highlight-color);
 | 
				
			||||||
  --a-player-bar-title-alone-sz: 1.6rem;
 | 
					  --a-player-bar-title-alone-sz: 1.6rem;
 | 
				
			||||||
  --button-fg: var(--text-color);
 | 
					  --button-fg: var(--highlight-color-2);
 | 
				
			||||||
  --button-bg: var(--highlight-color);
 | 
					  --button-bg: var(--highlight-color);
 | 
				
			||||||
  --button-hg-fg: var(--highlight-color-2);
 | 
					  --button-hg-fg: var(--text-color);
 | 
				
			||||||
  --button-hg-bg: var(--highlight-color);
 | 
					  --button-hg-bg: var(--highlight-color);
 | 
				
			||||||
  --button-active-fg: var(--highlight-color);
 | 
					  --button-active-fg: var(--highlight-color);
 | 
				
			||||||
  --button-active-bg: var(--highlight-color-2);
 | 
					  --button-active-bg: var(--highlight-color-2);
 | 
				
			||||||
@ -234,6 +234,14 @@
 | 
				
			|||||||
  height: var(--preview-cover-size);
 | 
					  height: var(--preview-cover-size);
 | 
				
			||||||
  width: var(--preview-cover-size);
 | 
					  width: var(--preview-cover-size);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.preview-card.small {
 | 
				
			||||||
 | 
					  height: var(--preview-cover-small-size);
 | 
				
			||||||
 | 
					  width: var(--preview-cover-small-size);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.preview-card.tiny {
 | 
				
			||||||
 | 
					  height: var(--preview-cover-tiny-size);
 | 
				
			||||||
 | 
					  width: var(--preview-cover-tiny-size);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
.preview-card:not(.header) {
 | 
					.preview-card:not(.header) {
 | 
				
			||||||
  box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
 | 
					  box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -259,57 +267,6 @@ preview-header:not(.no-cover) .card-headings .heading, preview-header:not(.no-co
 | 
				
			|||||||
  margin-bottom: 0.6rem;
 | 
					  margin-bottom: 0.6rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.header.preview-header {
 | 
					 | 
				
			||||||
  align-items: start;
 | 
					 | 
				
			||||||
  gap: 0.6rem;
 | 
					 | 
				
			||||||
  min-height: unset;
 | 
					 | 
				
			||||||
  padding-top: 0.6rem !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.header .headings {
 | 
					 | 
				
			||||||
  width: unset;
 | 
					 | 
				
			||||||
  flex-grow: 1;
 | 
					 | 
				
			||||||
  padding-top: 0 !important;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  flex-direction: column;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.header.has-cover {
 | 
					 | 
				
			||||||
  min-height: calc(var(--header-height) / 2);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.header .title {
 | 
					 | 
				
			||||||
  font-size: 40px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.header .subtitle {
 | 
					 | 
				
			||||||
  font-size: 32px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.header-cover:not(:only-child) {
 | 
					 | 
				
			||||||
  float: right;
 | 
					 | 
				
			||||||
  height: var(--header-height);
 | 
					 | 
				
			||||||
  max-width: calc(var(--header-height) * 2);
 | 
					 | 
				
			||||||
  margin: 0 0 1.2rem 1.2rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.header-cover:only-child {
 | 
					 | 
				
			||||||
  with: 100%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@media screen and (max-width: 600px) {
 | 
					 | 
				
			||||||
  .container.header {
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  .container.header .headings {
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
    clear: both;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  .container.header .header-cover {
 | 
					 | 
				
			||||||
    float: none;
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
    max-width: unset;
 | 
					 | 
				
			||||||
    height: unset;
 | 
					 | 
				
			||||||
    margin-left: 0rem;
 | 
					 | 
				
			||||||
    margin-right: 0rem;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.card-grid {
 | 
					.card-grid {
 | 
				
			||||||
  display: grid;
 | 
					  display: grid;
 | 
				
			||||||
  grid-template-columns: 1fr 1fr 1fr;
 | 
					  grid-template-columns: 1fr 1fr 1fr;
 | 
				
			||||||
@ -430,6 +387,12 @@ preview-header:not(.no-cover) .card-headings .heading, preview-header:not(.no-co
 | 
				
			|||||||
.a-player a {
 | 
					.a-player a {
 | 
				
			||||||
  color: var(--a-player-url-fg);
 | 
					  color: var(--a-player-url-fg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.a-player .button {
 | 
				
			||||||
 | 
					  color: var(--text-black);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.a-player .button:hover {
 | 
				
			||||||
 | 
					  color: var(--button-fg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.a-player-panels {
 | 
					.a-player-panels {
 | 
				
			||||||
  background: var(--a-player-panel-bg);
 | 
					  background: var(--a-player-panel-bg);
 | 
				
			||||||
@ -2719,12 +2682,12 @@ a.navbar-item:focus, a.navbar-item:focus-within, a.navbar-item:hover, a.navbar-i
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#player .button, #player a.button, #player button.button, #player .nav-urls a, .ax .button, .ax a.button, .ax button.button, .ax .nav-urls a {
 | 
					#player .button, #player a.button, #player button.button, #player .nav-urls a, .ax .button, .ax a.button, .ax button.button, .ax .nav-urls a {
 | 
				
			||||||
  display: inline-block;
 | 
					  display: inline-block;
 | 
				
			||||||
  padding: 0.6em;
 | 
					  padding: 0.4em;
 | 
				
			||||||
  border-radius: 4px;
 | 
					 | 
				
			||||||
  border: 1px var(--highlight-color-2-alpha) solid;
 | 
					  border: 1px var(--highlight-color-2-alpha) solid;
 | 
				
			||||||
  justify-content: center;
 | 
					  justify-content: center;
 | 
				
			||||||
  text-align: center;
 | 
					  text-align: center;
 | 
				
			||||||
  font-size: 1.4rem;
 | 
					  font-size: 1.4rem;
 | 
				
			||||||
 | 
					  color: var(--button-fg);
 | 
				
			||||||
  background-color: var(--button-bg);
 | 
					  background-color: var(--button-bg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#player .button .icon, #player a.button .icon, #player button.button .icon, #player .nav-urls a .icon, .ax .button .icon, .ax a.button .icon, .ax button.button .icon, .ax .nav-urls a .icon {
 | 
					#player .button .icon, #player a.button .icon, #player button.button .icon, #player .nav-urls a .icon, .ax .button .icon, .ax a.button .icon, .ax button.button .icon, .ax .nav-urls a .icon {
 | 
				
			||||||
 | 
				
			|||||||
@ -33,9 +33,9 @@
 | 
				
			|||||||
  --a-player-panel-bg: var(--highlight-color);
 | 
					  --a-player-panel-bg: var(--highlight-color);
 | 
				
			||||||
  --a-player-bar-bg: var(--highlight-color);
 | 
					  --a-player-bar-bg: var(--highlight-color);
 | 
				
			||||||
  --a-player-bar-title-alone-sz: 1.6rem;
 | 
					  --a-player-bar-title-alone-sz: 1.6rem;
 | 
				
			||||||
  --button-fg: var(--text-color);
 | 
					  --button-fg: var(--highlight-color-2);
 | 
				
			||||||
  --button-bg: var(--highlight-color);
 | 
					  --button-bg: var(--highlight-color);
 | 
				
			||||||
  --button-hg-fg: var(--highlight-color-2);
 | 
					  --button-hg-fg: var(--text-color);
 | 
				
			||||||
  --button-hg-bg: var(--highlight-color);
 | 
					  --button-hg-bg: var(--highlight-color);
 | 
				
			||||||
  --button-active-fg: var(--highlight-color);
 | 
					  --button-active-fg: var(--highlight-color);
 | 
				
			||||||
  --button-active-bg: var(--highlight-color-2);
 | 
					  --button-active-bg: var(--highlight-color-2);
 | 
				
			||||||
@ -234,6 +234,14 @@
 | 
				
			|||||||
  height: var(--preview-cover-size);
 | 
					  height: var(--preview-cover-size);
 | 
				
			||||||
  width: var(--preview-cover-size);
 | 
					  width: var(--preview-cover-size);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.preview-card.small {
 | 
				
			||||||
 | 
					  height: var(--preview-cover-small-size);
 | 
				
			||||||
 | 
					  width: var(--preview-cover-small-size);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.preview-card.tiny {
 | 
				
			||||||
 | 
					  height: var(--preview-cover-tiny-size);
 | 
				
			||||||
 | 
					  width: var(--preview-cover-tiny-size);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
.preview-card:not(.header) {
 | 
					.preview-card:not(.header) {
 | 
				
			||||||
  box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
 | 
					  box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.2);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -259,57 +267,6 @@ preview-header:not(.no-cover) .card-headings .heading, preview-header:not(.no-co
 | 
				
			|||||||
  margin-bottom: 0.6rem;
 | 
					  margin-bottom: 0.6rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.header.preview-header {
 | 
					 | 
				
			||||||
  align-items: start;
 | 
					 | 
				
			||||||
  gap: 0.6rem;
 | 
					 | 
				
			||||||
  min-height: unset;
 | 
					 | 
				
			||||||
  padding-top: 0.6rem !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.header .headings {
 | 
					 | 
				
			||||||
  width: unset;
 | 
					 | 
				
			||||||
  flex-grow: 1;
 | 
					 | 
				
			||||||
  padding-top: 0 !important;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  flex-direction: column;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.header.has-cover {
 | 
					 | 
				
			||||||
  min-height: calc(var(--header-height) / 2);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.header .title {
 | 
					 | 
				
			||||||
  font-size: 40px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.header .subtitle {
 | 
					 | 
				
			||||||
  font-size: 32px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.header-cover:not(:only-child) {
 | 
					 | 
				
			||||||
  float: right;
 | 
					 | 
				
			||||||
  height: var(--header-height);
 | 
					 | 
				
			||||||
  max-width: calc(var(--header-height) * 2);
 | 
					 | 
				
			||||||
  margin: 0 0 1.2rem 1.2rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.header-cover:only-child {
 | 
					 | 
				
			||||||
  with: 100%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@media screen and (max-width: 600px) {
 | 
					 | 
				
			||||||
  .container.header {
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  .container.header .headings {
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
    clear: both;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  .container.header .header-cover {
 | 
					 | 
				
			||||||
    float: none;
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
    max-width: unset;
 | 
					 | 
				
			||||||
    height: unset;
 | 
					 | 
				
			||||||
    margin-left: 0rem;
 | 
					 | 
				
			||||||
    margin-right: 0rem;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.card-grid {
 | 
					.card-grid {
 | 
				
			||||||
  display: grid;
 | 
					  display: grid;
 | 
				
			||||||
  grid-template-columns: 1fr 1fr 1fr;
 | 
					  grid-template-columns: 1fr 1fr 1fr;
 | 
				
			||||||
@ -430,6 +387,12 @@ preview-header:not(.no-cover) .card-headings .heading, preview-header:not(.no-co
 | 
				
			|||||||
.a-player a {
 | 
					.a-player a {
 | 
				
			||||||
  color: var(--a-player-url-fg);
 | 
					  color: var(--a-player-url-fg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.a-player .button {
 | 
				
			||||||
 | 
					  color: var(--text-black);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.a-player .button:hover {
 | 
				
			||||||
 | 
					  color: var(--button-fg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.a-player-panels {
 | 
					.a-player-panels {
 | 
				
			||||||
  background: var(--a-player-panel-bg);
 | 
					  background: var(--a-player-panel-bg);
 | 
				
			||||||
@ -7381,6 +7344,9 @@ a.tag:hover {
 | 
				
			|||||||
  text-decoration: none;
 | 
					  text-decoration: none;
 | 
				
			||||||
  padding: 0.4rem;
 | 
					  padding: 0.4rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.page a:hover {
 | 
				
			||||||
 | 
					  color: var(--text-color);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
.page section.container {
 | 
					.page section.container {
 | 
				
			||||||
  padding-top: 2rem;
 | 
					  padding-top: 2rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -7419,12 +7385,12 @@ a.tag:hover {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.button, a.button, button.button, .nav-urls a {
 | 
					.button, a.button, button.button, .nav-urls a {
 | 
				
			||||||
  display: inline-block;
 | 
					  display: inline-block;
 | 
				
			||||||
  padding: 0.6em;
 | 
					  padding: 0.4em;
 | 
				
			||||||
  border-radius: 4px;
 | 
					 | 
				
			||||||
  border: 1px var(--highlight-color-2-alpha) solid;
 | 
					  border: 1px var(--highlight-color-2-alpha) solid;
 | 
				
			||||||
  justify-content: center;
 | 
					  justify-content: center;
 | 
				
			||||||
  text-align: center;
 | 
					  text-align: center;
 | 
				
			||||||
  font-size: 1.4rem;
 | 
					  font-size: 1.4rem;
 | 
				
			||||||
 | 
					  color: var(--button-fg);
 | 
				
			||||||
  background-color: var(--button-bg);
 | 
					  background-color: var(--button-bg);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.button .icon, a.button .icon, button.button .icon, .nav-urls a .icon {
 | 
					.button .icon, a.button .icon, button.button .icon, .nav-urls a .icon {
 | 
				
			||||||
@ -7487,7 +7453,6 @@ a.tag:hover {
 | 
				
			|||||||
  display: none;
 | 
					  display: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.actions button, .actions .action {
 | 
					.actions button, .actions .action {
 | 
				
			||||||
  background-color: var(--highlight-color);
 | 
					 | 
				
			||||||
  justify-content: center;
 | 
					  justify-content: center;
 | 
				
			||||||
  min-width: 2rem;
 | 
					  min-width: 2rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -7500,9 +7465,6 @@ a.tag:hover {
 | 
				
			|||||||
.actions button label, .actions .action label {
 | 
					.actions button label, .actions .action label {
 | 
				
			||||||
  margin-left: 0.4rem;
 | 
					  margin-left: 0.4rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.actions button:hover, .actions button .selected, .actions .action:hover, .actions .action .selected {
 | 
					 | 
				
			||||||
  color: var(--highlight-color-2) !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
.label, .textarea, .input, .select {
 | 
					.label, .textarea, .input, .select {
 | 
				
			||||||
  font-size: 1.4rem;
 | 
					  font-size: 1.4rem;
 | 
				
			||||||
@ -7520,6 +7482,10 @@ a.tag:hover {
 | 
				
			|||||||
  margin-top: 0.6rem;
 | 
					  margin-top: 0.6rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.navs {
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.nav {
 | 
					.nav {
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  background-color: var(--highlight-color);
 | 
					  background-color: var(--highlight-color);
 | 
				
			||||||
@ -7547,7 +7513,7 @@ a.tag:hover {
 | 
				
			|||||||
  vertical-align: top;
 | 
					  vertical-align: top;
 | 
				
			||||||
  display: inline-block;
 | 
					  display: inline-block;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.nav .nav-item.active {
 | 
					.nav .nav-item.active, .nav .nav-item:hover {
 | 
				
			||||||
  background-color: var(--highlight-color-2);
 | 
					  background-color: var(--highlight-color-2);
 | 
				
			||||||
  color: var(--highlight-color);
 | 
					  color: var(--highlight-color);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -7581,12 +7547,32 @@ a.tag:hover {
 | 
				
			|||||||
  white-space: nowrap;
 | 
					  white-space: nowrap;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.nav.secondary {
 | 
					.nav.secondary {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  z-index: 100;
 | 
				
			||||||
 | 
					  box-shadow: 0em 0.5em 0.5em rgba(0, 0, 0, 0.05);
 | 
				
			||||||
  justify-content: right;
 | 
					  justify-content: right;
 | 
				
			||||||
 | 
					  display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.nav-item:hover + .nav.secondary, .nav.secondary:hover {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  top: var(--nav-primary-height);
 | 
				
			||||||
 | 
					  left: 0rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.nav.secondary .nav-item {
 | 
					.nav.secondary .nav-item {
 | 
				
			||||||
  font-size: 1rem;
 | 
					  font-size: 1rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.breadcrumbs {
 | 
				
			||||||
 | 
					  text-align: right;
 | 
				
			||||||
 | 
					  height: 2.2rem;
 | 
				
			||||||
 | 
					  margin-bottom: 0.4rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.breadcrumbs a + a:before {
 | 
				
			||||||
 | 
					  content: "/";
 | 
				
			||||||
 | 
					  margin: 0 0.4rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@media screen and (max-width: 1024px) {
 | 
					@media screen and (max-width: 1024px) {
 | 
				
			||||||
  .page {
 | 
					  .page {
 | 
				
			||||||
    margin-top: var(--nav-primary-height);
 | 
					    margin-top: var(--nav-primary-height);
 | 
				
			||||||
@ -7685,7 +7671,7 @@ nav li a, nav li .button {
 | 
				
			|||||||
  flex-direction: column;
 | 
					  flex-direction: column;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.header.has-cover {
 | 
					.header.has-cover {
 | 
				
			||||||
  min-height: calc(var(--header-height) / 2);
 | 
					  min-height: calc(var(--header-height) / 3);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.header .title {
 | 
					.header .title {
 | 
				
			||||||
  font-size: 40px;
 | 
					  font-size: 40px;
 | 
				
			||||||
 | 
				
			|||||||
@ -375,7 +375,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
 | 
				
			|||||||
  \**********************/
 | 
					  \**********************/
 | 
				
			||||||
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
 | 
					/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/all.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/all.min.css\");\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./app */ \"./src/app.js\");\n/* harmony import */ var _vueLoader__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./vueLoader */ \"./src/vueLoader.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./sound */ \"./src/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./model */ \"./src/model.js\");\n/* harmony import */ var _assets_common_scss__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./assets/common.scss */ \"./src/assets/common.scss\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n//-- aircox\n\n\n\n\n\nwindow.aircox = {\n  // main application\n  loader: null,\n  get app() {\n    return this.loader.app;\n  },\n  // player application\n  playerLoader: null,\n  get playerApp() {\n    return this.playerLoader && this.playerLoader.app;\n  },\n  get player() {\n    return this.playerLoader.vm && this.playerLoader.vm.$refs.player;\n  },\n  Set: _model__WEBPACK_IMPORTED_MODULE_4__.Set,\n  Sound: _sound__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n  /**\n   * Initialize main application and player.\n   */\n  init(props = null, {\n    hotReload = false,\n    el = null,\n    config = null,\n    playerConfig = null,\n    initApp = true,\n    initPlayer = true,\n    loader = null,\n    playerLoader = null\n  } = {}) {\n    if (initPlayer) {\n      playerConfig = playerConfig || _app__WEBPACK_IMPORTED_MODULE_1__.PlayerApp;\n      playerLoader = playerLoader || new _vueLoader__WEBPACK_IMPORTED_MODULE_2__[\"default\"](playerConfig);\n      playerLoader.enable(false);\n      this.playerLoader = playerLoader;\n    }\n    if (initApp) {\n      config = config || window.App || _app__WEBPACK_IMPORTED_MODULE_1__[\"default\"];\n      config.el = el || config.el;\n      loader = loader || new _vueLoader__WEBPACK_IMPORTED_MODULE_2__[\"default\"]({\n        el,\n        props,\n        ...config\n      });\n      loader.enable(hotReload);\n      this.loader = loader;\n    }\n  },\n  /**\n   * Filter navbar dropdown menu items\n   */\n  filter_menu(event) {\n    var filter = new RegExp(event.target.value, 'gi');\n    var container = event.target.closest('.navbar-dropdown');\n    if (event.target.value) for (let item of container.querySelectorAll('a.navbar-item')) item.style.display = item.innerHTML.search(filter) == -1 ? 'none' : null;else for (let item of container.querySelectorAll('a.navbar-item')) item.style.display = null;\n  },\n  pickDate(url, date) {\n    url = `${url}?date=${date.id}`;\n    this.builder.fetch(url);\n  }\n};\n\n//# sourceURL=webpack://aircox-assets/./src/index.js?");
 | 
					eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/all.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/all.min.css\");\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./app */ \"./src/app.js\");\n/* harmony import */ var _vueLoader__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./vueLoader */ \"./src/vueLoader.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./sound */ \"./src/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./model */ \"./src/model.js\");\n/* harmony import */ var _assets_common_scss__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./assets/common.scss */ \"./src/assets/common.scss\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n//-- aircox\n\n\n\n\n\nwindow.aircox = {\n  // main application\n  loader: null,\n  get app() {\n    return this.loader.app;\n  },\n  // player application\n  playerLoader: null,\n  get playerApp() {\n    return this.playerLoader && this.playerLoader.app;\n  },\n  get player() {\n    return this.playerLoader.vm && this.playerLoader.vm.$refs.player;\n  },\n  Set: _model__WEBPACK_IMPORTED_MODULE_4__.Set,\n  Sound: _sound__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n  /**\n   * Initialize main application and player.\n   */\n  init(props = null, {\n    hotReload = false,\n    el = null,\n    config = null,\n    playerConfig = null,\n    initApp = true,\n    initPlayer = true,\n    loader = null,\n    playerLoader = null\n  } = {}) {\n    if (initPlayer) {\n      playerConfig = playerConfig || _app__WEBPACK_IMPORTED_MODULE_1__.PlayerApp;\n      playerLoader = playerLoader || new _vueLoader__WEBPACK_IMPORTED_MODULE_2__[\"default\"](playerConfig);\n      playerLoader.enable(false);\n      this.playerLoader = playerLoader;\n    }\n    if (initApp) {\n      config = config || window.App || _app__WEBPACK_IMPORTED_MODULE_1__[\"default\"];\n      config.el = el || config.el;\n      loader = loader || new _vueLoader__WEBPACK_IMPORTED_MODULE_2__[\"default\"]({\n        el,\n        props,\n        ...config\n      });\n      loader.enable(hotReload);\n      this.loader = loader;\n    }\n  },\n  /**\n   * Filter navbar dropdown menu items\n   */\n  filter_menu(event) {\n    var filter = new RegExp(event.target.value, 'gi');\n    var container = event.target.closest('.navbar-dropdown');\n    if (event.target.value) for (let item of container.querySelectorAll('a.navbar-item')) item.style.display = item.innerHTML.search(filter) == -1 ? 'none' : null;else for (let item of container.querySelectorAll('a.navbar-item')) item.style.display = null;\n  },\n  pickDate(url, date) {\n    url = `${url}?date=${date.id}`;\n    this.loader.pageLoad.load(url);\n  }\n};\n\n//# sourceURL=webpack://aircox-assets/./src/index.js?");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/***/ }),
 | 
					/***/ }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@
 | 
				
			|||||||
            {% if diffusions %}
 | 
					            {% if diffusions %}
 | 
				
			||||||
            <div class="card-grid">
 | 
					            <div class="card-grid">
 | 
				
			||||||
                {% for obj in diffusions %}
 | 
					                {% for obj in diffusions %}
 | 
				
			||||||
                    {% page_widget "card" obj.episode diffusion=obj timetable=True admin=True tag_class="small" %}
 | 
					                    {% page_widget "card" obj.episode diffusion=obj timetable=True admin=True tag_class="" %}
 | 
				
			||||||
                {% endfor %}
 | 
					                {% endfor %}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            {% else %}
 | 
					            {% else %}
 | 
				
			||||||
 | 
				
			|||||||
@ -61,6 +61,22 @@ Usefull context:
 | 
				
			|||||||
                        aria-label="{% translate "Main menu" %}">
 | 
					                        aria-label="{% translate "Main menu" %}">
 | 
				
			||||||
                    </a-switch>
 | 
					                    </a-switch>
 | 
				
			||||||
                    <div class="nav-menu">
 | 
					                    <div class="nav-menu">
 | 
				
			||||||
 | 
					                        {% for nav_item, secondary in nav_menu %}
 | 
				
			||||||
 | 
					                        <a class="nav-item" href="{{ nav_item.get_url }}">
 | 
				
			||||||
 | 
					                            {{ nav_item.get_label }}
 | 
				
			||||||
 | 
					                        </a>
 | 
				
			||||||
 | 
					                        {% if secondary %}
 | 
				
			||||||
 | 
					                        <div class="nav secondary">
 | 
				
			||||||
 | 
					                            {% for label, url in secondary %}
 | 
				
			||||||
 | 
					                            <a href="{{ url }}" class="nav-item">
 | 
				
			||||||
 | 
					                                {{ label }}
 | 
				
			||||||
 | 
					                            </a>
 | 
				
			||||||
 | 
					                            {% endfor %}
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        {% endif %}
 | 
				
			||||||
 | 
					                        {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        {% comment %}
 | 
				
			||||||
                        {% block nav-primary-menu %}
 | 
					                        {% block nav-primary-menu %}
 | 
				
			||||||
                        {% nav_items "top" css_class="nav-item" active_class="active" as items %}
 | 
					                        {% nav_items "top" css_class="nav-item" active_class="active" as items %}
 | 
				
			||||||
                        {% for item, render in items %}
 | 
					                        {% for item, render in items %}
 | 
				
			||||||
@ -72,6 +88,7 @@ Usefull context:
 | 
				
			|||||||
                        </a>
 | 
					                        </a>
 | 
				
			||||||
                        {% endif %}
 | 
					                        {% endif %}
 | 
				
			||||||
                        {% endblock %}
 | 
					                        {% endblock %}
 | 
				
			||||||
 | 
					                        {% endcomment %}
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    {% endblock %}
 | 
					                    {% endblock %}
 | 
				
			||||||
                </nav>
 | 
					                </nav>
 | 
				
			||||||
@ -79,6 +96,14 @@ Usefull context:
 | 
				
			|||||||
                {% block secondary-nav %}{% endblock %}
 | 
					                {% block secondary-nav %}{% endblock %}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {% spaceless %}
 | 
				
			||||||
 | 
					            {% block breadcrumbs-container %}
 | 
				
			||||||
 | 
					            <div class="breadcrumbs container">
 | 
				
			||||||
 | 
					                {% block breadcrumbs %}{% endblock %}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            {% endblock %}
 | 
				
			||||||
 | 
					            {% endspaceless %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {% block main-container %}
 | 
					            {% block main-container %}
 | 
				
			||||||
            <main class="page">
 | 
					            <main class="page">
 | 
				
			||||||
            {% block main %}
 | 
					            {% block main %}
 | 
				
			||||||
@ -99,20 +124,6 @@ Usefull context:
 | 
				
			|||||||
                        {% block subtitle %}
 | 
					                        {% block subtitle %}
 | 
				
			||||||
                        {% if subtitle %}
 | 
					                        {% if subtitle %}
 | 
				
			||||||
                            {{ subtitle }}
 | 
					                            {{ subtitle }}
 | 
				
			||||||
                        {% elif parent and parent.is_published %}
 | 
					 | 
				
			||||||
                            <a href="{{ parent.get_absolute_url|escape }}" class="heading subtitle">
 | 
					 | 
				
			||||||
                                <span class="icon">
 | 
					 | 
				
			||||||
                                    <i class="fa fa-angles-right"></i>
 | 
					 | 
				
			||||||
                                </span>
 | 
					 | 
				
			||||||
                                {{ parent.title }}
 | 
					 | 
				
			||||||
                            </a>
 | 
					 | 
				
			||||||
                        {% elif page and page.category %}
 | 
					 | 
				
			||||||
                            <a href="{% url "page-list" %}?category__id={{ page.category.id }}">
 | 
					 | 
				
			||||||
                                <span class="icon">
 | 
					 | 
				
			||||||
                                    <i class="fa fa-angles-right"></i>
 | 
					 | 
				
			||||||
                                </span>
 | 
					 | 
				
			||||||
                                {{ page.category.title }}
 | 
					 | 
				
			||||||
                            </a>
 | 
					 | 
				
			||||||
                        {% endif %}
 | 
					                        {% endif %}
 | 
				
			||||||
                        {% endblock %}
 | 
					                        {% endblock %}
 | 
				
			||||||
                        </span>
 | 
					                        </span>
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
{% block head_title %}{{ station.name }}{% endblock %}
 | 
					{% block head_title %}{{ station.name }}{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block breadcrumbs-container %}{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content-container %}
 | 
					{% block content-container %}
 | 
				
			||||||
{{ block.super }}
 | 
					{{ block.super }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -40,7 +42,7 @@
 | 
				
			|||||||
    {% endfor %}
 | 
					    {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <nav class="nav-urls">
 | 
					    <nav class="nav-urls">
 | 
				
			||||||
        <a href="{% url "log-list" %}"
 | 
					        <a href="{% url "timetable-list" %}"
 | 
				
			||||||
            aria-label="{% translate "Show all program's for today" %}">
 | 
					            aria-label="{% translate "Show all program's for today" %}">
 | 
				
			||||||
            {% translate "Today" %}
 | 
					            {% translate "Today" %}
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
 | 
				
			|||||||
@ -23,6 +23,22 @@ Context:
 | 
				
			|||||||
{% endif %}
 | 
					{% endif %}
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block breadcrumbs %}
 | 
				
			||||||
 | 
					{% if parent %}
 | 
				
			||||||
 | 
					    {% include "./widgets/breadcrumbs.html" with page=parent %}
 | 
				
			||||||
 | 
					    {% if page %}
 | 
				
			||||||
 | 
					    <a href="{% url page.list_url_name parent_slug=parent.slug %}">
 | 
				
			||||||
 | 
					        {{ page|verbose_name:True }}
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					{% elif page %}
 | 
				
			||||||
 | 
					    {% include "./widgets/breadcrumbs.html" with page=page no_title=True %}
 | 
				
			||||||
 | 
					{% endif %}
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block main %}
 | 
					{% block main %}
 | 
				
			||||||
{{ block.super }}
 | 
					{{ block.super }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -31,21 +47,9 @@ Context:
 | 
				
			|||||||
<section class="container">
 | 
					<section class="container">
 | 
				
			||||||
    {% with models=object|verbose_name:True %}
 | 
					    {% with models=object|verbose_name:True %}
 | 
				
			||||||
    <h3 class="title is-3">{% blocktranslate %}Related {{models}}{% endblocktranslate %}</h3>
 | 
					    <h3 class="title is-3">{% blocktranslate %}Related {{models}}{% endblocktranslate %}</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {% include "./widgets/carousel.html" with objects=related_objects url_name=object.list_url_name url_category=object.category %}
 | 
				
			||||||
    {% endwith %}
 | 
					    {% endwith %}
 | 
				
			||||||
 | 
					 | 
				
			||||||
    <a-carousel section-class="card-grid">
 | 
					 | 
				
			||||||
        {% for object in related_objects %}
 | 
					 | 
				
			||||||
        {% page_widget "card" object %}
 | 
					 | 
				
			||||||
        {% endfor %}
 | 
					 | 
				
			||||||
    </a-carousel>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    {% if related_url %}
 | 
					 | 
				
			||||||
    <nav class="nav-urls">
 | 
					 | 
				
			||||||
        <a href="{{ related_url }}">
 | 
					 | 
				
			||||||
            {% translate "See more" %}
 | 
					 | 
				
			||||||
        </a>
 | 
					 | 
				
			||||||
    </nav>
 | 
					 | 
				
			||||||
    {% endif %}
 | 
					 | 
				
			||||||
{% endif %}
 | 
					{% endif %}
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,12 +3,14 @@
 | 
				
			|||||||
{% load i18n aircox %}
 | 
					{% load i18n aircox %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block secondary-nav %}
 | 
					{% block secondary-nav %}
 | 
				
			||||||
{% if view.categories %}
 | 
					{% if categories %}
 | 
				
			||||||
<nav class="nav secondary">
 | 
					<nav class="nav secondary">
 | 
				
			||||||
    <div class="nav-menu nav-categories">
 | 
					    <div class="nav-menu nav-categories">
 | 
				
			||||||
        {% for id, title in view.categories.items %}
 | 
					        {% for cat in categories %}
 | 
				
			||||||
        <a class="nav-item{% if category_id == id or parent and parent.category_id == id %} active{% endif %}"
 | 
					        <a class="nav-item{% if cat == category %} active{% endif %}"
 | 
				
			||||||
            href="?category__id={{ id }}">{{ title }}</a>
 | 
					            href="{% url request.resolver_match.url_name category_slug=cat.slug %}">
 | 
				
			||||||
 | 
					            {{ cat.title }}
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
        {% endfor %}
 | 
					        {% endfor %}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <a-switch class="button burger"
 | 
					    <a-switch class="button burger"
 | 
				
			||||||
@ -19,6 +21,18 @@
 | 
				
			|||||||
{% endif %}
 | 
					{% endif %}
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}
 | 
				
			||||||
 | 
					{% if parent %}{{ parent.title }}
 | 
				
			||||||
 | 
					{% else %}{{ block.super }}
 | 
				
			||||||
 | 
					{% endif %}
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					    {% if parent %}{{ parent.content|safe }}
 | 
				
			||||||
 | 
					    {% else %}{{ block.super }}
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block header %}
 | 
					{% block header %}
 | 
				
			||||||
{% if page and not object %}
 | 
					{% if page and not object %}
 | 
				
			||||||
    {% with page as object %}
 | 
					    {% with page as object %}
 | 
				
			||||||
@ -28,3 +42,19 @@
 | 
				
			|||||||
    {{ block.super }}
 | 
					    {{ block.super }}
 | 
				
			||||||
{% endif %}
 | 
					{% endif %}
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block breadcrumbs %}
 | 
				
			||||||
 | 
					{% if parent %}
 | 
				
			||||||
 | 
					    {% include "./widgets/breadcrumbs.html" with page=parent %}
 | 
				
			||||||
 | 
					    <a href="{{ request.path }}">{{ model|verbose_name:True }}</a>
 | 
				
			||||||
 | 
					{% elif page %}
 | 
				
			||||||
 | 
					    <a href="{{ request.path }}">{{ page.title }}</a>
 | 
				
			||||||
 | 
					{% else %}
 | 
				
			||||||
 | 
					    <a href="{% url request.resolver_match.url_name %}">{{ model|verbose_name:True }}</a>
 | 
				
			||||||
 | 
					    {% if category %}
 | 
				
			||||||
 | 
					    <a href="{% url request.resolver_match.url_name category_slug=category.slug %}">
 | 
				
			||||||
 | 
					        {{ category.title }}
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					{% endif %}
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										31
									
								
								aircox/templates/aircox/timetable_list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								aircox/templates/aircox/timetable_list.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					{% extends "aircox/page_list.html" %}
 | 
				
			||||||
 | 
					{% comment %}List of diffusions as a timetable{% endcomment %}
 | 
				
			||||||
 | 
					{% load i18n aircox humanize %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block secondary-nav %}
 | 
				
			||||||
 | 
					<nav class="nav secondary">
 | 
				
			||||||
 | 
					    {% include "./widgets/dates_menu.html" with url_name=view.redirect_date_url %}
 | 
				
			||||||
 | 
					</nav>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block breadcrumbs %}
 | 
				
			||||||
 | 
					{{ block.super }}
 | 
				
			||||||
 | 
					<a href="{% url "timetable-list" date=date %}">{{ date|date:"l d F Y" }}</a>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block pages_list %}
 | 
				
			||||||
 | 
					{% with hide_schedule=True %}
 | 
				
			||||||
 | 
					<section role="list" class="list">
 | 
				
			||||||
 | 
					    {% for object in object_list %}
 | 
				
			||||||
 | 
					    {% if object.episode %}
 | 
				
			||||||
 | 
					        {% page_widget "item" object.episode diffusion=object timetable=True %}
 | 
				
			||||||
 | 
					    {% else %}
 | 
				
			||||||
 | 
					        {% page_widget "item" object timetable=True %}
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					    {% endfor %}
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					{% endwith %}
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										15
									
								
								aircox/templates/aircox/widgets/breadcrumbs.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								aircox/templates/aircox/widgets/breadcrumbs.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					{% load aircox %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<a href="{% url page.list_url_name %}">
 | 
				
			||||||
 | 
					    {{ page|verbose_name:True }}
 | 
				
			||||||
 | 
					</a>
 | 
				
			||||||
 | 
					{% if page.category and not no_cat %}
 | 
				
			||||||
 | 
					<a href="{% url page.list_url_name category_slug=page.category.slug %}">
 | 
				
			||||||
 | 
					    {{ page.category.title }}
 | 
				
			||||||
 | 
					</a>
 | 
				
			||||||
 | 
					{% endif %}
 | 
				
			||||||
 | 
					{% if not no_title %}
 | 
				
			||||||
 | 
					<a href="{{ page.get_absolute_url }}">
 | 
				
			||||||
 | 
					    {{ page.title|truncatechars:24 }}
 | 
				
			||||||
 | 
					</a>
 | 
				
			||||||
 | 
					{% endif %}
 | 
				
			||||||
@ -16,7 +16,9 @@ Context:
 | 
				
			|||||||
{% if url_name %}
 | 
					{% if url_name %}
 | 
				
			||||||
<nav class="nav-urls">
 | 
					<nav class="nav-urls">
 | 
				
			||||||
    {% if url_parent %}
 | 
					    {% if url_parent %}
 | 
				
			||||||
    <a href="{% url url_name parent_slug=program.slug %}">
 | 
					    <a href="{% url url_name parent_slug=url_parent.slug %}">
 | 
				
			||||||
 | 
					    {% elif url_category %}
 | 
				
			||||||
 | 
					    <a href="{% url url_name category_slug=url_category.slug %}">
 | 
				
			||||||
    {% else %}
 | 
					    {% else %}
 | 
				
			||||||
    <a href="{% url url_name %}">
 | 
					    <a href="{% url url_name %}">
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
 | 
				
			|||||||
@ -40,7 +40,8 @@ The audio player
 | 
				
			|||||||
            <h4 v-else-if="live && current && current.data.type == 'diffusion'"
 | 
					            <h4 v-else-if="live && current && current.data.type == 'diffusion'"
 | 
				
			||||||
                class="title"
 | 
					                class="title"
 | 
				
			||||||
                aria-description="{% translate "Diffusion currently on air" %}">
 | 
					                aria-description="{% translate "Diffusion currently on air" %}">
 | 
				
			||||||
                    <a :href="current.data.url">[[ current.data.title ]]</a>
 | 
					                    <a :href="current.data.url" v-if="current.data.url">[[ current.data.title ]]</a>
 | 
				
			||||||
 | 
					                    <template v-else>[[ current.data.title ]]</template>
 | 
				
			||||||
                </h4>
 | 
					                </h4>
 | 
				
			||||||
            <h4 v-else class="title is-4" aria-description="{% translate "Currently playing" %}">
 | 
					            <h4 v-else class="title is-4" aria-description="{% translate "Currently playing" %}">
 | 
				
			||||||
                {{ request.station.name }}
 | 
					                {{ request.station.name }}
 | 
				
			||||||
 | 
				
			|||||||
@ -38,38 +38,47 @@ api = [
 | 
				
			|||||||
urls = [
 | 
					urls = [
 | 
				
			||||||
    path("", views.HomeView.as_view(), name="home"),
 | 
					    path("", views.HomeView.as_view(), name="home"),
 | 
				
			||||||
    path("api/", include((api, "aircox"), namespace="api")),
 | 
					    path("api/", include((api, "aircox"), namespace="api")),
 | 
				
			||||||
    # path('', views.PageDetailView.as_view(model=models.Article),
 | 
					    # ---- ---- objects views
 | 
				
			||||||
    #     name='home'),
 | 
					    # ---- articles
 | 
				
			||||||
    path(
 | 
					    path(
 | 
				
			||||||
        _("articles/"),
 | 
					        _("articles/"),
 | 
				
			||||||
        views.ArticleListView.as_view(model=models.Article),
 | 
					        views.ArticleListView.as_view(model=models.Article),
 | 
				
			||||||
        name="article-list",
 | 
					        name="article-list",
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
 | 
					    path(
 | 
				
			||||||
 | 
					        _("articles/c/<slug:category_slug>/"), views.ArticleListView.as_view(model=models.Article), name="article-list"
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    path(
 | 
					    path(
 | 
				
			||||||
        _("articles/<slug:slug>/"),
 | 
					        _("articles/<slug:slug>/"),
 | 
				
			||||||
        views.ArticleDetailView.as_view(),
 | 
					        views.ArticleDetailView.as_view(),
 | 
				
			||||||
        name="article-detail",
 | 
					        name="article-detail",
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
 | 
					    # ---- episodes
 | 
				
			||||||
    path(_("episodes/"), views.EpisodeListView.as_view(), name="episode-list"),
 | 
					    path(_("episodes/"), views.EpisodeListView.as_view(), name="episode-list"),
 | 
				
			||||||
 | 
					    path(_("episodes/c/<slug:category_slug>"), views.EpisodeListView.as_view(), name="episode-list"),
 | 
				
			||||||
    path(
 | 
					    path(
 | 
				
			||||||
        _("episodes/<slug:slug>/"),
 | 
					        _("episodes/<slug:slug>/"),
 | 
				
			||||||
        views.EpisodeDetailView.as_view(),
 | 
					        views.EpisodeDetailView.as_view(),
 | 
				
			||||||
        name="episode-detail",
 | 
					        name="episode-detail",
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    path(_("podcasts/"), views.PodcastListView.as_view(), name="podcast-list"),
 | 
					    path(_("podcasts/"), views.PodcastListView.as_view(), name="podcast-list"),
 | 
				
			||||||
    path(_("podcasts/<slug:parent_slug>"), views.PodcastListView.as_view(), name="podcast-list"),
 | 
					    path(_("podcasts/c/<slug:category_slug>/"), views.PodcastListView.as_view(), name="podcast-list"),
 | 
				
			||||||
    path(_("week/"), views.DiffusionListView.as_view(), name="diffusion-list"),
 | 
					    # ---- timetable
 | 
				
			||||||
 | 
					    path(_("timetable/"), views.TimeTableView.as_view(), name="timetable-list"),
 | 
				
			||||||
    path(
 | 
					    path(
 | 
				
			||||||
        _("week/<date:date>/"),
 | 
					        _("timetable/<date:date>/"),
 | 
				
			||||||
        views.DiffusionListView.as_view(),
 | 
					        views.TimeTableView.as_view(),
 | 
				
			||||||
        name="diffusion-list",
 | 
					        name="timetable-list",
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    path(_("logs/"), views.LogListView.as_view(), name="log-list"),
 | 
					    # ---- pages
 | 
				
			||||||
    path(_("logs/<date:date>/"), views.LogListView.as_view(), name="log-list"),
 | 
					 | 
				
			||||||
    # path('<page_path:path>', views.route_page, name='page'),
 | 
					 | 
				
			||||||
    path(
 | 
					    path(
 | 
				
			||||||
        _("publications/"),
 | 
					        _("publications/"),
 | 
				
			||||||
        views.PageListView.as_view(model=models.Page, attach_to_value=models.StaticPage.ATTACH_TO_PAGES),
 | 
					        views.PageListView.as_view(model=models.Page, attach_to_value=models.StaticPage.Target.PAGES),
 | 
				
			||||||
 | 
					        name="page-list",
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    path(
 | 
				
			||||||
 | 
					        _("publications/c/<slug:category_slug>"),
 | 
				
			||||||
 | 
					        views.PageListView.as_view(model=models.Page, attach_to_value=models.StaticPage.Target.PAGES),
 | 
				
			||||||
        name="page-list",
 | 
					        name="page-list",
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    path(
 | 
					    path(
 | 
				
			||||||
@ -88,27 +97,18 @@ urls = [
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
        name="static-page-detail",
 | 
					        name="static-page-detail",
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
 | 
					    # ---- programs
 | 
				
			||||||
    path(_("programs/"), views.ProgramListView.as_view(), name="program-list"),
 | 
					    path(_("programs/"), views.ProgramListView.as_view(), name="program-list"),
 | 
				
			||||||
 | 
					    path(_("programs/c/<slug:category_slug>"), views.ProgramListView.as_view(), name="program-list"),
 | 
				
			||||||
    path(
 | 
					    path(
 | 
				
			||||||
        _("programs/<slug:slug>/"),
 | 
					        _("programs/<slug:slug>/"),
 | 
				
			||||||
        views.ProgramDetailView.as_view(),
 | 
					        views.ProgramDetailView.as_view(),
 | 
				
			||||||
        name="program-detail",
 | 
					        name="program-detail",
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    path(
 | 
					    path(_("program/<slug:parent_slug>/articles"), views.ArticleListView.as_view(), name="article-list"),
 | 
				
			||||||
        _("programs/<slug:parent_slug>/episodes/"),
 | 
					    path(_("program/<slug:parent_slug>/podcasts"), views.PodcastListView.as_view(), name="podcast-list"),
 | 
				
			||||||
        views.EpisodeListView.as_view(),
 | 
					    path(_("program/<slug:parent_slug>/episodes"), views.EpisodeListView.as_view(), name="episode-list"),
 | 
				
			||||||
        name="episode-list",
 | 
					    path(_("program/<slug:parent_slug>/diffusions"), views.DiffusionListView.as_view(), name="diffusion-list"),
 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    path(
 | 
					 | 
				
			||||||
        _("programs/<slug:parent_slug>/articles/"),
 | 
					 | 
				
			||||||
        views.ArticleListView.as_view(),
 | 
					 | 
				
			||||||
        name="article-list",
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    path(
 | 
					 | 
				
			||||||
        _("programs/<slug:parent_slug>/publications/"),
 | 
					 | 
				
			||||||
        views.ProgramPageListView.as_view(),
 | 
					 | 
				
			||||||
        name="program-page-list",
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    path(
 | 
					    path(
 | 
				
			||||||
        "errors/no-station",
 | 
					        "errors/no-station",
 | 
				
			||||||
        views.errors.NoStationErrorView.as_view(),
 | 
					        views.errors.NoStationErrorView.as_view(),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
from . import admin, errors
 | 
					from . import admin, errors
 | 
				
			||||||
from .article import ArticleDetailView, ArticleListView
 | 
					from .article import ArticleDetailView, ArticleListView
 | 
				
			||||||
from .base import BaseAPIView, BaseView
 | 
					from .base import BaseAPIView, BaseView
 | 
				
			||||||
from .diffusion import DiffusionListView
 | 
					from .diffusion import DiffusionListView, TimeTableView
 | 
				
			||||||
from .episode import EpisodeDetailView, EpisodeListView, PodcastListView
 | 
					from .episode import EpisodeDetailView, EpisodeListView, PodcastListView
 | 
				
			||||||
from .home import HomeView
 | 
					from .home import HomeView
 | 
				
			||||||
from .log import LogListAPIView, LogListView
 | 
					from .log import LogListAPIView, LogListView
 | 
				
			||||||
@ -26,6 +26,7 @@ __all__ = (
 | 
				
			|||||||
    "BaseAPIView",
 | 
					    "BaseAPIView",
 | 
				
			||||||
    "BaseView",
 | 
					    "BaseView",
 | 
				
			||||||
    "DiffusionListView",
 | 
					    "DiffusionListView",
 | 
				
			||||||
 | 
					    "TimeTableView",
 | 
				
			||||||
    "EpisodeDetailView",
 | 
					    "EpisodeDetailView",
 | 
				
			||||||
    "EpisodeListView",
 | 
					    "EpisodeListView",
 | 
				
			||||||
    "PodcastListView",
 | 
					    "PodcastListView",
 | 
				
			||||||
@ -40,4 +41,15 @@ __all__ = (
 | 
				
			|||||||
    "ProgramListView",
 | 
					    "ProgramListView",
 | 
				
			||||||
    "ProgramPageDetailView",
 | 
					    "ProgramPageDetailView",
 | 
				
			||||||
    "ProgramPageListView",
 | 
					    "ProgramPageListView",
 | 
				
			||||||
 | 
					    "attached",
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					attached = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for key in __all__:
 | 
				
			||||||
 | 
					    view = globals().get(key)
 | 
				
			||||||
 | 
					    if key == "attached":
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					    if attach := getattr(view, "attach_to_value", None):
 | 
				
			||||||
 | 
					        attached[attach] = view
 | 
				
			||||||
 | 
				
			|||||||
@ -16,4 +16,4 @@ class ArticleListView(PageListView):
 | 
				
			|||||||
    model = Article
 | 
					    model = Article
 | 
				
			||||||
    has_headline = True
 | 
					    has_headline = True
 | 
				
			||||||
    parent_model = Program
 | 
					    parent_model = Program
 | 
				
			||||||
    attach_to_value = StaticPage.ATTACH_TO_ARTICLES
 | 
					    attach_to_value = StaticPage.Target.ARTICLES
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,26 @@ 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_nav_menu(self):
 | 
				
			||||||
 | 
					        menu = []
 | 
				
			||||||
 | 
					        for item in self.station.navitem_set.all():
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                if item.page:
 | 
				
			||||||
 | 
					                    view = item.page.get_related_view()
 | 
				
			||||||
 | 
					                    secondary = view and view.get_secondary_nav()
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    secondary = None
 | 
				
			||||||
 | 
					                menu.append((item, secondary))
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                import traceback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                traceback.print_exc()
 | 
				
			||||||
 | 
					                raise
 | 
				
			||||||
 | 
					        return menu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_secondary_nav(self):
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_related_queryset(self):
 | 
					    def get_related_queryset(self):
 | 
				
			||||||
        """Return a queryset of related pages or None."""
 | 
					        """Return a queryset of related pages or None."""
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
@ -46,6 +66,8 @@ class BaseView(TemplateResponseMixin, ContextMixin):
 | 
				
			|||||||
            kwargs["title"] = page.display_title
 | 
					            kwargs["title"] = page.display_title
 | 
				
			||||||
            kwargs["cover"] = page.cover and page.cover.url
 | 
					            kwargs["cover"] = page.cover and page.cover.url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if "nav_menu" not in kwargs:
 | 
				
			||||||
 | 
					            kwargs["nav_menu"] = self.get_nav_menu()
 | 
				
			||||||
        return super().get_context_data(**kwargs)
 | 
					        return super().get_context_data(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def dispatch(self, *args, **kwargs):
 | 
					    def dispatch(self, *args, **kwargs):
 | 
				
			||||||
 | 
				
			|||||||
@ -1,29 +1,54 @@
 | 
				
			|||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.urls import reverse
 | 
				
			||||||
from django.views.generic import ListView
 | 
					from django.views.generic import ListView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from aircox.models import Diffusion, StaticPage
 | 
					from aircox.models import Diffusion, Log, StaticPage
 | 
				
			||||||
from .base import BaseView
 | 
					from .base import BaseView
 | 
				
			||||||
from .mixins import AttachedToMixin, GetDateMixin
 | 
					from .mixins import AttachedToMixin, GetDateMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = ("DiffusionListView",)
 | 
					__all__ = ("DiffusionListView", "TimeTableView")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DiffusionListView(GetDateMixin, AttachedToMixin, BaseView, ListView):
 | 
					class BaseDiffusionListView(AttachedToMixin, BaseView, ListView):
 | 
				
			||||||
 | 
					    model = Diffusion
 | 
				
			||||||
 | 
					    queryset = Diffusion.objects.on_air().order_by("-start")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DiffusionListView(BaseDiffusionListView):
 | 
				
			||||||
    """View for timetables."""
 | 
					    """View for timetables."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    model = Diffusion
 | 
					    model = Diffusion
 | 
				
			||||||
    redirect_date_url = "diffusion-list"
 | 
					
 | 
				
			||||||
    attach_to_value = StaticPage.ATTACH_TO_DIFFUSIONS
 | 
					
 | 
				
			||||||
 | 
					class TimeTableView(GetDateMixin, BaseDiffusionListView):
 | 
				
			||||||
 | 
					    model = Diffusion
 | 
				
			||||||
 | 
					    redirect_date_url = "timetable-list"
 | 
				
			||||||
 | 
					    attach_to_value = StaticPage.Target.TIMETABLE
 | 
				
			||||||
 | 
					    template_name = "aircox/timetable_list.html"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_date(self):
 | 
					    def get_date(self):
 | 
				
			||||||
        date = super().get_date()
 | 
					        date = super().get_date()
 | 
				
			||||||
        return date if date is not None else datetime.date.today()
 | 
					        return date if date is not None else datetime.date.today()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_queryset(self):
 | 
					    def get_logs(self, date):
 | 
				
			||||||
        return super().get_queryset().date(self.date).order_by("start")
 | 
					        return Log.objects.on_air().date(self.date).filter(track__isnull=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_context_data(self, **kwargs):
 | 
					    def get_queryset(self):
 | 
				
			||||||
 | 
					        return super().get_queryset().date(self.date)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_secondary_nav(cls):
 | 
				
			||||||
 | 
					        date = datetime.date.today()
 | 
				
			||||||
 | 
					        start = date - datetime.timedelta(days=date.weekday())
 | 
				
			||||||
 | 
					        dates = [start + datetime.timedelta(days=i) for i in range(0, 7)]
 | 
				
			||||||
 | 
					        return tuple((date.strftime("%A %d"), reverse("timetable-list", kwargs={"date": date})) for date in dates)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_context_data(self, object_list=None, **kwargs):
 | 
				
			||||||
        start = self.date - datetime.timedelta(days=self.date.weekday())
 | 
					        start = self.date - datetime.timedelta(days=self.date.weekday())
 | 
				
			||||||
        dates = [start + datetime.timedelta(days=i) for i in range(0, 7)]
 | 
					        dates = [start + datetime.timedelta(days=i) for i in range(0, 7)]
 | 
				
			||||||
        return super().get_context_data(date=self.date, dates=dates, **kwargs)
 | 
					
 | 
				
			||||||
 | 
					        if object_list is None:
 | 
				
			||||||
 | 
					            logs = self.get_logs(self.date)
 | 
				
			||||||
 | 
					            object_list = Log.merge_diffusions(logs, self.object_list)
 | 
				
			||||||
 | 
					        return super().get_context_data(date=self.date, dates=dates, object_list=object_list, **kwargs)
 | 
				
			||||||
 | 
				
			|||||||
@ -33,9 +33,9 @@ class EpisodeListView(PageListView):
 | 
				
			|||||||
    model = Episode
 | 
					    model = Episode
 | 
				
			||||||
    filterset_class = EpisodeFilters
 | 
					    filterset_class = EpisodeFilters
 | 
				
			||||||
    parent_model = Program
 | 
					    parent_model = Program
 | 
				
			||||||
    attach_to_value = StaticPage.ATTACH_TO_EPISODES
 | 
					    attach_to_value = StaticPage.Target.EPISODES
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PodcastListView(EpisodeListView):
 | 
					class PodcastListView(EpisodeListView):
 | 
				
			||||||
    attach_to_value = StaticPage.ATTACH_TO_PODCASTS
 | 
					    attach_to_value = StaticPage.Target.PODCASTS
 | 
				
			||||||
    queryset = Episode.objects.published().with_podcasts().order_by("-pub_date")
 | 
					    queryset = Episode.objects.published().with_podcasts().order_by("-pub_date")
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ from .mixins import AttachedToMixin
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class HomeView(AttachedToMixin, BaseView, ListView):
 | 
					class HomeView(AttachedToMixin, BaseView, ListView):
 | 
				
			||||||
    template_name = "aircox/home.html"
 | 
					    template_name = "aircox/home.html"
 | 
				
			||||||
    attach_to_value = StaticPage.ATTACH_TO_HOME
 | 
					    attach_to_value = StaticPage.Target.HOME
 | 
				
			||||||
    model = Diffusion
 | 
					    model = Diffusion
 | 
				
			||||||
    queryset = Diffusion.objects.on_air().select_related("episode").order_by("-start")
 | 
					    queryset = Diffusion.objects.on_air().select_related("episode").order_by("-start")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,12 +6,12 @@ from django.views.decorators.cache import cache_page
 | 
				
			|||||||
from django.views.generic import ListView
 | 
					from django.views.generic import ListView
 | 
				
			||||||
from rest_framework.generics import ListAPIView
 | 
					from rest_framework.generics import ListAPIView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ..models import Diffusion, Log, StaticPage
 | 
					from ..models import Diffusion, Log
 | 
				
			||||||
from ..serializers import LogInfo, LogInfoSerializer
 | 
					from ..serializers import LogInfo, LogInfoSerializer
 | 
				
			||||||
from .base import BaseAPIView, BaseView
 | 
					from .base import BaseAPIView, BaseView
 | 
				
			||||||
from .mixins import AttachedToMixin, GetDateMixin
 | 
					from .mixins import AttachedToMixin, GetDateMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = ["LogListMixin", "LogListView"]
 | 
					__all__ = ("LogListMixin", "LogListView", "LogListAPIView")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LogListMixin(GetDateMixin):
 | 
					class LogListMixin(GetDateMixin):
 | 
				
			||||||
@ -62,7 +62,6 @@ class LogListView(AttachedToMixin, BaseView, LogListMixin, ListView):
 | 
				
			|||||||
    `request.GET`, defaults to today)."""
 | 
					    `request.GET`, defaults to today)."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    redirect_date_url = "log-list"
 | 
					    redirect_date_url = "log-list"
 | 
				
			||||||
    attach_to_value = StaticPage.ATTACH_TO_LOGS
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_date(self):
 | 
					    def get_date(self):
 | 
				
			||||||
        date = super().get_date()
 | 
					        date = super().get_date()
 | 
				
			||||||
 | 
				
			|||||||
@ -44,16 +44,16 @@ class ParentMixin:
 | 
				
			|||||||
    parent = None
 | 
					    parent = None
 | 
				
			||||||
    """Parent page object."""
 | 
					    """Parent page object."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_parent(self, request, *args, **kwargs):
 | 
					    def get_parent(self, request, **kwargs):
 | 
				
			||||||
        if self.parent_model is None or self.parent_url_kwarg not in kwargs:
 | 
					        if self.parent_model is None or self.parent_url_kwarg not in kwargs:
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        lookup = {self.parent_field: kwargs[self.parent_url_kwarg]}
 | 
					        lookup = {self.parent_field: kwargs[self.parent_url_kwarg]}
 | 
				
			||||||
        return get_object_or_404(self.parent_model.objects.select_related("cover"), **lookup)
 | 
					        return get_object_or_404(self.parent_model.objects.select_related("cover"), **lookup)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get(self, request, *args, **kwargs):
 | 
					    def dispatch(self, request, *args, **kwargs):
 | 
				
			||||||
        self.parent = self.get_parent(request, *args, **kwargs)
 | 
					        self.parent = self.get_parent(request, **kwargs)
 | 
				
			||||||
        return super().get(request, *args, **kwargs)
 | 
					        return super().dispatch(request, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_queryset(self):
 | 
					    def get_queryset(self):
 | 
				
			||||||
        if self.parent is not None:
 | 
					        if self.parent is not None:
 | 
				
			||||||
@ -61,9 +61,10 @@ class ParentMixin:
 | 
				
			|||||||
        return super().get_queryset()
 | 
					        return super().get_queryset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_context_data(self, **kwargs):
 | 
					    def get_context_data(self, **kwargs):
 | 
				
			||||||
        self.parent = kwargs.setdefault("parent", self.parent)
 | 
					        parent = kwargs.setdefault("parent", self.parent)
 | 
				
			||||||
        if self.parent is not None:
 | 
					
 | 
				
			||||||
            kwargs.setdefault("cover", self.parent.cover.url)
 | 
					        if parent is not None:
 | 
				
			||||||
 | 
					            kwargs.setdefault("cover", parent.cover.url)
 | 
				
			||||||
        return super().get_context_data(**kwargs)
 | 
					        return super().get_context_data(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,12 @@
 | 
				
			|||||||
from django.http import Http404, HttpResponse
 | 
					from django.http import Http404, HttpResponse
 | 
				
			||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
from django.views.generic import DetailView, ListView
 | 
					from django.views.generic import DetailView, ListView
 | 
				
			||||||
 | 
					from django.urls import reverse
 | 
				
			||||||
from honeypot.decorators import check_honeypot
 | 
					from honeypot.decorators import check_honeypot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ..filters import PageFilters
 | 
					from ..filters import PageFilters
 | 
				
			||||||
from ..forms import CommentForm
 | 
					from ..forms import CommentForm
 | 
				
			||||||
from ..models import Comment
 | 
					from ..models import Comment, Category
 | 
				
			||||||
from ..utils import Redirect
 | 
					from ..utils import Redirect
 | 
				
			||||||
from .base import BaseView
 | 
					from .base import BaseView
 | 
				
			||||||
from .mixins import AttachedToMixin, FiltersMixin, ParentMixin
 | 
					from .mixins import AttachedToMixin, FiltersMixin, ParentMixin
 | 
				
			||||||
@ -18,7 +19,25 @@ __all__ = [
 | 
				
			|||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BasePageListView(AttachedToMixin, ParentMixin, BaseView, ListView):
 | 
					class BasePageMixin:
 | 
				
			||||||
 | 
					    category = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_category(self, page, **kwargs):
 | 
				
			||||||
 | 
					        if page:
 | 
				
			||||||
 | 
					            if getattr(page, "category_id", None):
 | 
				
			||||||
 | 
					                return page.category
 | 
				
			||||||
 | 
					            if page.parent_id:
 | 
				
			||||||
 | 
					                return self.get_category(page.parent_subclass)
 | 
				
			||||||
 | 
					        if slug := self.kwargs.get("category_slug"):
 | 
				
			||||||
 | 
					            return Category.objects.get(slug=slug)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_context_data(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        kwargs.setdefault("category", self.category)
 | 
				
			||||||
 | 
					        return super().get_context_data(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BasePageListView(AttachedToMixin, BasePageMixin, ParentMixin, BaseView, ListView):
 | 
				
			||||||
    """Base view class for BasePage list."""
 | 
					    """Base view class for BasePage list."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    template_name = "aircox/basepage_list.html"
 | 
					    template_name = "aircox/basepage_list.html"
 | 
				
			||||||
@ -26,11 +45,18 @@ class BasePageListView(AttachedToMixin, ParentMixin, BaseView, ListView):
 | 
				
			|||||||
    paginate_by = 30
 | 
					    paginate_by = 30
 | 
				
			||||||
    has_headline = True
 | 
					    has_headline = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def dispatch(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        return super().dispatch(request, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get(self, *args, **kwargs):
 | 
					    def get(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.category = self.get_category(self.parent)
 | 
				
			||||||
        return super().get(*args, **kwargs)
 | 
					        return super().get(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_queryset(self):
 | 
					    def get_queryset(self):
 | 
				
			||||||
        return super().get_queryset().select_subclasses().published().select_related("cover")
 | 
					        query = super().get_queryset().select_subclasses().published().select_related("cover")
 | 
				
			||||||
 | 
					        if self.category:
 | 
				
			||||||
 | 
					            query = query.filter(category=self.category)
 | 
				
			||||||
 | 
					        return query
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_context_data(self, **kwargs):
 | 
					    def get_context_data(self, **kwargs):
 | 
				
			||||||
        kwargs.setdefault("has_headline", self.has_headline)
 | 
					        kwargs.setdefault("has_headline", self.has_headline)
 | 
				
			||||||
@ -48,12 +74,15 @@ class BasePageListView(AttachedToMixin, ParentMixin, BaseView, ListView):
 | 
				
			|||||||
        return context
 | 
					        return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BasePageDetailView(BaseView, DetailView):
 | 
					class BasePageDetailView(BasePageMixin, BaseView, DetailView):
 | 
				
			||||||
    """Base view class for BasePage."""
 | 
					    """Base view class for BasePage."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    template_name = "aircox/basepage_detail.html"
 | 
					    template_name = "aircox/basepage_detail.html"
 | 
				
			||||||
    context_object_name = "page"
 | 
					    context_object_name = "page"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        return super().get(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_context_data(self, **kwargs):
 | 
					    def get_context_data(self, **kwargs):
 | 
				
			||||||
        if self.object.cover:
 | 
					        if self.object.cover:
 | 
				
			||||||
            kwargs.setdefault("cover", self.object.cover.url)
 | 
					            kwargs.setdefault("cover", self.object.cover.url)
 | 
				
			||||||
@ -81,6 +110,8 @@ class BasePageDetailView(BaseView, DetailView):
 | 
				
			|||||||
            if redirect_url:
 | 
					            if redirect_url:
 | 
				
			||||||
                raise Redirect(redirect_url)
 | 
					                raise Redirect(redirect_url)
 | 
				
			||||||
            raise Http404("%s not found" % self.model._meta.verbose_name)
 | 
					            raise Http404("%s not found" % self.model._meta.verbose_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.category = self.get_category(obj)
 | 
				
			||||||
        return obj
 | 
					        return obj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_page(self):
 | 
					    def get_page(self):
 | 
				
			||||||
@ -105,26 +136,22 @@ class PageListView(FiltersMixin, BasePageListView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def get_queryset(self):
 | 
					    def get_queryset(self):
 | 
				
			||||||
        qs = super().get_queryset().select_related("category").order_by("-pub_date")
 | 
					        qs = super().get_queryset().select_related("category").order_by("-pub_date")
 | 
				
			||||||
 | 
					        cat_ids = self.model.objects.published().values_list("category_id", flat=True)
 | 
				
			||||||
 | 
					        self.categories = Category.objects.filter(id__in=cat_ids)
 | 
				
			||||||
        return qs
 | 
					        return qs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_secondary_nav(cls):
 | 
				
			||||||
 | 
					        cat_ids = cls.model.objects.published().values_list("category_id", flat=True)
 | 
				
			||||||
 | 
					        categories = Category.objects.filter(id__in=cat_ids)
 | 
				
			||||||
 | 
					        return tuple(
 | 
				
			||||||
 | 
					            (category.title, reverse(cls.model.list_url_name, kwargs={"category_slug": category.slug}))
 | 
				
			||||||
 | 
					            for category in categories
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_context_data(self, **kwargs):
 | 
					    def get_context_data(self, **kwargs):
 | 
				
			||||||
        self.categories = {
 | 
					        kwargs["categories"] = self.categories
 | 
				
			||||||
            id: title
 | 
					        return super().get_context_data(**kwargs)
 | 
				
			||||||
            for title, id in self.model.objects.published()
 | 
					 | 
				
			||||||
            .filter(category__isnull=False)
 | 
					 | 
				
			||||||
            .values_list("category__title", "category__id")
 | 
					 | 
				
			||||||
            .distinct()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        cat_id = self.request.GET.get("category__id")
 | 
					 | 
				
			||||||
        if cat_id:
 | 
					 | 
				
			||||||
            cat_id = int(cat_id)
 | 
					 | 
				
			||||||
            kwargs["category_id"] = cat_id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        context = super().get_context_data(**kwargs)
 | 
					 | 
				
			||||||
        if context.get("parent") and not cat_id:
 | 
					 | 
				
			||||||
            kwargs["category_id"] = context["parent"].category_id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return context
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PageDetailView(BasePageDetailView):
 | 
					class PageDetailView(BasePageDetailView):
 | 
				
			||||||
@ -152,7 +179,6 @@ class PageDetailView(BasePageDetailView):
 | 
				
			|||||||
            if related:
 | 
					            if related:
 | 
				
			||||||
                related = related[: self.related_count]
 | 
					                related = related[: self.related_count]
 | 
				
			||||||
            kwargs["related_objects"] = related
 | 
					            kwargs["related_objects"] = related
 | 
				
			||||||
            kwargs["related_url"] = self.get_related_url()
 | 
					 | 
				
			||||||
        return super().get_context_data(**kwargs)
 | 
					        return super().get_context_data(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
 | 
				
			|||||||
@ -52,7 +52,7 @@ class ProgramDetailView(BaseProgramMixin, PageDetailView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class ProgramListView(PageListView):
 | 
					class ProgramListView(PageListView):
 | 
				
			||||||
    model = Program
 | 
					    model = Program
 | 
				
			||||||
    attach_to_value = StaticPage.ATTACH_TO_PROGRAMS
 | 
					    attach_to_value = StaticPage.Target.PROGRAMS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# FIXME: not used
 | 
					# FIXME: not used
 | 
				
			||||||
 | 
				
			|||||||
@ -39,9 +39,9 @@
 | 
				
			|||||||
    --a-player-bar-bg: var(--highlight-color);
 | 
					    --a-player-bar-bg: var(--highlight-color);
 | 
				
			||||||
    --a-player-bar-title-alone-sz: #{v.$text-size-bigger};
 | 
					    --a-player-bar-title-alone-sz: #{v.$text-size-bigger};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    --button-fg: var(--text-color);
 | 
					    --button-fg: var(--highlight-color-2);
 | 
				
			||||||
    --button-bg: var(--highlight-color);
 | 
					    --button-bg: var(--highlight-color);
 | 
				
			||||||
    --button-hg-fg: var(--highlight-color-2);
 | 
					    --button-hg-fg: var(--text-color);
 | 
				
			||||||
    --button-hg-bg: var(--highlight-color);
 | 
					    --button-hg-bg: var(--highlight-color);
 | 
				
			||||||
    --button-active-fg: var(--highlight-color);
 | 
					    --button-active-fg: var(--highlight-color);
 | 
				
			||||||
    --button-active-bg: var(--highlight-color-2);
 | 
					    --button-active-bg: var(--highlight-color-2);
 | 
				
			||||||
@ -102,13 +102,13 @@
 | 
				
			|||||||
@mixin button {
 | 
					@mixin button {
 | 
				
			||||||
    .button, a.button, button.button, .nav-urls a {
 | 
					    .button, a.button, button.button, .nav-urls a {
 | 
				
			||||||
        display: inline-block;
 | 
					        display: inline-block;
 | 
				
			||||||
        padding: v.$mp-3e;
 | 
					        padding: v.$mp-2e;
 | 
				
			||||||
        border-radius: 4px;
 | 
					 | 
				
			||||||
        border: 1px var(--highlight-color-2-alpha) solid;
 | 
					        border: 1px var(--highlight-color-2-alpha) solid;
 | 
				
			||||||
        justify-content: center;
 | 
					        justify-content: center;
 | 
				
			||||||
        text-align: center;
 | 
					        text-align: center;
 | 
				
			||||||
        font-size: v.$text-size-medium;
 | 
					        font-size: v.$text-size-medium;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        color: var(--button-fg);
 | 
				
			||||||
        background-color: var(--button-bg);
 | 
					        background-color: var(--button-bg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .icon {
 | 
					        .icon {
 | 
				
			||||||
@ -169,7 +169,6 @@
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
// ---- preview
 | 
					// ---- preview
 | 
				
			||||||
.preview {
 | 
					.preview {
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
@ -355,6 +354,15 @@
 | 
				
			|||||||
    height: var(--preview-cover-size);
 | 
					    height: var(--preview-cover-size);
 | 
				
			||||||
    width: var(--preview-cover-size);
 | 
					    width: var(--preview-cover-size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.small {
 | 
				
			||||||
 | 
					        height: var(--preview-cover-small-size);
 | 
				
			||||||
 | 
					        width: var(--preview-cover-small-size);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    &.tiny {
 | 
				
			||||||
 | 
					        height: var(--preview-cover-tiny-size);
 | 
				
			||||||
 | 
					        width: var(--preview-cover-tiny-size);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    &:not(.header) {
 | 
					    &:not(.header) {
 | 
				
			||||||
        box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
 | 
					        box-shadow: 0em 0em 1em rgba(0,0,0,0.2);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -389,72 +397,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ---- page header
 | 
					 | 
				
			||||||
.header {
 | 
					 | 
				
			||||||
    &.preview-header {
 | 
					 | 
				
			||||||
        //display: flex;
 | 
					 | 
				
			||||||
        align-items: start;
 | 
					 | 
				
			||||||
        gap: v.$mp-3;
 | 
					 | 
				
			||||||
        min-height: unset;
 | 
					 | 
				
			||||||
        padding-top: v.$mp-3 !important;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .headings {
 | 
					 | 
				
			||||||
        width: unset;
 | 
					 | 
				
			||||||
        flex-grow: 1;
 | 
					 | 
				
			||||||
        padding-top: 0 !important;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
        flex-direction: column;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    &.has-cover {
 | 
					 | 
				
			||||||
        min-height: calc( var(--header-height) / 2 );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .title {
 | 
					 | 
				
			||||||
        font-size: v.$h1-size;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    .subtitle {
 | 
					 | 
				
			||||||
        font-size: v.$h2-size;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.header-cover:not(:only-child) {
 | 
					 | 
				
			||||||
    float: right;
 | 
					 | 
				
			||||||
    height: var(--header-height);
 | 
					 | 
				
			||||||
    max-width: calc(var(--header-height) * 2);
 | 
					 | 
				
			||||||
    margin: 0 0 v.$mp-4 v.$mp-4;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.header-cover:only-child {
 | 
					 | 
				
			||||||
    with: 100%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@media screen and (max-width: v.$screen-small) {
 | 
					 | 
				
			||||||
    .container.header {
 | 
					 | 
				
			||||||
        width: 100%;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .headings {
 | 
					 | 
				
			||||||
            width: 100%;
 | 
					 | 
				
			||||||
            clear: both;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .header-cover {
 | 
					 | 
				
			||||||
            float: none;
 | 
					 | 
				
			||||||
            width: 100%;
 | 
					 | 
				
			||||||
            max-width: unset;
 | 
					 | 
				
			||||||
            height: unset;
 | 
					 | 
				
			||||||
            margin-left: 0rem;
 | 
					 | 
				
			||||||
            margin-right: 0rem;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ---- card grid
 | 
					// ---- card grid
 | 
				
			||||||
.card-grid {
 | 
					.card-grid {
 | 
				
			||||||
@ -606,6 +548,10 @@
 | 
				
			|||||||
    box-shadow: 0em -0.5em 0.5em rgba(0, 0, 0, 0.05);
 | 
					    box-shadow: 0em -0.5em 0.5em rgba(0, 0, 0, 0.05);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    a { color: var(--a-player-url-fg); }
 | 
					    a { color: var(--a-player-url-fg); }
 | 
				
			||||||
 | 
					    .button {
 | 
				
			||||||
 | 
					        color: var(--text-black);
 | 
				
			||||||
 | 
					        &:hover { color: var(--button-fg); }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.a-player-panels {
 | 
					.a-player-panels {
 | 
				
			||||||
 | 
				
			|||||||
@ -13,8 +13,13 @@
 | 
				
			|||||||
        color: var(--highlight-color-2);
 | 
					        color: var(--highlight-color-2);
 | 
				
			||||||
        text-decoration: none;
 | 
					        text-decoration: none;
 | 
				
			||||||
        padding: v.$mp-2;
 | 
					        padding: v.$mp-2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &:hover {
 | 
				
			||||||
 | 
					            color: var(--text-color);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    section.container {
 | 
					    section.container {
 | 
				
			||||||
        padding-top: v.$mp-6;
 | 
					        padding-top: v.$mp-6;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -74,22 +79,12 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    button, .action {
 | 
					    button, .action {
 | 
				
			||||||
        background-color: var(--highlight-color);
 | 
					 | 
				
			||||||
        justify-content: center;
 | 
					        justify-content: center;
 | 
				
			||||||
        min-width: 2rem;
 | 
					        min-width: 2rem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .not-selected { opacity: 0.6; }
 | 
					        .not-selected { opacity: 0.6; }
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .icon { margin: 0em !important; }
 | 
					        .icon { margin: 0em !important; }
 | 
				
			||||||
 | 
					        label { margin-left: v.$mp-2; }
 | 
				
			||||||
        label {
 | 
					 | 
				
			||||||
            margin-left: v.$mp-2;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        &:hover, .selected {
 | 
					 | 
				
			||||||
            color: var(--highlight-color-2) !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -115,6 +110,10 @@
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ---- main navigation
 | 
					// ---- main navigation
 | 
				
			||||||
 | 
					.navs {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.nav {
 | 
					.nav {
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
    background-color: var(--highlight-color);
 | 
					    background-color: var(--highlight-color);
 | 
				
			||||||
@ -145,7 +144,7 @@
 | 
				
			|||||||
            display: inline-block;
 | 
					            display: inline-block;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        &.active {
 | 
					        &.active, &:hover {
 | 
				
			||||||
            background-color: var(--highlight-color-2);
 | 
					            background-color: var(--highlight-color-2);
 | 
				
			||||||
            color: var(--highlight-color);
 | 
					            color: var(--highlight-color);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -189,7 +188,19 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    &.secondary {
 | 
					    &.secondary {
 | 
				
			||||||
 | 
					        position: absolute;
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					        z-index: 100;
 | 
				
			||||||
 | 
					        box-shadow: 0em 0.5em 0.5em rgba(0, 0, 0, 0.05);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        justify-content: right;
 | 
					        justify-content: right;
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .nav-item:hover + &, &:hover {
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            top: var(--nav-primary-height);
 | 
				
			||||||
 | 
					            left: 0rem;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .nav-item {
 | 
					        .nav-item {
 | 
				
			||||||
            font-size: v.$text-size;
 | 
					            font-size: v.$text-size;
 | 
				
			||||||
@ -197,6 +208,18 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ---- breadcrumbs
 | 
				
			||||||
 | 
					.breadcrumbs {
 | 
				
			||||||
 | 
					    text-align: right;
 | 
				
			||||||
 | 
					    height: calc( v.$mp-3 * 2 + v.$text-size);
 | 
				
			||||||
 | 
					    margin-bottom: v.$mp-2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    a + a:before {
 | 
				
			||||||
 | 
					        content: "/";
 | 
				
			||||||
 | 
					        margin: 0 v.$mp-2;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@media screen and (max-width: v.$screen-normal) {
 | 
					@media screen and (max-width: v.$screen-normal) {
 | 
				
			||||||
    .page {
 | 
					    .page {
 | 
				
			||||||
@ -324,7 +347,7 @@ nav li {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    &.has-cover {
 | 
					    &.has-cover {
 | 
				
			||||||
        min-height: calc( var(--header-height) / 2 );
 | 
					        min-height: calc( var(--header-height) / 3 );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .title {
 | 
					    .title {
 | 
				
			||||||
 | 
				
			|||||||
@ -69,6 +69,6 @@ window.aircox = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    pickDate(url, date) {
 | 
					    pickDate(url, date) {
 | 
				
			||||||
        url = `${url}?date=${date.id}`
 | 
					        url = `${url}?date=${date.id}`
 | 
				
			||||||
        this.builder.fetch(url)
 | 
					        this.loader.pageLoad.load(url)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user