1 from datetime import date, datetime, timedelta
3 from combo.data.library import register_cell_class
4 from combo.data.models import CellBase
5 from django import template
6 from django.db import models
7 from django.utils.translation import gettext_lazy as _
8 from emissions.models import Emission, Episode, Focus, NewsItem, SoundFile
9 from emissions.utils import period_program
10 from taggit.managers import TaggableManager
11 from taggit.models import Tag
12 from taggit.utils import parse_tags
16 class SoundCell(CellBase):
17 soundfile = models.ForeignKey('emissions.SoundFile', null=True, on_delete=models.CASCADE)
20 verbose_name = _('Sound')
22 def render(self, context):
23 tmpl = template.loader.get_template('panikombo/audio.html')
24 context['soundfile'] = self.soundfile
25 return tmpl.render(context)
27 def get_default_form_class(self):
28 from .forms import SoundCellForm
32 def get_included_items(self):
33 if not self.soundfile:
35 return [self.soundfile.episode]
37 def get_additional_label(self):
39 if self.soundfile.fragment:
40 return '%s - %s - %s' % (
41 self.soundfile.episode.emission.title,
42 self.soundfile.episode.title,
46 return '%s - %s' % (self.soundfile.episode.emission.title, self.soundfile.episode.title)
52 class EpisodeCell(CellBase):
53 episode = models.ForeignKey('emissions.Episode', null=True, on_delete=models.CASCADE)
56 verbose_name = _('Episode')
58 def render(self, context):
59 tmpl = template.loader.get_template('panikombo/episode.html')
60 context['episode'] = self.episode
62 context['soundfile'] = self.episode.main_sound
63 return tmpl.render(context)
65 def get_included_items(self):
70 def get_default_form_class(self):
71 from .forms import EpisodeCellForm
73 return EpisodeCellForm
75 def get_additional_label(self):
77 return '%s - %s' % (self.episode.emission.title, self.episode.title)
81 def get_parsed_tags(tagstring):
82 tags = parse_tags(tagstring)
83 return [x for x in Tag.objects.filter(name__in=tags)]
87 class EpisodeAutoSelectionCell(CellBase):
88 title = models.CharField(_('Title'), max_length=50, blank=True)
89 tags = TaggableManager(_('Tags'), blank=True)
90 and_tags = models.CharField(_('And Tags'), max_length=100, blank=True)
91 category = models.ForeignKey('emissions.Category', null=True, blank=True, on_delete=models.CASCADE)
92 period = models.PositiveSmallIntegerField(
93 _('Period'), default=0, choices=((0, _('All')), (1, _('Future')), (2, _('Past')))
96 default_template_name = 'panikombo/episode_auto_selection.html'
99 verbose_name = _('Automatic Episode Selection')
101 def get_included_items(self):
102 episodes_queryset = Episode.objects.select_related()
104 episodes_queryset = episodes_queryset.filter(emission__categories__in=[self.category.id])
105 if self.tags.count():
106 episodes_queryset = episodes_queryset.filter(tags__in=self.tags.all())
108 and_tags = get_parsed_tags(self.and_tags)
109 episodes_queryset = episodes_queryset.filter(tags__in=and_tags)
112 episodes_queryset = episodes_queryset.extra(
114 'first_diffusion': 'emissions_diffusion.datetime',
116 select_params=(False, True),
118 '''datetime = (SELECT MIN(datetime) FROM emissions_diffusion
119 WHERE episode_id = emissions_episode.id)'''
121 tables=['emissions_diffusion'],
123 episodes_queryset = episodes_queryset.order_by('first_diffusion').distinct()
124 elif self.period == 1:
125 episodes_queryset = episodes_queryset.extra(
127 'first_diffusion': 'emissions_diffusion.datetime',
129 select_params=(False, True),
131 '''datetime = (SELECT MIN(datetime) FROM emissions_diffusion
132 WHERE episode_id = emissions_episode.id) AND
133 datetime >= CURRENT_TIMESTAMP'''
135 tables=['emissions_diffusion'],
137 episodes_queryset = episodes_queryset.order_by('-first_diffusion').distinct()
138 elif self.period == 2:
139 episodes_queryset = episodes_queryset.extra(
141 'first_diffusion': 'emissions_diffusion.datetime',
143 select_params=(False, True),
145 '''datetime = (SELECT MIN(datetime) FROM emissions_diffusion
146 WHERE episode_id = emissions_episode.id) AND
147 datetime < CURRENT_TIMESTAMP'''
149 tables=['emissions_diffusion'],
151 episodes_queryset = episodes_queryset.order_by('-first_diffusion').distinct()
153 return episodes_queryset
155 def get_cell_extra_context(self, context):
156 ctx = super().get_cell_extra_context(context)
157 ctx['title'] = self.title
159 if self.category or self.period or self.tags.count():
160 ctx['episodes'] = self.get_included_items()
166 def get_default_form_class(self):
167 from .forms import EpisodeAutoSelectionCellForm
169 return EpisodeAutoSelectionCellForm
171 def get_additional_label(self):
178 class NewsItemAutoSelectionCell(CellBase):
179 title = models.CharField(_('Title'), max_length=50, blank=True)
180 tags = TaggableManager(_('Tags'), blank=True)
181 and_tags = models.CharField(_('And Tags'), max_length=100, blank=True)
182 future = models.BooleanField(_('Future Events Only'), default=True)
183 category = models.ForeignKey(
184 'emissions.NewsCategory', verbose_name=_('Category'), null=True, blank=True, on_delete=models.SET_NULL
187 default_template_name = 'panikombo/newsitem_auto_selection.html'
190 verbose_name = _('Automatic Newsitem Selection')
192 def get_included_items(self):
193 newsitems_queryset = NewsItem.objects.select_related()
194 if self.tags.count():
195 newsitems_queryset = newsitems_queryset.filter(tags__in=self.tags.all())
197 and_tags = get_parsed_tags(self.and_tags)
198 newsitems_queryset = newsitems_queryset.filter(tags__in=and_tags)
200 newsitems_queryset = newsitems_queryset.filter(event_date__gte=date.today())
202 newsitems_queryset = newsitems_queryset.filter(category=self.category)
203 newsitems_queryset = newsitems_queryset.order_by('-event_date', '-creation_timestamp')
204 return newsitems_queryset
206 def get_cell_extra_context(self, context):
207 ctx = super().get_cell_extra_context(context)
208 ctx['title'] = self.title
210 if self.tags.count() or self.future or self.category:
211 ctx['newsitems'] = self.get_included_items()
213 ctx['newsitems'] = []
217 def get_default_form_class(self):
218 from .forms import NewsItemAutoSelectionCellForm
220 return NewsItemAutoSelectionCellForm
222 def get_additional_label(self):
228 class ItemTopik(models.Model):
229 newsitem = models.ForeignKey(
230 'emissions.NewsItem', verbose_name=_('News Item'), null=True, blank=True, on_delete=models.SET_NULL
232 episode = models.ForeignKey(
233 'emissions.Episode', verbose_name=_('Episode'), null=True, blank=True, on_delete=models.SET_NULL
235 page = models.ForeignKey('data.Page', null=True, blank=True, on_delete=models.SET_NULL)
239 class SoundsCell(CellBase):
240 title = models.CharField(_('Title'), max_length=150, blank=True)
241 include_search_input = models.BooleanField(_('Include search input'), default=True)
242 include_fragments = models.BooleanField(_('Include fragments'), default=True)
243 limit_to_focus = models.BooleanField(_('Limit to focused elements'), default=False)
244 sound_format = models.ForeignKey(
245 'emissions.Format', verbose_name=_('Limit to format'), null=True, blank=True, on_delete=models.CASCADE
247 tags = TaggableManager(_('Tags'), blank=True)
248 minimal_duration = models.PositiveIntegerField(
249 _('Minimal duration (in minutes)'), default=None, blank=True, null=True
251 maximal_duration = models.PositiveIntegerField(
252 _('Maximal duration (in minutes)'), default=None, blank=True, null=True
254 count = models.PositiveSmallIntegerField(_('Count'), default=20)
255 sort_order = models.CharField(
257 default='-creation_timestamp',
260 ('-creation_timestamp', _('Reverse chronological (creation)')),
261 ('-first_diffusion', _('Reverse chronological (diffusion)')),
262 ('creation_timestamp', _('Chronological (creation)')),
263 ('first_diffusion', _('Chronological (diffusion)')),
269 verbose_name = _('Sounds')
271 def get_default_form_fields(self):
272 fields = super().get_default_form_fields()
273 fields.insert(fields.index('minimal_duration'), 'tags')
276 def get_cell_extra_context(self, context):
277 soundfiles = SoundFile.objects.prefetch_related('episode__emission__categories')
278 soundfiles = soundfiles.filter(podcastable=True)
279 if not self.include_fragments:
280 soundfiles = soundfiles.filter(fragment=False)
281 if self.limit_to_focus:
282 soundfiles = soundfiles.filter(got_focus__isnull=False)
283 if self.sound_format:
284 soundfiles = soundfiles.filter(format_id=self.sound_format_id)
285 if self.minimal_duration:
286 soundfiles = soundfiles.filter(duration__gte=self.minimal_duration * 60)
287 if self.maximal_duration:
288 soundfiles = soundfiles.filter(duration__lte=self.maximal_duration * 60)
289 if self.tags.exists():
290 soundfiles = soundfiles.filter(episode__tags__in=self.tags.all())
292 soundfiles.select_related()
295 'first_diffusion': 'emissions_diffusion.datetime',
297 select_params=(False, True),
299 '''datetime = (SELECT MIN(datetime)
300 FROM emissions_diffusion
301 WHERE episode_id = emissions_episode.id)'''
303 tables=['emissions_diffusion'],
305 .order_by(self.sort_order)
309 'include_search_input': self.include_search_input,
310 'soundfiles': soundfiles[: self.count],
315 class WeekProgramCell(CellBase):
316 include_nonstop = models.BooleanField(_('Include nonstop'), default=True)
318 default_template_name = 'panikombo/week_program.html'
321 verbose_name = _('Week Program')
324 def tofirstdayinisoweek(year, week):
325 # from http://stackoverflow.com/questions/5882405/get-date-from-iso-week-number-in-python
326 ret = datetime.strptime('%04d-%02d-1' % (year, week), '%Y-%W-%w')
327 if datetime(year, 1, 4).isoweekday() > 4:
328 ret -= timedelta(days=7)
331 def get_cell_extra_context(self, context):
332 ctx = super().get_cell_extra_context(context)
333 year = context['request'].GET.get('year') if 'request' in context else None
334 week = context['request'].GET.get('week') if 'request' in context else None
335 weekday = context['request'].GET.get('weekday') if 'request' in context else None
336 year = int(year if year else datetime.today().isocalendar()[0])
337 week = int(week if week else datetime.today().isocalendar()[1])
338 weekday = int(weekday if weekday is not None else datetime.today().weekday())
340 date = WeekProgramCell.tofirstdayinisoweek(year, week)
341 date = datetime(*date.timetuple()[:3])
343 previous_week = date - timedelta(days=7)
344 previous_week_year, previous_week_no = previous_week.isocalendar()[:2]
346 next_week = date + timedelta(days=7)
347 next_week_year, next_week_no = next_week.isocalendar()[:2]
349 program = period_program(date, date + timedelta(days=7), include_nonstop=self.include_nonstop)
354 'cells': [x for x in program if x.is_on_weekday(day + 1)],
355 'datetime': date + timedelta(days=day),
365 'previous_week_year': previous_week_year,
366 'previous_week_no': previous_week_no,
367 'next_week_year': next_week_year,
368 'next_week_no': next_week_no,
376 class FocusCarrouselCell(CellBase):
377 count = models.PositiveSmallIntegerField(_('Count'), default=3)
379 default_template_name = 'panikombo/focus_carrousel.html'
382 verbose_name = _('Focus carrousel')
384 def get_cell_extra_context(self, context):
385 ctx = super().get_cell_extra_context(context)
387 Focus.objects.filter(current=True)
388 .select_related('emission', 'newsitem', 'soundfile', 'episode', 'newsitem__category')
389 .order_by('?')[: self.count]
395 class EmissionsCell(CellBase):
396 title = models.CharField(_('Title'), max_length=50, blank=True)
397 count = models.PositiveSmallIntegerField(_('Count'), default=3)
398 include_active = models.BooleanField(_('Include active'), default=True)
399 include_archived = models.BooleanField(_('Include archived'), default=False)
400 sort_order = models.CharField(
402 default='-creation_timestamp',
405 ('title', _('Alphabetical')),
406 ('-creation_timestamp', _('Reverse chronological (latest emissions)')),
411 default_template_name = 'panikombo/emissions.html'
414 verbose_name = _('Emissions')
416 def get_cell_extra_context(self, context):
417 ctx = super().get_cell_extra_context(context)
419 qs = Emission.objects.all()
420 if not self.include_active:
421 qs = qs.exclude(archived=False)
422 if not self.include_archived:
423 qs = qs.exclude(archived=True)
425 qs = qs.order_by(self.sort_order)
427 ctx['emissions'] = qs[: self.count]