-from datetime import date
-import os
+from datetime import date, datetime, timedelta
+from combo.data.library import register_cell_class
+from combo.data.models import CellBase
from django import template
from django.db import models
-from django.db.models.functions import Lower
-from django.utils.encoding import force_text, python_2_unicode_compatible
-from django.utils.translation import ugettext_lazy as _
-
-from ckeditor.fields import RichTextField
-from taggit.models import Tag
+from django.utils.translation import gettext_lazy as _
+from emissions.models import Emission, Episode, Focus, NewsItem, SoundFile
+from emissions.utils import period_program
from taggit.managers import TaggableManager
+from taggit.models import Tag
from taggit.utils import parse_tags
-from combo.data.models import CellBase
-from combo.data.library import register_cell_class
-
-from emissions.models import Episode, NewsItem
@register_cell_class
class SoundCell(CellBase):
- soundfile = models.ForeignKey('emissions.SoundFile', null=True)
+ soundfile = models.ForeignKey('emissions.SoundFile', null=True, on_delete=models.CASCADE)
class Meta:
verbose_name = _('Sound')
def get_default_form_class(self):
from .forms import SoundCellForm
+
return SoundCellForm
def get_included_items(self):
def get_additional_label(self):
if self.soundfile:
if self.soundfile.fragment:
- return u'%s - %s - %s' % (
- self.soundfile.episode.emission.title,
- self.soundfile.episode.title,
- self.soundfile.title)
+ return '%s - %s - %s' % (
+ self.soundfile.episode.emission.title,
+ self.soundfile.episode.title,
+ self.soundfile.title,
+ )
else:
- return u'%s - %s' % (
- self.soundfile.episode.emission.title,
- self.soundfile.episode.title)
+ return '%s - %s' % (self.soundfile.episode.emission.title, self.soundfile.episode.title)
return ''
@register_cell_class
class EpisodeCell(CellBase):
- episode = models.ForeignKey('emissions.Episode', null=True)
+ episode = models.ForeignKey('emissions.Episode', null=True, on_delete=models.CASCADE)
class Meta:
verbose_name = _('Episode')
def get_default_form_class(self):
from .forms import EpisodeCellForm
+
return EpisodeCellForm
def get_additional_label(self):
if self.episode:
- return u'%s - %s' % (
- self.episode.emission.title,
- self.episode.title)
+ return '%s - %s' % (self.episode.emission.title, self.episode.title)
return ''
title = models.CharField(_('Title'), max_length=50, blank=True)
tags = TaggableManager(_('Tags'), blank=True)
and_tags = models.CharField(_('And Tags'), max_length=100, blank=True)
- category = models.ForeignKey('emissions.Category', null=True, blank=True)
+ category = models.ForeignKey('emissions.Category', null=True, blank=True, on_delete=models.CASCADE)
period = models.PositiveSmallIntegerField(
- _('Period'), default=0,
- choices=((0, _('All')),
- (1, _('Future')),
- (2, _('Past'))))
-
- template_name = 'panikombo/episode_auto_selection.html'
+ _('Period'), default=0, choices=((0, _('All')), (1, _('Future')), (2, _('Past')))
+ )
+ default_template_name = 'panikombo/episode_auto_selection.html'
class Meta:
- verbose_name = _('Automatic Episode Selection')
+ verbose_name = _('Episodes')
def get_included_items(self):
episodes_queryset = Episode.objects.select_related()
if self.period == 0:
episodes_queryset = episodes_queryset.extra(
- select={ 'first_diffusion': 'emissions_diffusion.datetime', },
- select_params=(False, True),
- where=['''datetime = (SELECT MIN(datetime) FROM emissions_diffusion
- WHERE episode_id = emissions_episode.id)'''],
- tables=['emissions_diffusion'])
+ select={
+ 'first_diffusion': 'emissions_diffusion.datetime',
+ },
+ select_params=(False, True),
+ where=[
+ '''datetime = (SELECT MIN(datetime) FROM emissions_diffusion
+ WHERE episode_id = emissions_episode.id)'''
+ ],
+ tables=['emissions_diffusion'],
+ )
episodes_queryset = episodes_queryset.order_by('first_diffusion').distinct()
elif self.period == 1:
episodes_queryset = episodes_queryset.extra(
- select={ 'first_diffusion': 'emissions_diffusion.datetime', },
- select_params=(False, True),
- where=['''datetime = (SELECT MIN(datetime) FROM emissions_diffusion
+ select={
+ 'first_diffusion': 'emissions_diffusion.datetime',
+ },
+ select_params=(False, True),
+ where=[
+ '''datetime = (SELECT MIN(datetime) FROM emissions_diffusion
WHERE episode_id = emissions_episode.id) AND
- datetime >= CURRENT_TIMESTAMP'''],
- tables=['emissions_diffusion'])
+ datetime >= CURRENT_TIMESTAMP'''
+ ],
+ tables=['emissions_diffusion'],
+ )
episodes_queryset = episodes_queryset.order_by('-first_diffusion').distinct()
elif self.period == 2:
episodes_queryset = episodes_queryset.extra(
- select={ 'first_diffusion': 'emissions_diffusion.datetime', },
- select_params=(False, True),
- where=['''datetime = (SELECT MIN(datetime) FROM emissions_diffusion
+ select={
+ 'first_diffusion': 'emissions_diffusion.datetime',
+ },
+ select_params=(False, True),
+ where=[
+ '''datetime = (SELECT MIN(datetime) FROM emissions_diffusion
WHERE episode_id = emissions_episode.id) AND
- datetime < CURRENT_TIMESTAMP'''],
- tables=['emissions_diffusion'])
+ datetime < CURRENT_TIMESTAMP'''
+ ],
+ tables=['emissions_diffusion'],
+ )
episodes_queryset = episodes_queryset.order_by('-first_diffusion').distinct()
return episodes_queryset
def get_cell_extra_context(self, context):
- ctx = super(EpisodeAutoSelectionCell, self).get_cell_extra_context(context)
+ ctx = super().get_cell_extra_context(context)
ctx['title'] = self.title
- if (self.category or self.period or self.tags.count()):
+ if self.category or self.period or self.tags.count():
ctx['episodes'] = self.get_included_items()
else:
ctx['episodes'] = []
def get_default_form_class(self):
from .forms import EpisodeAutoSelectionCellForm
+
return EpisodeAutoSelectionCellForm
def get_additional_label(self):
return self.title
return ''
+
@register_cell_class
class NewsItemAutoSelectionCell(CellBase):
title = models.CharField(_('Title'), max_length=50, blank=True)
tags = TaggableManager(_('Tags'), blank=True)
and_tags = models.CharField(_('And Tags'), max_length=100, blank=True)
future = models.BooleanField(_('Future Events Only'), default=True)
- category = models.ForeignKey('emissions.NewsCategory',
- verbose_name=_('Category'), null=True, blank=True)
+ category = models.ForeignKey(
+ 'emissions.NewsCategory', verbose_name=_('Category'), null=True, blank=True, on_delete=models.SET_NULL
+ )
+ count = models.PositiveSmallIntegerField(_('Count'), default=20)
- template_name = 'panikombo/newsitem_auto_selection.html'
+ default_template_name = 'panikombo/newsitem_auto_selection.html'
class Meta:
- verbose_name = _('Automatic Newsitem Selection')
+ verbose_name = _('Newsitems')
def get_included_items(self):
newsitems_queryset = NewsItem.objects.select_related()
if self.category:
newsitems_queryset = newsitems_queryset.filter(category=self.category)
newsitems_queryset = newsitems_queryset.order_by('-event_date', '-creation_timestamp')
- return newsitems_queryset
+ return newsitems_queryset[: self.count]
def get_cell_extra_context(self, context):
- ctx = super(NewsItemAutoSelectionCell, self).get_cell_extra_context(context)
+ ctx = super().get_cell_extra_context(context)
ctx['title'] = self.title
-
- if self.tags.count() or self.future or self.category:
- ctx['newsitems'] = self.get_included_items()
- else:
- ctx['newsitems'] = []
-
+ ctx['newsitems'] = self.get_included_items()
return ctx
def get_default_form_class(self):
from .forms import NewsItemAutoSelectionCellForm
+
return NewsItemAutoSelectionCellForm
def get_additional_label(self):
return ''
-def get_topik_image_path(instance, filename):
- return os.path.join('images', 'topik', instance.page.slug,
- os.path.basename(filename))
+class ItemTopik(models.Model):
+ newsitem = models.ForeignKey(
+ 'emissions.NewsItem', verbose_name=_('News Item'), null=True, blank=True, on_delete=models.SET_NULL
+ )
+ episode = models.ForeignKey(
+ 'emissions.Episode', verbose_name=_('Episode'), null=True, blank=True, on_delete=models.SET_NULL
+ )
+ page = models.ForeignKey('data.Page', null=True, blank=True, on_delete=models.SET_NULL)
-@python_2_unicode_compatible
-class Topik(models.Model):
- page = models.ForeignKey('data.Page')
- image = models.ImageField(_('Image'),
- upload_to=get_topik_image_path, max_length=250, null=True, blank=True)
- # denormalized from Focus
- got_focus = models.DateTimeField(default=None, null=True, blank=True)
- has_focus = models.BooleanField(default=False)
+@register_cell_class
+class SoundsCell(CellBase):
+ title = models.CharField(_('Title'), max_length=150, blank=True)
+ include_search_input = models.BooleanField(_('Include search input'), default=True)
+ include_fragments = models.BooleanField(_('Include fragments'), default=True)
+ limit_to_focus = models.BooleanField(_('Limit to focused elements'), default=False)
+ sound_format = models.ForeignKey(
+ 'emissions.Format', verbose_name=_('Limit to format'), null=True, blank=True, on_delete=models.CASCADE
+ )
+ tags = TaggableManager(_('Tags'), blank=True)
+ minimal_duration = models.PositiveIntegerField(
+ _('Minimal duration (in minutes)'), default=None, blank=True, null=True
+ )
+ maximal_duration = models.PositiveIntegerField(
+ _('Maximal duration (in minutes)'), default=None, blank=True, null=True
+ )
+ count = models.PositiveSmallIntegerField(_('Count'), default=20)
+ sort_order = models.CharField(
+ _('Sort order'),
+ default='-creation_timestamp',
+ max_length=30,
+ choices=[
+ ('-creation_timestamp', _('Reverse chronological (creation)')),
+ ('-first_diffusion', _('Reverse chronological (diffusion)')),
+ ('creation_timestamp', _('Chronological (creation)')),
+ ('first_diffusion', _('Chronological (diffusion)')),
+ ('?', _('Random')),
+ ],
+ )
- def __str__(self):
- if not self.page:
- return super(Topik, self).__str__()
- return force_text(self.page)
+ class Meta:
+ verbose_name = _('Sounds')
+ def get_default_form_fields(self):
+ fields = super().get_default_form_fields()
+ fields.insert(fields.index('minimal_duration'), 'tags')
+ return fields
-class ItemTopik(models.Model):
- newsitem = models.ForeignKey('emissions.NewsItem', verbose_name=_('News Item'),
- null=True, blank=True)
- episode = models.ForeignKey('emissions.Episode', verbose_name=_('Episode'),
- null=True, blank=True)
- page = models.ForeignKey('data.Page', null=True, blank=True)
- topik = models.ForeignKey('Topik', verbose_name='Topik',
- null=True, blank=True)
+ def get_cell_extra_context(self, context):
+ soundfiles = SoundFile.objects.prefetch_related('episode__emission__categories')
+ soundfiles = soundfiles.filter(podcastable=True)
+ soundfiles = soundfiles.exclude(creation_timestamp__isnull=True)
+ if not self.include_fragments:
+ soundfiles = soundfiles.filter(fragment=False)
+ if self.limit_to_focus:
+ soundfiles = soundfiles.filter(got_focus__isnull=False)
+ if self.sound_format:
+ soundfiles = soundfiles.filter(format_id=self.sound_format_id)
+ if self.minimal_duration:
+ soundfiles = soundfiles.filter(duration__gte=self.minimal_duration * 60)
+ if self.maximal_duration:
+ soundfiles = soundfiles.filter(duration__lte=self.maximal_duration * 60)
+ if self.tags.exists():
+ soundfiles = soundfiles.filter(episode__tags__in=self.tags.all())
+ soundfiles = (
+ soundfiles.select_related()
+ .extra(
+ select={
+ 'first_diffusion': 'emissions_diffusion.datetime',
+ },
+ select_params=(False, True),
+ where=[
+ '''datetime = (SELECT MIN(datetime)
+ FROM emissions_diffusion
+ WHERE episode_id = emissions_episode.id)'''
+ ],
+ tables=['emissions_diffusion'],
+ )
+ .order_by(self.sort_order)
+ .distinct()
+ )
+ return {
+ 'include_search_input': self.include_search_input,
+ 'soundfiles': soundfiles[: self.count],
+ }
@register_cell_class
-class TopikCell(CellBase):
- topik = models.ForeignKey(Topik, null=True)
- text = RichTextField(_('Text'), blank=True, null=True)
+class WeekProgramCell(CellBase):
+ include_nonstop = models.BooleanField(_('Include nonstop'), default=True)
- template_name = 'panikombo/topik-cell.html'
+ default_template_name = 'panikombo/week_program.html'
class Meta:
- verbose_name = _('Topik')
+ verbose_name = _('Week Program')
- def get_additional_label(self):
- if not self.topik:
- return ''
- return self.topik.page.title
+ @staticmethod
+ def tofirstdayinisoweek(year, week):
+ # from http://stackoverflow.com/questions/5882405/get-date-from-iso-week-number-in-python
+ ret = datetime.strptime('%04d-%02d-1' % (year, week), '%Y-%W-%w')
+ if datetime(year, 1, 4).isoweekday() > 4:
+ ret -= timedelta(days=7)
+ return ret
+
+ def get_cell_extra_context(self, context):
+ ctx = super().get_cell_extra_context(context)
+ year = context['request'].GET.get('year') if 'request' in context else None
+ week = context['request'].GET.get('week') if 'request' in context else None
+ weekday = context['request'].GET.get('weekday') if 'request' in context else None
+ year = int(year if year else datetime.today().isocalendar()[0])
+ week = int(week if week else datetime.today().isocalendar()[1])
+ weekday = int(weekday if weekday is not None else datetime.today().weekday())
+
+ date = WeekProgramCell.tofirstdayinisoweek(year, week)
+ date = datetime(*date.timetuple()[:3])
+
+ previous_week = date - timedelta(days=7)
+ previous_week_year, previous_week_no = previous_week.isocalendar()[:2]
+
+ next_week = date + timedelta(days=7)
+ next_week_year, next_week_no = next_week.isocalendar()[:2]
+
+ program = period_program(date, date + timedelta(days=7), include_nonstop=self.include_nonstop)
+ days = []
+ for day in range(7):
+ days.append(
+ {
+ 'cells': [x for x in program if x.is_on_weekday(day + 1)],
+ 'datetime': date + timedelta(days=day),
+ }
+ )
+
+ ctx.update(
+ {
+ 'days': days,
+ 'weekday': weekday,
+ 'week': week,
+ 'year': year,
+ 'previous_week_year': previous_week_year,
+ 'previous_week_no': previous_week_no,
+ 'next_week_year': next_week_year,
+ 'next_week_no': next_week_no,
+ }
+ )
+
+ return ctx
+
+
+@register_cell_class
+class FocusCarrouselCell(CellBase):
+ count = models.PositiveSmallIntegerField(_('Count'), default=3)
+
+ default_template_name = 'panikombo/focus_carrousel.html'
+
+ class Meta:
+ verbose_name = _('Focus carrousel')
+
+ def get_cell_extra_context(self, context):
+ ctx = super().get_cell_extra_context(context)
+ ctx['news'] = (
+ Focus.objects.filter(current=True)
+ .select_related('emission', 'newsitem', 'soundfile', 'episode', 'newsitem__category')
+ .order_by('?')[: self.count]
+ )
+ return ctx
+
+
+@register_cell_class
+class EmissionsCell(CellBase):
+ title = models.CharField(_('Title'), max_length=50, blank=True)
+ count = models.PositiveSmallIntegerField(_('Count'), default=3)
+ include_active = models.BooleanField(_('Include active'), default=True)
+ include_archived = models.BooleanField(_('Include archived'), default=False)
+ sort_order = models.CharField(
+ _('Sort order'),
+ default='-creation_timestamp',
+ max_length=30,
+ choices=[
+ ('title', _('Alphabetical')),
+ ('-creation_timestamp', _('Reverse chronological (latest emissions)')),
+ ('?', _('Random')),
+ ],
+ )
+
+ default_template_name = 'panikombo/emissions.html'
+
+ class Meta:
+ verbose_name = _('Emissions')
+
+ def get_cell_extra_context(self, context):
+ ctx = super().get_cell_extra_context(context)
+
+ qs = Emission.objects.all()
+ if not self.include_active:
+ qs = qs.exclude(archived=False)
+ if not self.include_archived:
+ qs = qs.exclude(archived=True)
+
+ qs = qs.order_by(self.sort_order)
+
+ ctx['emissions'] = qs[: self.count]
+
+ return ctx