#132 | #121: backoffice / dev-1.0-121 #131

Merged
thomas merged 151 commits from dev-1.0-121 into develop-1.0 2024-04-28 20:02:14 +00:00
29 changed files with 454 additions and 347 deletions
Showing only changes of commit 46a9008cda - Show all commits

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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 {

View File

@ -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;

View File

@ -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?");
/***/ }), /***/ }),

View File

@ -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 %}

View File

@ -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>

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View 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 %}

View 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 %}

View File

@ -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 %}

View File

@ -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 }}

View File

@ -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(),

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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")

View File

@ -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")

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -13,7 +13,12 @@
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 {

View File

@ -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)
} }
} }