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 = _('Episodes')
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
186 count = models.PositiveSmallIntegerField(_('Count'), default=20)
188 default_template_name = 'panikombo/newsitem_auto_selection.html'
191 verbose_name = _('Newsitems')
193 def get_included_items(self):
194 newsitems_queryset = NewsItem.objects.select_related()
195 if self.tags.count():
196 newsitems_queryset = newsitems_queryset.filter(tags__in=self.tags.all())
198 and_tags = get_parsed_tags(self.and_tags)
199 newsitems_queryset = newsitems_queryset.filter(tags__in=and_tags)
201 newsitems_queryset = newsitems_queryset.filter(event_date__gte=date.today())
203 newsitems_queryset = newsitems_queryset.filter(category=self.category)
204 newsitems_queryset = newsitems_queryset.order_by('-event_date', '-creation_timestamp')
205 return newsitems_queryset[: self.count]
207 def get_cell_extra_context(self, context):
208 ctx = super().get_cell_extra_context(context)
209 ctx['title'] = self.title
210 ctx['newsitems'] = self.get_included_items()
213 def get_default_form_class(self):
214 from .forms import NewsItemAutoSelectionCellForm
216 return NewsItemAutoSelectionCellForm
218 def get_additional_label(self):
224 class ItemTopik(models.Model):
225 newsitem = models.ForeignKey(
226 'emissions.NewsItem', verbose_name=_('News Item'), null=True, blank=True, on_delete=models.SET_NULL
228 episode = models.ForeignKey(
229 'emissions.Episode', verbose_name=_('Episode'), null=True, blank=True, on_delete=models.SET_NULL
231 page = models.ForeignKey('data.Page', null=True, blank=True, on_delete=models.SET_NULL)
235 class SoundsCell(CellBase):
236 title = models.CharField(_('Title'), max_length=150, blank=True)
237 include_search_input = models.BooleanField(_('Include search input'), default=True)
238 include_fragments = models.BooleanField(_('Include fragments'), default=True)
239 limit_to_focus = models.BooleanField(_('Limit to focused elements'), default=False)
240 sound_format = models.ForeignKey(
241 'emissions.Format', verbose_name=_('Limit to format'), null=True, blank=True, on_delete=models.CASCADE
243 tags = TaggableManager(_('Tags'), blank=True)
244 minimal_duration = models.PositiveIntegerField(
245 _('Minimal duration (in minutes)'), default=None, blank=True, null=True
247 maximal_duration = models.PositiveIntegerField(
248 _('Maximal duration (in minutes)'), default=None, blank=True, null=True
250 count = models.PositiveSmallIntegerField(_('Count'), default=20)
251 sort_order = models.CharField(
253 default='-creation_timestamp',
256 ('-creation_timestamp', _('Reverse chronological (creation)')),
257 ('-first_diffusion', _('Reverse chronological (diffusion)')),
258 ('creation_timestamp', _('Chronological (creation)')),
259 ('first_diffusion', _('Chronological (diffusion)')),
265 verbose_name = _('Sounds')
267 def get_default_form_fields(self):
268 fields = super().get_default_form_fields()
269 fields.insert(fields.index('minimal_duration'), 'tags')
272 def get_cell_extra_context(self, context):
273 soundfiles = SoundFile.objects.prefetch_related('episode__emission__categories')
274 soundfiles = soundfiles.filter(podcastable=True)
275 soundfiles = soundfiles.exclude(creation_timestamp__isnull=True)
276 if not self.include_fragments:
277 soundfiles = soundfiles.filter(fragment=False)
278 if self.limit_to_focus:
279 soundfiles = soundfiles.filter(got_focus__isnull=False)
280 if self.sound_format:
281 soundfiles = soundfiles.filter(format_id=self.sound_format_id)
282 if self.minimal_duration:
283 soundfiles = soundfiles.filter(duration__gte=self.minimal_duration * 60)
284 if self.maximal_duration:
285 soundfiles = soundfiles.filter(duration__lte=self.maximal_duration * 60)
286 if self.tags.exists():
287 soundfiles = soundfiles.filter(episode__tags__in=self.tags.all())
289 soundfiles.select_related()
292 'first_diffusion': 'emissions_diffusion.datetime',
294 select_params=(False, True),
296 '''datetime = (SELECT MIN(datetime)
297 FROM emissions_diffusion
298 WHERE episode_id = emissions_episode.id)'''
300 tables=['emissions_diffusion'],
302 .order_by(self.sort_order)
306 'include_search_input': self.include_search_input,
307 'soundfiles': soundfiles[: self.count],
312 class WeekProgramCell(CellBase):
313 include_nonstop = models.BooleanField(_('Include nonstop'), default=True)
315 default_template_name = 'panikombo/week_program.html'
318 verbose_name = _('Week Program')
321 def tofirstdayinisoweek(year, week):
322 # from http://stackoverflow.com/questions/5882405/get-date-from-iso-week-number-in-python
323 ret = datetime.strptime('%04d-%02d-1' % (year, week), '%Y-%W-%w')
324 if datetime(year, 1, 4).isoweekday() > 4:
325 ret -= timedelta(days=7)
328 def get_cell_extra_context(self, context):
329 ctx = super().get_cell_extra_context(context)
330 year = context['request'].GET.get('year') if 'request' in context else None
331 week = context['request'].GET.get('week') if 'request' in context else None
332 weekday = context['request'].GET.get('weekday') if 'request' in context else None
333 year = int(year if year else datetime.today().isocalendar()[0])
334 week = int(week if week else datetime.today().isocalendar()[1])
335 weekday = int(weekday if weekday is not None else datetime.today().weekday())
337 date = WeekProgramCell.tofirstdayinisoweek(year, week)
338 date = datetime(*date.timetuple()[:3])
340 previous_week = date - timedelta(days=7)
341 previous_week_year, previous_week_no = previous_week.isocalendar()[:2]
343 next_week = date + timedelta(days=7)
344 next_week_year, next_week_no = next_week.isocalendar()[:2]
346 program = period_program(date, date + timedelta(days=7), include_nonstop=self.include_nonstop)
351 'cells': [x for x in program if x.is_on_weekday(day + 1)],
352 'datetime': date + timedelta(days=day),
362 'previous_week_year': previous_week_year,
363 'previous_week_no': previous_week_no,
364 'next_week_year': next_week_year,
365 'next_week_no': next_week_no,
373 class FocusCarrouselCell(CellBase):
374 count = models.PositiveSmallIntegerField(_('Count'), default=3)
376 default_template_name = 'panikombo/focus_carrousel.html'
379 verbose_name = _('Focus carrousel')
381 def get_cell_extra_context(self, context):
382 ctx = super().get_cell_extra_context(context)
384 Focus.objects.filter(current=True)
385 .select_related('emission', 'newsitem', 'soundfile', 'episode', 'newsitem__category')
386 .order_by('?')[: self.count]
392 class EmissionsCell(CellBase):
393 title = models.CharField(_('Title'), max_length=50, blank=True)
394 count = models.PositiveSmallIntegerField(_('Count'), default=3)
395 include_active = models.BooleanField(_('Include active'), default=True)
396 include_archived = models.BooleanField(_('Include archived'), default=False)
397 sort_order = models.CharField(
399 default='-creation_timestamp',
402 ('title', _('Alphabetical')),
403 ('-creation_timestamp', _('Reverse chronological (latest emissions)')),
408 default_template_name = 'panikombo/emissions.html'
411 verbose_name = _('Emissions')
413 def get_cell_extra_context(self, context):
414 ctx = super().get_cell_extra_context(context)
416 qs = Emission.objects.all()
417 if not self.include_active:
418 qs = qs.exclude(archived=False)
419 if not self.include_archived:
420 qs = qs.exclude(archived=True)
422 qs = qs.order_by(self.sort_order)
424 ctx['emissions'] = qs[: self.count]