add date_list; LogsPage; ListPage

This commit is contained in:
bkfox 2016-07-22 12:38:42 +02:00
parent ba3bf68e33
commit 3975c222c6
11 changed files with 491 additions and 112 deletions

View File

@ -1,3 +1,6 @@
import datetime
import re
from enum import Enum, IntEnum
from django.db import models from django.db import models
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -30,16 +33,32 @@ from taggit.models import TaggedItemBase
import bleach import bleach
import aircox.programs.models as programs import aircox.programs.models as programs
import aircox.controllers.models as controllers
import aircox.cms.settings as settings import aircox.cms.settings as settings
class ListItem:
"""
Generic normalized element to add item in lists that are not based
on Publication.
"""
title = ''
summary = ''
url = ''
cover = None
date = None
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
self.specific = self
@register_setting @register_setting
class WebsiteSettings(BaseSetting): class WebsiteSettings(BaseSetting):
logo = models.ForeignKey( logo = models.ForeignKey(
'wagtailimages.Image', 'wagtailimages.Image',
verbose_name = _('logo'), verbose_name = _('logo'),
null=True, blank=True, null=True, blank=True, on_delete=models.SET_NULL,
on_delete=models.SET_NULL,
related_name='+', related_name='+',
help_text = _('logo of the website'), help_text = _('logo of the website'),
) )
@ -91,8 +110,10 @@ class WebsiteSettings(BaseSetting):
verbose_name = _('website settings') verbose_name = _('website settings')
class RelatedLink(Orderable): #
parent = ParentalKey('Publication', related_name='related_links') # Base
#
class BaseRelatedLink(Orderable):
url = models.URLField( url = models.URLField(
_('url'), _('url'),
help_text = _('URL of the link'), help_text = _('URL of the link'),
@ -112,6 +133,9 @@ class RelatedLink(Orderable):
help_text = _('text to display of the link'), help_text = _('text to display of the link'),
) )
class Meta:
abstract = True
panels = [ panels = [
FieldPanel('url'), FieldPanel('url'),
FieldRowPanel([ FieldRowPanel([
@ -121,6 +145,9 @@ class RelatedLink(Orderable):
] ]
#
# Publications
#
@register_snippet @register_snippet
class Comment(models.Model): class Comment(models.Model):
publication = models.ForeignKey( publication = models.ForeignKey(
@ -170,6 +197,9 @@ class Comment(models.Model):
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
class RelatedLink(BaseRelatedLink):
parent = ParentalKey('Publication', related_name='related_links')
class PublicationTag(TaggedItemBase): class PublicationTag(TaggedItemBase):
content_object = ParentalKey('Publication', related_name='tagged_items') content_object = ParentalKey('Publication', related_name='tagged_items')
@ -248,80 +278,6 @@ class Publication(Page):
published = True, published = True,
).order_by('-date') ).order_by('-date')
@classmethod
def get_queryset(cl, request, *args,
thread = None, context = {},
**kwargs):
"""
Return a queryset from the request's GET parameters. Context
can be used to update relative informations.
Parameters:
* type: ['program','diffusion','event'] type of the publication
* tag: tag to search for
* search: query to search in the publications
* thread: children of the thread passed in arguments only
* order: ['asc','desc'] sort ordering
* page: page number
Context's fields:
* list_type: type of the items in the list (as Page subclass)
* tag_query: tag searched for
* search_query: search terms
* thread_query: thread
* paginator: paginator object
"""
if 'thread' in request.GET and thread:
qs = self.get_children()
context['thread_query'] = thread
else:
qs = cl.objects.all()
qs = qs.not_in_menu().live()
# type
type = request.GET.get('type')
if type == 'program':
qs = qs.type(ProgramPage)
context['list_type'] = ProgramPage
elif type == 'diffusion':
qs = qs.type(DiffusionPage)
context['list_type'] = DiffusionPage
elif type == 'event':
qs = qs.type(EventPage)
context['list_type'] = EventPage
# filter by tag
tag = request.GET.get('tag')
if tag:
context['tag_query'] = tag
qs = qs.filter(tags__name = tag)
# search
search = request.GET.get('search')
if search:
context['search_query'] = search
qs = qs.search(search)
# ordering
order = request.GET.get('order')
if order not in ('asc','desc'):
order = 'desc'
qs = qs.order_by(
('-' if order == 'desc' else '') + 'first_published_at'
)
qs = self.get_queryset(request, *args, context, **kwargs)
if qs:
paginator = Paginator(qs, 30)
try:
qs = paginator.page('page')
except PageNotAnInteger:
qs = paginator.page(1)
except EmptyPage:
qs = parginator.page(paginator.num_pages)
context['paginator'] = paginator
return qs
def get_context(self, request, *args, **kwargs): def get_context(self, request, *args, **kwargs):
from aircox.cms.forms import CommentForm from aircox.cms.forms import CommentForm
context = super().get_context(request, *args, **kwargs) context = super().get_context(request, *args, **kwargs)
@ -333,7 +289,7 @@ class Publication(Page):
context['comment_form'] = CommentForm() context['comment_form'] = CommentForm()
if view == 'list': if view == 'list':
context['object_list'] = self.get_queryset( context['object_list'] = ListPage.get_queryset(
request, *args, context = context, thread = self, **kwargs request, *args, context = context, thread = self, **kwargs
) )
return context return context
@ -366,6 +322,7 @@ class ProgramPage(Publication):
program = models.ForeignKey( program = models.ForeignKey(
programs.Program, programs.Program,
verbose_name = _('program'), verbose_name = _('program'),
related_name = 'page',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
blank=True, null=True, blank=True, null=True,
) )
@ -403,20 +360,22 @@ class ProgramPage(Publication):
), ),
cover = self.cover, cover = self.cover,
live = True, live = True,
date = diff.start,
) )
diff.page_.date = diff.start
return [ return [
diff.page_ for diff in diffs if diff.page_.live diff.page_ for diff in diffs if diff.page_.live
] ]
def next_diffs(self): @property
def next(self):
now = tz.now() now = tz.now()
diffs = programs.Diffusion.objects \ diffs = programs.Diffusion.objects \
.filter(end__gte = now, program = self.program) \ .filter(end__gte = now, program = self.program) \
.order_by('start').prefetch_related('page') .order_by('start').prefetch_related('page')
return self.diffs_to_page(diffs) return self.diffs_to_page(diffs)
def prev_diffs(self): @property
def prev(self):
now = tz.now() now = tz.now()
diffs = programs.Diffusion.objects \ diffs = programs.Diffusion.objects \
.filter(end__lte = now, program = self.program) \ .filter(end__lte = now, program = self.program) \
@ -471,6 +430,32 @@ class DiffusionPage(Publication):
InlinePanel('tracks', label=_('Tracks')) InlinePanel('tracks', label=_('Tracks'))
] ]
@classmethod
def as_item(cl, diff):
"""
Return a DiffusionPage or ListItem from a Diffusion
"""
if diff.page.all().count():
item = diff.page.live().first()
else:
item = ListItem(
title = '{}, {}'.format(
diff.program.name, diff.date.strftime('%d %B %Y')
),
cover = (diff.program.page.count() and \
diff.program.page.first().cover) or '',
live = True,
date = diff.start,
)
if diff.initial:
item.info = _('Rerun of %(date)s') % {
'date': diff.initial.start.strftime('%A %d')
}
diff.css_class = 'diffusion'
return item
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.diffusion: if self.diffusion:
self.first_published_at = self.diffusion.start self.first_published_at = self.diffusion.start
@ -528,6 +513,260 @@ class EventPage(Publication):
super().save(*args, **kwargs) super().save(*args, **kwargs)
#
# Indexes
#
class BaseDateList(models.Model):
nav_days = models.SmallIntegerField(
_('navigation days count'),
default = 7,
help_text = _('number of days to display in the navigation header '
'when we use dates')
)
nav_per_week = models.BooleanField(
_('navigation per week'),
default = False,
help_text = _('if selected, show dates navigation per weeks instead '
'of show days equally around the current date')
)
@staticmethod
def str_to_date(date):
"""
Parse a string and return a regular date or None.
Format is either "YYYY/MM/DD" or "YYYY-MM-DD" or "YYYYMMDD"
"""
try:
exp = r'(?P<year>[0-9]{4})(-|\/)?(?P<month>[0-9]{1,2})(-|\/)?' \
r'(?P<day>[0-9]{1,2})'
date = re.match(exp, date).groupdict()
return datetime.date(
year = int(date['year']), month = int(date['month']),
day = int(date['day'])
)
except:
return None
def get_date_context(self, date, date_max = None):
"""
Return a dict that can be added to the context to be used by
a date_list.
"""
if not date:
date = tz.now().today()
if date_max:
date = min(date, date_max)
# dates
if date_max == date:
first = self.nav_days - 1
elif self.nav_per_week:
first = date.weekday()
else:
first = int((self.nav_days - 1) / 2)
first = date - tz.timedelta(days = first)
dates = [ first + tz.timedelta(days=i)
for i in range(0, self.nav_days) ]
# next/prev weeks/date bunch
next = date + tz.timedelta(days=self.nav_days)
prev = date - tz.timedelta(days=self.nav_days)
if date_max:
dates = [ date for date in dates if date <= date_max ]
next = min(next, date_max)
if next in dates:
next = None
prev = min(prev, date_max)
# context dict
return {
'nav_dates': {
'date': date,
'next': next,
'prev': prev,
'dates': dates,
}
}
class Meta:
abstract = True
class ListPage(Page):
"""
Page for simple lists, query is done though request' GET fields.
Look at get_queryset for more information.
"""
summary = models.TextField(
_('summary'),
blank = True, null = True,
help_text = _('some short description if you want to, just for fun'),
)
@classmethod
def get_queryset(cl, request, *args,
thread = None, context = {},
**kwargs):
"""
Return a queryset from the request's GET parameters. Context
can be used to update relative informations.
This function can be used by other views if needed
Parameters:
* type: ['program','diffusion','event'] type of the publication
* tag: tag to search for
* search: query to search in the publications
* thread: children of the thread passed in arguments only
* order: ['asc','desc'] sort ordering
* page: page number
Context's fields:
* object_list: the final queryset
* list_type: type of the items in the list (as Page subclass)
* tag_query: tag searched for
* search_query: search terms
* thread_query: thread
* paginator: paginator object
"""
if 'thread' in request.GET and thread:
qs = thread.get_children().not_in_menu()
context['thread_query'] = thread
else:
qs = Publication.objects.all()
qs = qs.live()
# ordering
order = request.GET.get('order')
if order not in ('asc','desc'):
order = 'desc'
qs = qs.order_by(
('-' if order == 'desc' else '') + 'first_published_at'
)
# type
type = request.GET.get('type')
if type == 'program':
qs = qs.type(ProgramPage)
context['list_type'] = ProgramPage
elif type == 'diffusion':
qs = qs.type(DiffusionPage)
context['list_type'] = DiffusionPage
elif type == 'event':
qs = qs.type(EventPage)
context['list_type'] = EventPage
# filter by tag
tag = request.GET.get('tag')
if tag:
context['tag_query'] = tag
qs = qs.filter(tags__name = tag)
# search
search = request.GET.get('search')
if search:
context['search_query'] = search
qs = qs.search(search)
# paginator
if qs:
paginator = Paginator(qs, 30)
try:
qs = paginator.page('page')
except PageNotAnInteger:
qs = paginator.page(1)
except EmptyPage:
qs = parginator.page(paginator.num_pages)
context['paginator'] = paginator
context['object_list'] = qs
return qs
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
qs = self.get_queryset(request, context=context)
context['object_list'] = qs
return context
class LogsPage(BaseDateList,Page):
summary = models.TextField(
_('summary'),
blank = True, null = True,
help_text = _('some short description if you want to, just for fun'),
)
station = models.ForeignKey(
controllers.Station,
verbose_name = _('station'),
null = True,
on_delete=models.SET_NULL,
help_text = _('(required for logs) the station on which the logs '
'happened')
)
max_days = models.IntegerField(
_('maximum days'),
default=15,
help_text = _('maximum days in the past allowed to be shown. '
'0 means no limit')
)
content_panels = [
FieldPanel('title'),
MultiFieldPanel([
FieldPanel('station'),
FieldPanel('max_days'),
], 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_queryset(self, request, context):
if 'date' in request.GET:
date = request.GET.get('date')
date = self.str_to_date(date)
if date and self.max_days:
date = max(
tz.now().date() - tz.timedelta(days=self.max_days),
date
)
else:
date = tz.now().date()
context.update(self.get_date_context(date, date_max=tz.now().date()))
r = []
for date in context['nav_dates']['dates']:
logs = self.station.get_on_air(date)
logs = [ self.as_item(log) for log in logs ]
r.append((date, logs))
return r
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
qs = self.get_queryset(request, context)
context['object_list'] = qs
return context
# #
# Menus and Sections # Menus and Sections
# #

View File

@ -34,7 +34,7 @@
{% endif %} {% endif %}
{% with list_paginator=paginator %} {% with list_paginator=paginator %}
{% include "cms/list.html" %} {% include "cms/snippets/list.html" %}
{% endwith %} {% endwith %}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "cms/base_site.html" %}
{# generic page to display list of articles #}
{% block content %}
{% include "cms/snippets/date_list.html" %}
{% endblock %}

View File

@ -34,21 +34,21 @@
{% block page_nav_extras %} {% block page_nav_extras %}
{% if page.program.active %} {% if page.program.active %}
{% with object_list=page.next_diffs %} {% with object_list=page.next %}
{% if object_list %} {% if object_list %}
<div> <div>
<h2>{% trans "Next Diffusions" %}</h2> <h2>{% trans "Next Diffusions" %}</h2>
{% include "cms/list.html" %} {% include "cms/snippets/list.html" %}
</div> </div>
{% endif %} {% endif %}
{% endwith %} {% endwith %}
{% endif %}{# program.active #} {% endif %}{# program.active #}
{% with object_list=page.prev_diffs %} {% with object_list=page.prev %}
{% if object_list %} {% if object_list %}
<div> <div>
<h2>{% trans "Previous Diffusions" %}</h2> <h2>{% trans "Previous Diffusions" %}</h2>
{% include "cms/list.html" %} {% include "cms/snippets/list.html" %}
</div> </div>
{% endif %} {% endif %}
{% endwith %} {% endwith %}

View File

@ -1,10 +0,0 @@
{% extends "cms/base_site.html" %}
{% load i18n %}
{% block title %}
{% endblock %}

View File

@ -0,0 +1,37 @@
{% load i18n %}
{# FIXME: get current complete URL #}
<div class="list dated_list">
{% if nav_dates %}
<nav class="nav_dates">
{% if nav_dates.prev %}
<a href="?date={{ nav_dates.prev|date:"Y-m-d" }}" title="{% trans "previous days" %}">&lt;</a>
{% endif %}
{% for day in nav_dates.dates %}
<a href="#day_{{day|date:"Y-m-d"}}"
{% if day == nav_dates.date %}class="today"{% endif %}>
{{ day|date:'D. d' }}
</a>
{% endfor %}
{% if nav_dates.next %}
<a href="?date={{ nav_dates.next|date:"Y-m-d" }}" title="{% trans "next days" %}">&gt;</a>
{% endif %}
</nav>
{% endif %}
{% for day, list in object_list %}
<ul id="day_{{day|date:"Y-m-d"}}"
{% if day == nav_dates.date %}class="today"{% endif %}>
{# you might like to hide it by default -- this more for sections #}
<h2>{{ day|date:'l d F' }}</h2>
{% with object_list=list list_date_format="H:i" %}
{% for item in list %}
{% include "cms/snippets/list_item.html" %}
{% endfor %}
{% endwith %}
</ul>
{% endfor %}
</div>

View File

@ -1,9 +1,59 @@
{% load i18n %}
{% load aircox_cms %}
<div class="list"> <div class="list">
<ul>
{% for page in object_list %} {% for page in object_list %}
{% with item=page.specific %} {% with item=page.specific %}
{% include "cms/snippets/list_item.html" %} {% include "cms/snippets/list_item.html" %}
{% endwith %} {% endwith %}
{% endfor %} {% endfor %}
</ul>
{# we use list_paginator to avoid conflicts when there are multiple lists #}
{% if list_paginator and list_paginator.num_pages > 1 %}
<nav>
{% with list_paginator.num_pages as num_pages %}
{% if object_list.has_previous %}
<a href="?page={{ object_list.previous_page_number }}">
{% trans "previous page" %}
</a>
{% endif %}
{% if object_list.number > 3 %}
<a href="?page=1">1</a>
{% if object_list.number > 4 %}
&#8230;
{% endif %}
{% endif %}
{% for i in object_list.number|around:2 %}
{% if i == object_list.number %}
{{ object_list.number }}
{% elif i > 0 and i <= num_pages %}
<a href="?page={{ i }}">{{ i }}</a>
{% endif %}
{% endfor %}
{% with object_list.number|add:"2" as max %}
{% if max < num_pages %}
{% if max|add:"1" < num_pages %}
&#8230;
{% endif %}
<a href="?page={{ num_pages }}">{{ num_pages }}</a>
{% endif %}
{% endwith %}
{% if object_list.has_next %}
<a href="?page={{ object_list.next_page_number }}">
{% trans "next page" %}
</a>
{% endif %}
{% endwith %}
</nav>
{% endif %}
</div> </div>

View File

@ -1,15 +1,28 @@
{% comment %}
Configurable item to be put in a list. Support standard Publication or
ListItem instance.
Options:
* item: item to render. Fields: title, summary, cover, url, date, info, css_class
* list_date_format: format passed to the date filter instead of default one
{% endcomment %}
{% load wagtailimages_tags %} {% load wagtailimages_tags %}
<a {% if item.url %}href="{{ item.url }}" {% endif %}class="item page_item"> <a {% if item.url %}href="{{ item.url }}" {% endif %}
class="item page_item{% if item.css_class %}{{ item.css_class }}{% endif %}">
{% image item.cover fill-64x64 class="cover item_cover" %} {% image item.cover fill-64x64 class="cover item_cover" %}
<h3>{{ item.title }}</h3> <h3>{{ item.title }}</h3>
<div class="summary">{{ item.summary }}</div> {% if item.summary %}<div class="summary">{{ item.summary }}</div>{% endif %}
{% if not item.show_in_menus %} {% if not item.show_in_menus and item.date %}
{% if item.date %} {% with date_format=list_date_format|default:'l d F, H:i' %}
<time datetime="{{ item.date }}"> <time datetime="{{ item.date }}">
{{ item.date|date:'l d F, H:i' }} {{ item.date|date:date_format }}
</time> </time>
{% endwith %}
{% endif %} {% endif %}
{% if item.info %}
<span class="info">{{ item.info|safe }}</span>
{% endif %} {% endif %}
</a> </a>

View File

@ -0,0 +1,11 @@
from django import template
register = template.Library()
@register.filter(name='around')
def around(page_num, n):
"""
Return a range of value around a given number.
"""
return range(page_num-n, page_num+n+1)

View File

@ -1,12 +1,2 @@
from django.shortcuts import render from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from aircox.cms.models import *
def index_page(request):
context = {}
if ('tag' or 'search') in request.GET:
qs = Publication.get_queryset(request, context = context)
return render(request, 'index_page.html', context)

View File

@ -12,6 +12,7 @@ sources that are used to generate the audio stream:
- **master**: main output - **master**: main output
""" """
import os import os
import datetime
import logging import logging
from enum import Enum, IntEnum from enum import Enum, IntEnum
@ -160,6 +161,47 @@ class Station(programs.Nameable):
) )
return qs.order_by('date') return qs.order_by('date')
def get_on_air(self, date = None):
"""
Return a list of what should have normally been on air at the
given date, ordered descending on the diffusion time
The list contains:
- track logs: for the streamed programs;
- diffusion: for the scheduled diffusions;
"""
# TODO: argument to get sound instead of tracks
date = date or tz.now().date()
if date > datetime.date.today():
return []
logs = Log.get_for(model = programs.Track) \
.filter(date__contains = date) \
.order_by('date')
diffs = programs.Diffusion.objects.get_at(date) \
.filter(type = programs.Diffusion.Type.normal) \
.order_by('start')
# mix up
items = []
prev_diff = None
for diff in diffs:
logs_ = logs.filter(date__gt = prev_diff.end,
date__lt = diff.start) \
if prev_diff else \
logs.filter(date__lt = diff.start)
prev_diff = diff
items.extend(logs_)
items.append(diff)
# last logs
if prev_diff:
logs_ = logs.filter(date__gt = prev_diff.end)
items.extend(logs_)
return reversed(items)
def save(self, make_sources = True, *args, **kwargs): def save(self, make_sources = True, *args, **kwargs):
""" """
* make_sources: if the model has not been yet saved, generate * make_sources: if the model has not been yet saved, generate