diff --git a/cms/models.py b/cms/models.py index 4e2eb4a..fcd0d0e 100644 --- a/cms/models.py +++ b/cms/models.py @@ -40,20 +40,22 @@ class WebsiteSettings(BaseSetting): # exist. Update all dependent code such as signal handling # general website information - logo = models.ForeignKey( - 'wagtailimages.Image', - verbose_name = _('logo'), - null=True, blank=True, on_delete=models.SET_NULL, - related_name='+', - help_text = _('logo of the website'), - ) - favicon = models.ForeignKey( - 'wagtailimages.Image', + favicon = models.ImageField( verbose_name = _('favicon'), null=True, blank=True, - on_delete=models.SET_NULL, - related_name='+', - help_text = _('favicon for the website'), + help_text = _('small logo for the website displayed in the browser'), + ) + tags = models.CharField( + _('tags'), + max_length=256, + null=True, blank=True, + help_text = _('tags describing the website; used for referencing'), + ) + description = models.CharField( + _('public description'), + max_length=256, + null=True, blank=True, + help_text = _('public description of the website; used for referencing'), ) # comments @@ -107,8 +109,11 @@ class WebsiteSettings(BaseSetting): panels = [ - ImageChooserPanel('logo'), - ImageChooserPanel('favicon'), + MultiFieldPanel([ + FieldPanel('favicon'), + FieldPanel('tags'), + FieldPanel('description'), + ], heading=_('promotion')), MultiFieldPanel([ FieldPanel('allow_comments'), FieldPanel('accept_comments'), @@ -252,7 +257,8 @@ class Publication(Page): FieldPanel('allow_comments'), ] search_fields = [ - index.SearchField('body'), + index.SearchField('title', partial_match=True), + index.SearchField('body', partial_match=True), index.FilterField('live'), index.FilterField('show_in_menus'), ] @@ -285,7 +291,7 @@ class Publication(Page): context['comment_form'] = CommentForm() if view == 'list': - context['object_list'] = ListPage.get_queryset( + context['object_list'] = ListBase.from_request( request, context = context, related = self ) return context @@ -448,11 +454,17 @@ class DiffusionPage(Publication): item = cl.from_diffusion(diff, ListItem) item.live = True + item.info = [] if diff.initial: - item.info = _('Rerun of %(date)s') % { + item.info.append(_('Rerun of %(date)s') % { 'date': diff.initial.start.strftime('%A %d') - } - diff.css_class = 'diffusion' + }) + if diff.type == diff.Type.canceled: + item.info.append(_('Cancelled')) + item.info = '; '.join(item.info) + + item.date = diff.start + item.css_class = 'diffusion' return item def save(self, *args, **kwargs): @@ -460,6 +472,7 @@ class DiffusionPage(Publication): self.date = self.diffusion.start super().save(*args, **kwargs) + class EventPageQuerySet(PageQuerySet): def upcoming(self): now = tz.now().date() @@ -587,8 +600,7 @@ class LogsPage(DatedListPage): verbose_name = _('station'), null = True, on_delete=models.SET_NULL, - help_text = _('(required for logs) the station on which the logs ' - 'happened') + help_text = _('(required) the station on which the logs happened') ) age_max = models.IntegerField( _('maximum age'), @@ -608,24 +620,6 @@ class LogsPage(DatedListPage): ], heading=_('Configuration')), ] - def as_item(cl, log): - """ - Return a log object as a DiffusionPage or ListItem. - Supports: Log/Track, Diffusion - """ - if type(log) == programs.Diffusion: - return DiffusionPage.as_item(log) - return ListItem( - title = '{artist} -- {title}'.format( - artist = log.related.artist, - title = log.related.title, - ), - summary = log.related.info, - date = log.date, - info = '♫', - css_class = 'track' - ) - def get_nav_dates(self, date): """ Return a list of dates availables for the navigation @@ -648,8 +642,8 @@ class LogsPage(DatedListPage): logs = [] for date in context['nav_dates']['dates']: - items = self.station.on_air(date = date) - items = [ self.as_item(item) for item in items ] + items = [ SectionLogsList.as_item(item) + for item in self.station.on_air(date = date) ] logs.append((date, items)) return logs diff --git a/cms/sections.py b/cms/sections.py index e20e690..a42c25f 100644 --- a/cms/sections.py +++ b/cms/sections.py @@ -34,7 +34,7 @@ from taggit.models import TaggedItemBase # aircox import aircox.programs.models as programs - +import aircox.controllers.models as controllers def related_pages_filter(reset_cache=False): @@ -102,6 +102,11 @@ class RelatedLinkBase(Orderable): related_name='+', help_text = _('icon to display before the url'), ) + # icon = models.ImageField( + # verbose_name = _('icon'), + # null=True, blank=True, + # help_text = _('icon to display before the url'), + #) text = models.CharField( _('text'), max_length = 64, @@ -327,7 +332,9 @@ class ListBase(models.Model): search = request.GET.get('search') if search: kwargs['terms'] = search + print(search, qs) qs = qs.search(search) + print(qs.count()) set('list_selector', kwargs) @@ -534,7 +541,6 @@ class SectionItemMeta(models.base.ModelBase): cl.template = 'cms/sections/section_item.html' return cl - @register_snippet class SectionItem(models.Model,metaclass=SectionItemMeta): """ @@ -554,15 +560,6 @@ class SectionItem(models.Model,metaclass=SectionItemMeta): default = False, help_text=_('if set show a title at the head of the section'), ) - is_related = models.BooleanField( - _('is related'), - default = False, - help_text=_( - 'if set, section is related to the page being processed ' - 'e.g rendering a list of links will use thoses of the ' - 'publication instead of an assigned one.' - ) - ) css_class = models.CharField( _('CSS class'), max_length=64, @@ -574,7 +571,6 @@ class SectionItem(models.Model,metaclass=SectionItemMeta): FieldPanel('title'), FieldPanel('show_title'), FieldPanel('css_class'), - FieldPanel('is_related'), ], heading=_('General')), ] @@ -593,12 +589,7 @@ class SectionItem(models.Model,metaclass=SectionItemMeta): self.real_type = type(self).__name__.lower() return super().save(*args, **kwargs) - - def related_page_attr(self, page, attr): - return self.is_related and hasattr(page, attr) \ - and getattr(page, attr) - - def get_context(self, request, page, *args, **kwargs): + def get_context(self, request, page): """ Default context attributes: * self: section being rendered @@ -636,6 +627,33 @@ class SectionItem(models.Model,metaclass=SectionItemMeta): self.title or self.pk ) +class SectionRelativeItem(SectionItem): + is_related = models.BooleanField( + _('is related'), + default = False, + help_text=_( + 'if set, section is related to the page being processed ' + 'e.g rendering a list of links will use thoses of the ' + 'publication instead of an assigned one.' + ) + ) + + class Meta: + abstract=True + + panels = SectionItem.panels.copy() + panels[-1] = MultiFieldPanel( + panels[-1].children + [ FieldPanel('is_related') ], + heading = panels[-1].heading + ) + + def related_attr(self, page, attr): + """ + Return an attribute from the given page if self.is_related, + otherwise retrieve the attribute from self. + """ + return self.is_related and hasattr(page, attr) \ + and getattr(page, attr) @register_snippet class SectionText(SectionItem): @@ -644,14 +662,14 @@ class SectionText(SectionItem): FieldPanel('body'), ] - def get_context(self, request, page, *args, **kwargs): + def get_context(self, request, page): from wagtail.wagtailcore.rich_text import expand_db_html - context = super().get_context(request, page, *args, **kwargs) + context = super().get_context(request, page) context['content'] = expand_db_html(self.body) return context @register_snippet -class SectionImage(SectionItem): +class SectionImage(SectionRelativeItem): class ResizeMode(IntEnum): max = 0x00 min = 0x01 @@ -685,7 +703,10 @@ class SectionImage(SectionItem): ) panels = SectionItem.panels + [ - ImageChooserPanel('image'), + MultiFieldPanel([ + ImageChooserPanel('image'), + FieldPanel('image'), + ], heading=_('Source')), MultiFieldPanel([ FieldPanel('width'), FieldPanel('height'), @@ -693,21 +714,25 @@ class SectionImage(SectionItem): ], heading=_('Resizing')) ] - def get_context(self, request, page, *args, **kwargs): - context = super().get_context(request, page, *args, **kwargs) + def get_filter(self): + return \ + 'original' if not (self.height or self.width) else \ + 'width-{}'.format(self.width) if not self.height else \ + 'height-{}'.format(self.height) if not self.width else \ + '{}-{}x{}'.format( + self.get_resize_mode_display(), + self.width, self.height + ) - image = self.related_page_attr(page, 'cover') or self.image + def get_context(self, request, page): + context = super().get_context(request, page) + + image = self.related_attr(page, 'cover') or self.image if not image: return context if self.width or self.height: - filter_spec = \ - 'width-{}'.format(self.width) if not self.height else \ - 'height-{}'.format(self.height) if not self.width else \ - '{}-{}x{}'.format( - self.get_resize_mode_display(), - self.width, self.height - ) + filter_spec = self.get_filter() filter_spec = (image.id, filter_spec) url = reverse( 'wagtailimages_serve', @@ -732,7 +757,7 @@ class SectionLink(RelatedLinkBase, SectionItem): @register_snippet -class SectionLinkList(SectionItem, ClusterableModel): +class SectionLinkList(SectionRelativeItem, ClusterableModel): """ Render a list of links. If related to the current page, print the page's links otherwise, the assigned link list. @@ -746,16 +771,15 @@ class SectionLinkList(SectionItem, ClusterableModel): )) ] - def get_context(self, request, page, *args, **kwargs): - context = super().get_context(*args, **kwargs) - - links = self.related_page_attr(page, 'related_link') or self.links - context['object_list'] = links + def get_context(self, request, page): + context = super().get_context(request, page) + links = self.related_attr(page, 'related_link') or self.links + context['object_list'] = links.all() return context @register_snippet -class SectionList(ListBase, SectionItem): +class SectionList(ListBase, SectionRelativeItem): """ This one is quite badass, but needed: render a list of pages using given parameters (cf. ListBase). @@ -789,18 +813,17 @@ class SectionList(ListBase, SectionItem): ], heading=_('Rendering')), ] + ListBase.panels - def get_context(self, request, page, *args, **kwargs): + def get_context(self, request, page): from aircox.cms.models import Publication - context = super().get_context(request, page, *args, **kwargs) + context = super().get_context(request, page) qs = self.get_queryset() qs = qs.live() if self.focus_available: focus = qs.type(Publication).filter(focus = True).first() if focus: - focus.css_class = \ - focus.css_class + ' focus' if focus.css_class else 'focus' - qs = qs.exclude(focus.page) + focus.css_class = 'focus' + qs = qs.exclude(pk = focus.pk) else: focus = None pages = qs[:self.count - (focus != None)] @@ -814,8 +837,65 @@ class SectionList(ListBase, SectionItem): return context +@register_snippet +class SectionLogsList(SectionItem): + station = models.ForeignKey( + controllers.Station, + verbose_name = _('station'), + null = True, + on_delete=models.SET_NULL, + help_text = _('(required) the station on which the logs happened') + ) + count = models.SmallIntegerField( + _('count'), + default = 5, + help_text = _('number of items to display in the list (max 100)'), + ) + + class Meta: + verbose_name = _('list of logs') + verbose_name_plural = _('lists of logs') + + panels = SectionItem.panels + [ + FieldPanel('station'), + FieldPanel('count'), + ] + + @staticmethod + def as_item(log): + """ + Return a log object as a DiffusionPage or ListItem. + Supports: Log/Track, Diffusion + """ + from aircox.cms.models import DiffusionPage + if type(log) == programs.Diffusion: + return DiffusionPage.as_item(log) + return ListItem( + title = '{artist} -- {title}'.format( + artist = log.related.artist, + title = log.related.title, + ), + summary = log.related.info, + date = log.date, + info = '♫', + css_class = 'track' + ) + + def get_context(self, request, page): + context = super().get_context(request, page) + context['object_list'] = [ + self.as_item(item) + for item in self.station.on_air(count = min(self.count, 100)) + ] + return context + + @register_snippet class SectionTimetable(SectionItem,DatedListBase): + class Meta: + verbose_name = _('timetable') + verbose_name_plural = _('timetable') + panels = SectionItem.panels + DatedListBase.panels def get_queryset(self, context): @@ -827,27 +907,48 @@ class SectionTimetable(SectionItem,DatedListBase): diffs.append((date, items)) return diffs - def get_context(self, request, page, *args, **kwargs): - context = super().get_context(request, page, *args, **kwargs) + def get_context(self, request, page): + context = super().get_context(request, page) context.update(self.get_date_context()) context['object_list'] = self.get_queryset(context) return context @register_snippet -class SectionLogs(SectionItem): - count = models.SmallIntegerField( - _('count'), - default = 5, - help_text = _('number of items to display in the list'), +class SectionPublicationInfo(SectionItem): + class Meta: + verbose_name = _('section with publication\'s info') + verbose_name = _('sections with publication\'s info') + +@register_snippet +class SectionSearchField(SectionItem): + page = models.ForeignKey( + 'cms.ListPage', + verbose_name = _('search page'), + blank = True, null = True, + help_text=_('page used to display the results'), + ) + default_text = models.CharField( + _('default text'), + max_length=32, + default=_('search'), + help_text=_('text to display when the search field is empty'), ) + class Meta: + verbose_name = _('search field') + verbose_name_plural = _('search fields') + panels = SectionItem.panels + [ - FieldPanel('count'), + PageChooserPanel('page'), + FieldPanel('default_text'), ] - def get_context(self, request, page, *args, **kwargs): - pass - - + def get_context(self, request, page): + from aircox.cms.models import ListPage + context = super().get_context(request, page) + list_page = self.page or ListPage.objects.live().first() + context['list_page'] = list_page + print(context, self.template) + return context diff --git a/cms/static/cms/css/layout.css b/cms/static/cms/css/layout.css new file mode 100644 index 0000000..f26f5cd --- /dev/null +++ b/cms/static/cms/css/layout.css @@ -0,0 +1,104 @@ +/** + * Define rules for the default layouts, and some useful classes + */ +body { + margin: 0em; + padding: 0em; +} + +h1, h2, h3, h4, h5 { + margin: 0.4em 0em; +} + + +/** classes: flex **/ +.flex_row { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: row; + flex-direction: row; +} + +.flex_column { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: column; + flex-direction: column; +} + +.flex_row > .flex_item, +.flex_column > .flex_item { + -webkit-flex: auto; + flex: auto; +} + + +/** content: list & items **/ +.list { + width: 100%; + padding: 0.4em; +} + +.list_item { + width: inherit; + margin: 0.4em 0; +} + +.list_item > *:not(:last-child) { + margin-right: 0.4em; +} + +.list_item img.cover.big { + display: block; +} + +.list_item img.cover.small { + margin-right: 0.4em; + border-radius: 0.4em; + float: left; + min-height: 64px; +} + + +/** content: date list **/ +.date_list nav { + text-align:center; +} + + .date_list nav a { + display: inline-block; + width: 4em; + } + + .date_list nav a[selected] { + color: #007EDF; + border-bottom: 0.2em #007EDF dotted; + } + +.date_list ul:not([selected]) { + display: none; +} + +.date_list ul:target { + display: block; +} + + .date_list h2 { + display: none; + } + +.date_list_item .cover.small { + width: 64px; + margin: 0.4em; +} + +.date_list_item h3 { + width: 100%; +} + + +/** content: publication **/ + + + + diff --git a/cms/static/cms/css/theme.css b/cms/static/cms/css/theme.css new file mode 100644 index 0000000..7a2db3c --- /dev/null +++ b/cms/static/cms/css/theme.css @@ -0,0 +1,74 @@ +/* + * Define a default theme, that is the one for RadioCampus + * + * Colors: + * - light: + * - background: #F2F2F2 + * - color: #000 + * + * - dark: + * - background: #212121 + * - color: #007EDF + * + * - info: + * - generic (time,url,...): #616161 + * - additional: #007EDF + * - active: #007EDF + */ + +/** main **/ +body { + background-color: #F2F2F2; + font-family: "Myriad Pro",Calibri,Helvetica,Arial,sans-serif; + margin: 0em; + padding: 0em; +} + + +h1, h2, h3 { + font-family: "Myriad Pro",Calibri,Helvetica,Arial,sans-serif; +} + +h1:first-letter, h2:first-letter, h3:first-letter { + text-transform: capitalize; +} + +h1 { font-size: 1.4em; } +h2 { font-size: 1.2em; } +h3 { font-size: 1.0em; } + + +/** info **/ +time { + font-size: 0.9em; + color: #616161; +} + +.info { + font-size: 0.9em; + color: #007EDF; +} + +a { + cursor: pointer; + text-decoration: none; + color: #616161; +} + +a:hover { + color: #007EDF; +} + + +.error { color: red; } +.warning { color: orange; } +.success { color: green; } + + +/** page **/ +.page > nav { + width: 20em; + overflow: hidden; +} + + diff --git a/cms/templates/cms/base_site.html b/cms/templates/cms/base_site.html index 636fe92..aa6c86a 100644 --- a/cms/templates/cms/base_site.html +++ b/cms/templates/cms/base_site.html @@ -10,6 +10,14 @@
+ + + + + + {% with favicon=settings.cms.WebsiteSettings.favicon %} + + {% endwith %} {% block css %} @@ -21,19 +29,19 @@