]> git.0d.be Git - django-panik-combo.git/blob - panikombo/models.py
trivial: remove unused imports
[django-panik-combo.git] / panikombo / models.py
1 from datetime import date, datetime, timedelta
2
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
13
14
15 @register_cell_class
16 class SoundCell(CellBase):
17     soundfile = models.ForeignKey('emissions.SoundFile', null=True, on_delete=models.CASCADE)
18
19     class Meta:
20         verbose_name = _('Sound')
21
22     def render(self, context):
23         tmpl = template.loader.get_template('panikombo/audio.html')
24         context['soundfile'] = self.soundfile
25         return tmpl.render(context)
26
27     def get_default_form_class(self):
28         from .forms import SoundCellForm
29
30         return SoundCellForm
31
32     def get_included_items(self):
33         if not self.soundfile:
34             return []
35         return [self.soundfile.episode]
36
37     def get_additional_label(self):
38         if self.soundfile:
39             if self.soundfile.fragment:
40                 return '%s - %s - %s' % (
41                     self.soundfile.episode.emission.title,
42                     self.soundfile.episode.title,
43                     self.soundfile.title,
44                 )
45             else:
46                 return '%s - %s' % (self.soundfile.episode.emission.title, self.soundfile.episode.title)
47
48         return ''
49
50
51 @register_cell_class
52 class EpisodeCell(CellBase):
53     episode = models.ForeignKey('emissions.Episode', null=True, on_delete=models.CASCADE)
54
55     class Meta:
56         verbose_name = _('Episode')
57
58     def render(self, context):
59         tmpl = template.loader.get_template('panikombo/episode.html')
60         context['episode'] = self.episode
61         if self.episode:
62             context['soundfile'] = self.episode.main_sound
63         return tmpl.render(context)
64
65     def get_included_items(self):
66         if not self.episode:
67             return []
68         return [self.episode]
69
70     def get_default_form_class(self):
71         from .forms import EpisodeCellForm
72
73         return EpisodeCellForm
74
75     def get_additional_label(self):
76         if self.episode:
77             return '%s - %s' % (self.episode.emission.title, self.episode.title)
78         return ''
79
80
81 def get_parsed_tags(tagstring):
82     tags = parse_tags(tagstring)
83     return [x for x in Tag.objects.filter(name__in=tags)]
84
85
86 @register_cell_class
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')))
94     )
95
96     default_template_name = 'panikombo/episode_auto_selection.html'
97
98     class Meta:
99         verbose_name = _('Automatic Episode Selection')
100
101     def get_included_items(self):
102         episodes_queryset = Episode.objects.select_related()
103         if self.category:
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())
107             if self.and_tags:
108                 and_tags = get_parsed_tags(self.and_tags)
109                 episodes_queryset = episodes_queryset.filter(tags__in=and_tags)
110
111         if self.period == 0:
112             episodes_queryset = episodes_queryset.extra(
113                 select={
114                     'first_diffusion': 'emissions_diffusion.datetime',
115                 },
116                 select_params=(False, True),
117                 where=[
118                     '''datetime = (SELECT MIN(datetime) FROM emissions_diffusion
119                                            WHERE episode_id = emissions_episode.id)'''
120                 ],
121                 tables=['emissions_diffusion'],
122             )
123             episodes_queryset = episodes_queryset.order_by('first_diffusion').distinct()
124         elif self.period == 1:
125             episodes_queryset = episodes_queryset.extra(
126                 select={
127                     'first_diffusion': 'emissions_diffusion.datetime',
128                 },
129                 select_params=(False, True),
130                 where=[
131                     '''datetime = (SELECT MIN(datetime) FROM emissions_diffusion
132                                            WHERE episode_id = emissions_episode.id) AND
133                                                  datetime >= CURRENT_TIMESTAMP'''
134                 ],
135                 tables=['emissions_diffusion'],
136             )
137             episodes_queryset = episodes_queryset.order_by('-first_diffusion').distinct()
138         elif self.period == 2:
139             episodes_queryset = episodes_queryset.extra(
140                 select={
141                     'first_diffusion': 'emissions_diffusion.datetime',
142                 },
143                 select_params=(False, True),
144                 where=[
145                     '''datetime = (SELECT MIN(datetime) FROM emissions_diffusion
146                                            WHERE episode_id = emissions_episode.id) AND
147                                                  datetime < CURRENT_TIMESTAMP'''
148                 ],
149                 tables=['emissions_diffusion'],
150             )
151             episodes_queryset = episodes_queryset.order_by('-first_diffusion').distinct()
152
153         return episodes_queryset
154
155     def get_cell_extra_context(self, context):
156         ctx = super().get_cell_extra_context(context)
157         ctx['title'] = self.title
158
159         if self.category or self.period or self.tags.count():
160             ctx['episodes'] = self.get_included_items()
161         else:
162             ctx['episodes'] = []
163
164         return ctx
165
166     def get_default_form_class(self):
167         from .forms import EpisodeAutoSelectionCellForm
168
169         return EpisodeAutoSelectionCellForm
170
171     def get_additional_label(self):
172         if self.title:
173             return self.title
174         return ''
175
176
177 @register_cell_class
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
185     )
186
187     default_template_name = 'panikombo/newsitem_auto_selection.html'
188
189     class Meta:
190         verbose_name = _('Automatic Newsitem Selection')
191
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())
196             if self.and_tags:
197                 and_tags = get_parsed_tags(self.and_tags)
198                 newsitems_queryset = newsitems_queryset.filter(tags__in=and_tags)
199         if self.future:
200             newsitems_queryset = newsitems_queryset.filter(event_date__gte=date.today())
201         if self.category:
202             newsitems_queryset = newsitems_queryset.filter(category=self.category)
203         newsitems_queryset = newsitems_queryset.order_by('-event_date', '-creation_timestamp')
204         return newsitems_queryset
205
206     def get_cell_extra_context(self, context):
207         ctx = super().get_cell_extra_context(context)
208         ctx['title'] = self.title
209
210         if self.tags.count() or self.future or self.category:
211             ctx['newsitems'] = self.get_included_items()
212         else:
213             ctx['newsitems'] = []
214
215         return ctx
216
217     def get_default_form_class(self):
218         from .forms import NewsItemAutoSelectionCellForm
219
220         return NewsItemAutoSelectionCellForm
221
222     def get_additional_label(self):
223         if self.title:
224             return self.title
225         return ''
226
227
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
231     )
232     episode = models.ForeignKey(
233         'emissions.Episode', verbose_name=_('Episode'), null=True, blank=True, on_delete=models.SET_NULL
234     )
235     page = models.ForeignKey('data.Page', null=True, blank=True, on_delete=models.SET_NULL)
236
237
238 @register_cell_class
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
246     )
247     tags = TaggableManager(_('Tags'), blank=True)
248     minimal_duration = models.PositiveIntegerField(
249         _('Minimal duration (in minutes)'), default=None, blank=True, null=True
250     )
251     maximal_duration = models.PositiveIntegerField(
252         _('Maximal duration (in minutes)'), default=None, blank=True, null=True
253     )
254     count = models.PositiveSmallIntegerField(_('Count'), default=20)
255     sort_order = models.CharField(
256         _('Sort order'),
257         default='-creation_timestamp',
258         max_length=30,
259         choices=[
260             ('-creation_timestamp', _('Reverse chronological (creation)')),
261             ('-first_diffusion', _('Reverse chronological (diffusion)')),
262             ('creation_timestamp', _('Chronological (creation)')),
263             ('first_diffusion', _('Chronological (diffusion)')),
264             ('?', _('Random')),
265         ],
266     )
267
268     class Meta:
269         verbose_name = _('Sounds')
270
271     def get_default_form_fields(self):
272         fields = super().get_default_form_fields()
273         fields.insert(fields.index('minimal_duration'), 'tags')
274         return fields
275
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())
291         soundfiles = (
292             soundfiles.select_related()
293             .extra(
294                 select={
295                     'first_diffusion': 'emissions_diffusion.datetime',
296                 },
297                 select_params=(False, True),
298                 where=[
299                     '''datetime = (SELECT MIN(datetime)
300                                             FROM emissions_diffusion
301                                         WHERE episode_id = emissions_episode.id)'''
302                 ],
303                 tables=['emissions_diffusion'],
304             )
305             .order_by(self.sort_order)
306             .distinct()
307         )
308         return {
309             'include_search_input': self.include_search_input,
310             'soundfiles': soundfiles[: self.count],
311         }
312
313
314 @register_cell_class
315 class WeekProgramCell(CellBase):
316     include_nonstop = models.BooleanField(_('Include nonstop'), default=True)
317
318     default_template_name = 'panikombo/week_program.html'
319
320     class Meta:
321         verbose_name = _('Week Program')
322
323     @staticmethod
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)
329         return ret
330
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())
339
340         date = WeekProgramCell.tofirstdayinisoweek(year, week)
341         date = datetime(*date.timetuple()[:3])
342
343         previous_week = date - timedelta(days=7)
344         previous_week_year, previous_week_no = previous_week.isocalendar()[:2]
345
346         next_week = date + timedelta(days=7)
347         next_week_year, next_week_no = next_week.isocalendar()[:2]
348
349         program = period_program(date, date + timedelta(days=7), include_nonstop=self.include_nonstop)
350         days = []
351         for day in range(7):
352             days.append(
353                 {
354                     'cells': [x for x in program if x.is_on_weekday(day + 1)],
355                     'datetime': date + timedelta(days=day),
356                 }
357             )
358
359         ctx.update(
360             {
361                 'days': days,
362                 'weekday': weekday,
363                 'week': week,
364                 'year': year,
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,
369             }
370         )
371
372         return ctx
373
374
375 @register_cell_class
376 class FocusCarrouselCell(CellBase):
377     count = models.PositiveSmallIntegerField(_('Count'), default=3)
378
379     default_template_name = 'panikombo/focus_carrousel.html'
380
381     class Meta:
382         verbose_name = _('Focus carrousel')
383
384     def get_cell_extra_context(self, context):
385         ctx = super().get_cell_extra_context(context)
386         ctx['news'] = (
387             Focus.objects.filter(current=True)
388             .select_related('emission', 'newsitem', 'soundfile', 'episode', 'newsitem__category')
389             .order_by('?')[: self.count]
390         )
391         return ctx
392
393
394 @register_cell_class
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(
401         _('Sort order'),
402         default='-creation_timestamp',
403         max_length=30,
404         choices=[
405             ('title', _('Alphabetical')),
406             ('-creation_timestamp', _('Reverse chronological (latest emissions)')),
407             ('?', _('Random')),
408         ],
409     )
410
411     default_template_name = 'panikombo/emissions.html'
412
413     class Meta:
414         verbose_name = _('Emissions')
415
416     def get_cell_extra_context(self, context):
417         ctx = super().get_cell_extra_context(context)
418
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)
424
425         qs = qs.order_by(self.sort_order)
426
427         ctx['emissions'] = qs[: self.count]
428
429         return ctx