]> git.0d.be Git - panikweb.git/blob - panikweb/views.py
1b8b0f4066190fdbfd0c6c115848c18b99e6f09e
[panikweb.git] / panikweb / views.py
1 from datetime import datetime, timedelta, date
2 import math
3 import random
4 import os
5 import stat
6 import time
7
8 from django.core.urlresolvers import reverse
9 from django.conf import settings
10 from django.http import Http404, JsonResponse
11 from django.utils.encoding import force_text
12 from django.utils.encoding import python_2_unicode_compatible
13 from django.utils.six.moves.urllib import parse as urlparse
14 from django.views.decorators.cache import cache_control
15 from django.views.generic.base import TemplateView
16 from django.views.generic.detail import DetailView
17 from django.views.decorators.csrf import csrf_exempt
18 from django.views.generic.dates import _date_from_string
19 from django.views.generic.dates import MonthArchiveView
20
21 from django.core.paginator import Paginator
22
23 from django.contrib.sites.models import Site
24 from django.contrib.syndication.views import Feed, add_domain
25 from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
26
27 from haystack.query import SearchQuerySet
28
29 from emissions.models import Category, Emission, Episode, Diffusion, SoundFile, \
30         Schedule, Nonstop, NewsItem, NewsCategory, Focus
31
32 from emissions.utils import whatsonair, period_program
33
34 from newsletter.forms import SubscribeForm
35 from nonstop.utils import get_current_nonstop_track
36 from nonstop.models import SomaLogLine
37
38 from panikombo.models import ItemTopik
39
40 from . import utils
41
42
43 class EmissionMixin:
44     def get_emission_context(self, emission, episode_ids=None):
45         context = {}
46
47         # get all episodes, with an additional attribute to get the date of
48         # their first diffusion
49         episodes_queryset = Episode.objects.select_related()
50         if episode_ids is not None:
51             episodes_queryset = episodes_queryset.filter(id__in=episode_ids)
52         else:
53             episodes_queryset = episodes_queryset.filter(emission=emission)
54
55         context['episodes'] = \
56                 episodes_queryset.extra(select={
57                         'first_diffusion': 'emissions_diffusion.datetime',
58                         },
59                         select_params=(False, True),
60                         where=['''datetime = (SELECT MIN(datetime)
61                                                 FROM emissions_diffusion
62                                                WHERE episode_id = emissions_episode.id
63                                                  AND datetime <= CURRENT_TIMESTAMP)'''],
64                         tables=['emissions_diffusion'],
65                     ).order_by('-first_diffusion').distinct()
66
67         context['futurEpisodes'] = \
68                 episodes_queryset.extra(select={
69                         'first_diffusion': 'emissions_diffusion.datetime',
70                         },
71                         select_params=(False, True),
72                         where=['''datetime = (SELECT MIN(datetime)
73                                                 FROM emissions_diffusion
74                                                WHERE episode_id = emissions_episode.id
75                                                  AND datetime > CURRENT_TIMESTAMP)'''],
76                         tables=['emissions_diffusion'],
77                     ).order_by('first_diffusion').distinct()
78
79
80         # get all related soundfiles in a single query
81         soundfiles = {}
82         if episode_ids is not None:
83             for episode_id in episode_ids:
84                 soundfiles[episode_id] = None
85         else:
86             for episode in Episode.objects.filter(emission=emission):
87                 soundfiles[episode.id] = None
88
89         for soundfile in SoundFile.objects.select_related().filter(podcastable=True,
90                 fragment=False, episode__emission=emission):
91             soundfiles[soundfile.episode_id] = soundfile
92
93         Episode.set_prefetched_soundfiles(soundfiles)
94
95         #context['futurEpisodes'] = context['episodes'].filter(first_diffusion='2013')[0:3]
96
97         return context
98
99
100 class EmissionDetailView(DetailView, EmissionMixin):
101     model = Emission
102
103     def get_context_data(self, **kwargs):
104         context = super(EmissionDetailView, self).get_context_data(**kwargs)
105         context['schedules'] = Schedule.objects.select_related().filter(
106                 emission=self.object).order_by('rerun', 'datetime')
107         context['news'] = NewsItem.objects.all().filter(emission=self.object.id).order_by('-date')[:3]
108         try:
109             nonstop_object = Nonstop.objects.get(slug=self.object.slug)
110         except Nonstop.DoesNotExist:
111             pass
112         else:
113             today = date.today()
114             dates = [today - timedelta(days=x) for x in range(7)]
115             if datetime.now().time() < nonstop_object.start:
116                 dates = dates[1:]
117             context['nonstop'] = nonstop_object
118             context['nonstop_dates'] = dates
119         context.update(self.get_emission_context(self.object))
120         return context
121 emission = EmissionDetailView.as_view()
122
123 class EpisodeDetailView(DetailView, EmissionMixin):
124     model = Episode
125
126     def get_context_data(self, **kwargs):
127         context = super(EpisodeDetailView, self).get_context_data(**kwargs)
128         context['diffusions'] = Diffusion.objects.select_related().filter(
129                 episode=self.object.id).order_by('datetime')
130         try:
131             context['emission'] = context['episode'].emission
132         except Emission.DoesNotExist:
133             raise Http404()
134         if self.kwargs.get('emission_slug') != context['emission'].slug:
135             raise Http404()
136         context.update(self.get_emission_context(context['emission']))
137         context['topiks'] = [x.topik for x in ItemTopik.objects.filter(episode=self.object)]
138         return context
139 episode = EpisodeDetailView.as_view()
140
141
142 class NonstopPlaylistView(TemplateView):
143     template_name = 'nonstop_playlist.html'
144
145     def get_context_data(self, **kwargs):
146         context = super(NonstopPlaylistView, self).get_context_data(**kwargs)
147         try:
148             context['emission'] = Emission.objects.get(slug=kwargs.get('slug'))
149         except Emission.DoesNotExist:
150             raise Http404()
151         context['date'] = date(int(kwargs.get('year')),
152                 int(kwargs.get('month')), int(kwargs.get('day')))
153         context['future'] = (context['date'] >= date.today())
154
155         nonstop_object = Nonstop.objects.get(slug=kwargs.get('slug'))
156         start = datetime(
157                 int(kwargs.get('year')), int(kwargs.get('month')), int(kwargs.get('day')),
158                 nonstop_object.start.hour, nonstop_object.start.minute)
159         end = datetime(
160                 int(kwargs.get('year')), int(kwargs.get('month')), int(kwargs.get('day')),
161                 nonstop_object.end.hour, nonstop_object.end.minute)
162         if end < start:
163             end = end + timedelta(days=1)
164         context['tracks'] = SomaLogLine.objects.filter(
165                 play_timestamp__gte=start,
166                 play_timestamp__lte=end,
167                 on_air=True).select_related()
168         return context
169
170 nonstop_playlist = NonstopPlaylistView.as_view()
171
172 class EmissionEpisodesDetailView(DetailView, EmissionMixin):
173     model = Emission
174     template_name = 'emissions/episodes.html'
175
176     def get_context_data(self, **kwargs):
177         context = super(EmissionEpisodesDetailView, self).get_context_data(**kwargs)
178         context['schedules'] = Schedule.objects.select_related().filter(
179                 emission=self.object).order_by('rerun', 'datetime')
180
181         context['search_query'] = self.request.GET.get('q')
182         if context['search_query']:
183             # query string
184             sqs = SearchQuerySet().models(Episode).filter(
185                     emission_slug_exact=self.object.slug, text=context['search_query'])
186             episode_ids = [x.pk for x in sqs]
187         else:
188             episode_ids = None
189
190         context.update(self.get_emission_context(self.object, episode_ids=episode_ids))
191         return context
192 emissionEpisodes = EmissionEpisodesDetailView.as_view()
193
194
195 class SoundFileEmbedView(DetailView):
196     model = SoundFile
197     template_name = 'soundfiles/embed.html'
198
199     def get_context_data(self, **kwargs):
200         context = super(SoundFileEmbedView, self).get_context_data(**kwargs)
201         if self.kwargs.get('episode_slug') != self.object.episode.slug:
202             raise Http404()
203         if self.kwargs.get('emission_slug') != self.object.episode.emission.slug:
204             raise Http404()
205         context['episode'] = self.object.episode
206         return context
207 soundfile_embed = SoundFileEmbedView.as_view()
208
209
210 class SoundFileDialogEmbedView(DetailView):
211     model = SoundFile
212     template_name = 'soundfiles/dialog-embed.html'
213
214     def get_context_data(self, **kwargs):
215         context = super(SoundFileDialogEmbedView, self).get_context_data(**kwargs)
216         if self.kwargs.get('episode_slug') != self.object.episode.slug:
217             raise Http404()
218         if self.kwargs.get('emission_slug') != self.object.episode.emission.slug:
219             raise Http404()
220         context['episode'] = self.object.episode
221         return context
222 soundfile_dlg_embed = SoundFileDialogEmbedView.as_view()
223
224
225
226 class ProgramView(TemplateView):
227     template_name = 'program.html'
228
229     def get_context_data(self, year=None, week=None, **kwargs):
230         context = super(ProgramView, self).get_context_data(**kwargs)
231
232         context['weekday'] = datetime.today().weekday()
233
234         context['week'] = week = int(week) if week is not None else datetime.today().isocalendar()[1]
235         context['year'] = year = int(year) if year is not None else datetime.today().isocalendar()[0]
236         context['week_first_day'] = utils.tofirstdayinisoweek(year, week)
237         context['week_last_day'] = context['week_first_day'] + timedelta(days=6)
238
239         return context
240
241 program = ProgramView.as_view()
242
243 @python_2_unicode_compatible
244 class TimeCell:
245     nonstop = None
246     w = 1
247     h = 1
248     time_label = None
249
250     def __init__(self, i, j):
251         self.x = i
252         self.y = j
253         self.schedules = []
254
255     def add_schedule(self, schedule):
256         end_time = schedule.datetime + timedelta(
257                 minutes=schedule.get_duration())
258         self.time_label = '%02d:%02d-%02d:%02d' % (
259                 schedule.datetime.hour,
260                 schedule.datetime.minute,
261                 end_time.hour,
262                 end_time.minute)
263         self.schedules.append(schedule)
264
265     def __str__(self):
266         if self.schedules:
267             return ', '.join([x.emission.title for x in self.schedules])
268         else:
269             return self.nonstop
270
271     def __eq__(self, other):
272         return (force_text(self) == force_text(other) and self.time_label == other.time_label)
273
274
275 class Grid(TemplateView):
276     template_name = 'grid.html'
277
278     def get_context_data(self, **kwargs):
279         context = super(Grid, self).get_context_data(**kwargs)
280
281         nb_lines = 2 * 24 # the cells are half hours
282         grid = []
283
284         times = ['%02d:%02d' % (x/2, x%2*30) for x in range(nb_lines)]
285         # start grid after the night programs
286         times = times[2*Schedule.DAY_HOUR_START:] + times[:2*Schedule.DAY_HOUR_START]
287
288         nonstops = []
289         for nonstop in Nonstop.objects.all():
290             if nonstop.start == nonstop.end:
291                 continue
292             if nonstop.start < nonstop.end:
293                 nonstops.append([nonstop.start.hour + nonstop.start.minute/60.,
294                                  nonstop.end.hour + nonstop.end.minute/60.,
295                                  nonstop.title, nonstop.slug, nonstop])
296             else:
297                 # crossing midnight
298                 nonstops.append([nonstop.start.hour + nonstop.start.minute/60.,
299                                  24,
300                                  nonstop.title, nonstop.slug, nonstop])
301                 nonstops.append([0,
302                                  nonstop.end.hour + nonstop.end.minute/60.,
303                                  nonstop.title, nonstop.slug, nonstop])
304         nonstops.sort()
305
306         for i in range(nb_lines):
307             grid.append([])
308             for j in range(7):
309                 grid[-1].append(TimeCell(i, j))
310
311             nonstop = [x for x in nonstops if i>=x[0]*2 and i<x[1]*2][0]
312             for time_cell in grid[-1]:
313                 time_cell.nonstop = nonstop[2]
314                 time_cell.nonstop_slug = nonstop[3]
315                 time_cell.redirect_path = nonstop[4].redirect_path
316                 if nonstop[1] == 5:
317                     # the one ending at 5am will be cut down, so we inscribe
318                     # its duration manually
319                     time_cell.time_label = '%02d:00-%02d:00' % (
320                             nonstop[0], nonstop[1])
321
322         for schedule in Schedule.objects.prefetch_related(
323                 'emission__categories').select_related().order_by('datetime'):
324             row_start = schedule.datetime.hour * 2 + \
325                     int(math.ceil(schedule.datetime.minute / 30))
326             day_no = schedule.get_weekday()
327
328             for step in range(int(math.ceil(schedule.get_duration() / 30.))):
329                 if grid[(row_start+step)%nb_lines][day_no] is None:
330                     grid[(row_start+step)%nb_lines][day_no] = TimeCell()
331                 grid[(row_start+step)%nb_lines][day_no].add_schedule(schedule)
332
333         # start grid after the night programs
334         grid = grid[2*Schedule.DAY_HOUR_START:] + grid[:2*Schedule.DAY_HOUR_START]
335
336         # look for the case where the same emission has different schedules for
337         # the same time cell, for example if it lasts one hour the first week,
338         # and two hours the third week.
339         for i in range(nb_lines):
340             grid[i] = [x for x in grid[i] if x is not None]
341             for j, cell in enumerate(grid[i]):
342                 if grid[i][j] is None:
343                     continue
344                 if len(grid[i][j].schedules) > 1:
345                     time_cell_emissions = {}
346                     for schedule in grid[i][j].schedules:
347                         if not schedule.emission.id in time_cell_emissions:
348                             time_cell_emissions[schedule.emission.id] = []
349                         time_cell_emissions[schedule.emission.id].append(schedule)
350                     for schedule_list in time_cell_emissions.values():
351                         if len(schedule_list) == 1:
352                             continue
353                         # here it is, same cell, same emission, several
354                         # schedules
355                         schedule_list.sort(key=lambda x: x.get_duration())
356
357                         schedule = schedule_list[0]
358                         end_time = schedule.datetime + timedelta(
359                                 minutes=schedule.get_duration())
360                         grid[i][j].time_label = '%02d:%02d-%02d:%02d' % (
361                                 schedule.datetime.hour,
362                                 schedule.datetime.minute,
363                                 end_time.hour,
364                                 end_time.minute)
365
366                         for schedule in schedule_list[1:]:
367                             grid[i][j].schedules.remove(schedule)
368                             end_time = schedule.datetime + timedelta(minutes=schedule.get_duration())
369                             schedule_list[0].time_label_extra = ', -%02d:%02d %s' % (
370                                     end_time.hour, end_time.minute, schedule.weeks_string)
371
372         # merge adjacent
373
374         # 1st thing is to merge cells on the same line, this will mostly catch
375         # consecutive nonstop cells
376         for i in range(nb_lines):
377             for j, cell in enumerate(grid[i]):
378                 if grid[i][j] is None:
379                     continue
380                 t = 1
381                 try:
382                     # if the cells are identical, they are removed from the
383                     # grid, and current cell width is increased
384                     while grid[i][j+t] == cell:
385                         cell.w += 1
386                         grid[i][j+t] = None
387                         t += 1
388                 except IndexError:
389                     pass
390
391             # once we're done we remove empty cells
392             grid[i] = [x for x in grid[i] if x is not None]
393
394         # 2nd thing is to merge cells vertically, this is emissions that last
395         # for more than 30 minutes
396         for i in range(nb_lines):
397             grid[i] = [x for x in grid[i] if x is not None]
398             for j, cell in enumerate(grid[i]):
399                 if grid[i][j] is None:
400                     continue
401                 t = 1
402                 try:
403                     while True:
404                         # we look if the next time cell has the same emissions
405                         same_cell_below = [(bj, x) for bj, x in enumerate(grid[i+cell.h])
406                                            if x == cell and x.y == cell.y and x.w == cell.w]
407                         if same_cell_below:
408                             # if the cell was identical, we remove it and
409                             # increase current cell height
410                             bj, same_cell_below = same_cell_below[0]
411                             del grid[i+cell.h][bj]
412                             cell.h += 1
413                         else:
414                             # if the cell is different, we have a closer look
415                             # to it, so we can remove emissions that will
416                             # already be mentioned in the current cell.
417                             #
418                             # For example:
419                             #  - 7am30, seuls contre tout, 1h30
420                             #  - 8am, du pied gauche & la voix de la rue, 1h
421                             # should produce: (this is case A)
422                             #  |      7:30-9:00      |
423                             #  |  seuls contre tout  |
424                             #  |---------------------|
425                             #  |      8:00-9:00      |
426                             #  |   du pied gauche    |
427                             #  |  la voix de la rue  |
428                             #
429                             # On the other hand, if all three emissions started
430                             # at 7am30, we want: (this is case B)
431                             #  |      7:30-9:00      |
432                             #  |  seuls contre tout  |
433                             #  |   du pied gauche    |
434                             #  |  la voix de la rue  |
435                             # that is we merge all of them, ignoring the fact
436                             # that the other emissions will stop at 8am30
437                             current_cell_schedules = set(grid[i][j].schedules)
438                             current_cell_emissions = set([x.emission for x in current_cell_schedules])
439                             cursor = 1
440                             while True and current_cell_schedules:
441                                 same_cell_below = [x for x in grid[i+cursor] if x.y == grid[i][j].y]
442                                 if not same_cell_below:
443                                     cursor += 1
444                                     continue
445                                 same_cell_below = same_cell_below[0]
446                                 same_cell_below_emissions = set([x.emission for x in same_cell_below.schedules])
447
448                                 if current_cell_emissions.issubset(same_cell_below_emissions):
449                                     # this handles case A (see comment above)
450                                     for schedule in current_cell_schedules:
451                                         if schedule in same_cell_below.schedules:
452                                             same_cell_below.schedules.remove(schedule)
453                                 elif same_cell_below_emissions and \
454                                         current_cell_emissions.issuperset(same_cell_below_emissions):
455                                     # this handles case B (see comment above)
456                                     # we set the cell time label to the longest
457                                     # period
458                                     grid[i][j].time_label = same_cell_below.time_label
459                                     # then we sort emissions so the longest are
460                                     # put first
461                                     grid[i][j].schedules.sort(key=lambda x: -x.get_duration())
462                                     # then we add individual time labels to the
463                                     # other schedules
464                                     for schedule in current_cell_schedules:
465                                         if schedule not in same_cell_below.schedules:
466                                             end_time = schedule.datetime + timedelta(
467                                                     minutes=schedule.get_duration())
468                                             schedule.time_label = '%02d:%02d-%02d:%02d' % (
469                                                     schedule.datetime.hour,
470                                                     schedule.datetime.minute,
471                                                     end_time.hour,
472                                                     end_time.minute)
473                                     grid[i][j].h += 1
474                                     grid[i+cursor].remove(same_cell_below)
475                                 elif same_cell_below_emissions and \
476                                         current_cell_emissions.intersection(same_cell_below_emissions):
477                                     same_cell_below.schedules = [x for x in
478                                             same_cell_below.schedules if
479                                             x.emission not in
480                                             current_cell_emissions or
481                                             x.get_duration() < 30]
482                                 cursor += 1
483                             break
484                 except IndexError:
485                     pass
486
487         # cut night at 3am
488         grid = grid[:42]
489         times = times[:42]
490
491         context['grid'] = grid
492         context['times'] = times
493         context['categories'] = Category.objects.all()
494         context['weekdays'] = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi',
495                 'Vendredi', 'Samedi', 'Dimanche']
496
497         return context
498
499 grid = Grid.as_view()
500
501
502 class Home(TemplateView):
503     template_name = 'home.html'
504
505     def get_context_data(self, **kwargs):
506         context = super(Home, self).get_context_data(**kwargs)
507         context['emissions'] = Emission.objects.filter(archived=False).order_by('-creation_timestamp')[:3]
508         context['newsitems'] = NewsItem.objects.exclude(date__gt=date.today()).order_by('-date')[:3]
509
510         context['soundfiles'] = SoundFile.objects.prefetch_related('episode__emission__categories').filter(
511                 podcastable=True, fragment=False) \
512                 .select_related().extra(select={
513                     'first_diffusion': 'emissions_diffusion.datetime', },
514                     select_params=(False, True),
515                     where=['''datetime = (SELECT MIN(datetime)
516                                             FROM emissions_diffusion
517                                         WHERE episode_id = emissions_episode.id)'''],
518                     tables=['emissions_diffusion'],).order_by('-creation_timestamp').distinct() [:3]
519
520         context['newsletter_form'] = SubscribeForm()
521
522         return context
523
524 home = Home.as_view()
525
526 class NewsItemView(DetailView):
527     model = NewsItem
528     def get_context_data(self, **kwargs):
529         context = super(NewsItemView, self).get_context_data(**kwargs)
530         context['categories'] = NewsCategory.objects.all()
531         context['news'] = NewsItem.objects.all().order_by('-date')
532         context['topiks'] = [x.topik for x in ItemTopik.objects.filter(newsitem=self.object)]
533         return context
534 newsitemview = NewsItemView.as_view()
535
536 class News(TemplateView):
537     template_name = 'news.html'
538     def get_context_data(self, **kwargs):
539         context = super(News, self).get_context_data(**kwargs)
540         context['focus'] = NewsItem.objects.exclude(date__gt=date.today()  # publication date
541                 ).exclude(expiration_date__lt=date.today()  # expiration date
542                 ).filter(got_focus__isnull=False
543                 ).select_related('category').order_by('-date')[:10]
544         context['news'] = NewsItem.objects.exclude(date__gt=date.today()).order_by('-date')
545         return context
546
547 news = News.as_view()
548
549
550 class Agenda(TemplateView):
551     template_name = 'agenda.html'
552     def get_context_data(self, **kwargs):
553         context = super(Agenda, self).get_context_data(**kwargs)
554         context['agenda'] = NewsItem.objects.exclude(date__gt=date.today()).filter(
555                 event_date__gte=date.today()).order_by('date')[:20]
556         context['news'] = NewsItem.objects.exclude(date__gt=date.today()).order_by('-date')
557         context['previous_month'] = datetime.today().replace(day=1) - timedelta(days=2)
558         return context
559
560 agenda = Agenda.as_view()
561
562
563 class AgendaByMonth(MonthArchiveView):
564     template_name = 'agenda.html'
565     queryset = NewsItem.objects.filter(event_date__isnull=False)
566     allow_future = True
567     date_field = 'event_date'
568     month_format = '%m'
569
570     def get_context_data(self, **kwargs):
571         context = super(AgendaByMonth, self).get_context_data(**kwargs)
572         context['agenda'] = context['object_list']
573         context['news'] = NewsItem.objects.all().order_by('-date')
574         return context
575
576 agenda_by_month = AgendaByMonth.as_view()
577
578
579 class Emissions(TemplateView):
580     template_name = 'emissions.html'
581
582     def get_context_data(self, **kwargs):
583         context = super(Emissions, self).get_context_data(**kwargs)
584         context['emissions'] = Emission.objects.prefetch_related('categories').filter(archived=False).order_by('title')
585         context['categories'] = Category.objects.all()
586         return context
587
588 emissions = Emissions.as_view()
589
590 class EmissionsArchives(TemplateView):
591     template_name = 'emissions/archives.html'
592
593     def get_context_data(self, **kwargs):
594         context = super(EmissionsArchives, self).get_context_data(**kwargs)
595         context['emissions'] = Emission.objects.prefetch_related('categories').filter(archived=True).order_by('title')
596         context['categories'] = Category.objects.all()
597         return context
598 emissionsArchives = EmissionsArchives.as_view()
599
600
601 class Listen(TemplateView):
602     template_name = 'listen.html'
603
604     def get_context_data(self, **kwargs):
605         context = super(Listen, self).get_context_data(**kwargs)
606         context['focus'] = SoundFile.objects.prefetch_related('episode__emission__categories').filter(
607                 podcastable=True, got_focus__isnull=False) \
608                 .select_related().extra(select={
609                     'first_diffusion': 'emissions_diffusion.datetime', },
610                     select_params=(False, True),
611                     where=['''datetime = (SELECT MIN(datetime)
612                                             FROM emissions_diffusion
613                                         WHERE episode_id = emissions_episode.id)'''],
614                     tables=['emissions_diffusion'],).order_by('-first_diffusion').distinct() [:10]
615         context['soundfiles'] = SoundFile.objects.prefetch_related('episode__emission__categories').filter(
616                 podcastable=True) \
617                 .select_related().extra(select={
618                     'first_diffusion': 'emissions_diffusion.datetime', },
619                     select_params=(False, True),
620                     where=['''datetime = (SELECT MIN(datetime)
621                                             FROM emissions_diffusion
622                                         WHERE episode_id = emissions_episode.id)'''],
623                     tables=['emissions_diffusion'],).order_by('-creation_timestamp').distinct()
624
625
626         return context
627
628 listen = Listen.as_view()
629
630 @cache_control(max_age=15)
631 @csrf_exempt
632 def onair(request):
633     d = whatsonair()
634     if d.get('episode'):
635         d['episode'] = {
636             'title': d['episode'].title,
637             'url': d['episode'].get_absolute_url()
638         }
639     if d.get('emission'):
640         chat_url = None
641         if d['emission'].chat_open:
642             chat_url = reverse('emission-chat', kwargs={'slug': d['emission'].slug})
643         d['emission'] = {
644             'title': d['emission'].title,
645             'url': d['emission'].get_absolute_url(),
646             'chat': chat_url,
647         }
648     if d.get('nonstop'):
649         redirect_path = d['nonstop'].redirect_path
650         d['nonstop'] = {
651             'title': d['nonstop'].title,
652         }
653         if redirect_path:
654             d['nonstop']['url'] = redirect_path
655         d.update(get_current_nonstop_track())
656     if d.get('current_slot'):
657         del d['current_slot']
658     return JsonResponse({'data': d})
659
660
661 class NewsItemDetailView(DetailView):
662     model = NewsItem
663
664 newsitem = NewsItemDetailView.as_view()
665
666 class RssCustomPodcastsFeed(Rss201rev2Feed):
667     def add_root_elements(self, handler):
668         super(RssCustomPodcastsFeed, self).add_root_elements(handler)
669         emission = self.feed.get('emission')
670         if emission and emission.image and emission.image.url:
671             image_url = emission.image.url
672         else:
673             image_url = '/static/img/logo-panik-500.png'
674         image_url = urlparse.urljoin(self.feed['link'], image_url)
675         handler.startElement('image', {})
676         if emission:
677             handler.addQuickElement('title', emission.title)
678         else:
679             handler.addQuickElement('title', settings.RADIO_NAME)
680         handler.addQuickElement('url', image_url)
681         handler.endElement('image')
682         handler.addQuickElement('itunes:explicit', 'no')  # invidividual items will get their own value
683         handler.addQuickElement('itunes:image', None, {'href': image_url})
684         if emission:
685             if emission.subtitle:
686                 handler.addQuickElement('itunes:subtitle', emission.subtitle)
687             for category in emission.categories.all():
688                 if category.itunes_category:
689                     handler.addQuickElement('itunes:category', None, {'text': category.itunes_category})
690
691             handler.addQuickElement('itunes:author', emission.title)
692             handler.startElement('itunes:owner', {})
693             if emission.email:
694                 handler.addQuickElement('itunes:email', emission.email)
695             handler.addQuickElement('itunes:name', emission.title)
696             handler.endElement('itunes:owner')
697         else:
698             handler.addQuickElement('itunes:author', settings.RADIO_NAME)
699             handler.startElement('itunes:owner', {})
700             handler.addQuickElement('itunes:email', 'info@radiopanik.org')
701             handler.addQuickElement('itunes:name', settings.RADIO_NAME)
702             handler.endElement('itunes:owner')
703
704     def root_attributes(self):
705         attrs = super(RssCustomPodcastsFeed, self).root_attributes()
706         attrs['xmlns:dc'] = 'http://purl.org/dc/elements/1.1/'
707         attrs['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
708         return attrs
709
710     def add_item_elements(self, handler, item):
711         super(RssCustomPodcastsFeed, self).add_item_elements(handler, item)
712         explicit = 'no'
713         for tag in item.get('tags') or []:
714             handler.addQuickElement('dc:subject', tag)
715             if tag == 'explicit':
716                 explicit = 'yes'
717         if item.get('tags'):
718             handler.addQuickElement('itunes:keywords', ','.join(item.get('tags')))
719         handler.addQuickElement('itunes:explicit', explicit)
720         episode = item.get('episode')
721         if episode and episode.image and episode.image.url:
722             image_url = urlparse.urljoin(self.feed['link'], episode.image.url)
723             handler.addQuickElement('itunes:image', None, {'href': image_url})
724         soundfile = item.get('soundfile')
725         if soundfile.duration:
726             handler.addQuickElement('itunes:duration', '%02d:%02d:%02d' % (
727                 soundfile.duration / 3600,
728                 soundfile.duration % 3600 / 60,
729                 soundfile.duration % 60))
730
731
732 class PodcastsFeed(Feed):
733     title = '%s - Podcasts' % settings.RADIO_NAME
734     link = '/'
735     description_template = 'feed/soundfile.html'
736     feed_type = RssCustomPodcastsFeed
737
738     def items(self):
739         return SoundFile.objects.select_related().filter(
740                 podcastable=True).order_by('-creation_timestamp')[:20]
741
742     def item_title(self, item):
743         if item.fragment:
744             return '[%s] %s - %s' % (item.episode.emission.title, item.title, item.episode.title)
745         return '[%s] %s' % (item.episode.emission.title, item.episode.title)
746
747     def item_link(self, item):
748         return item.episode.get_absolute_url()
749
750     def item_enclosure_url(self, item):
751         current_site = Site.objects.get(id=settings.SITE_ID)
752         return add_domain(current_site.domain, item.get_format_url('mp3'))
753
754     def item_enclosure_length(self, item):
755         sound_path = item.get_format_path('mp3')
756         try:
757             return os.stat(sound_path)[stat.ST_SIZE]
758         except OSError:
759             return 0
760
761     def item_enclosure_mime_type(self, item):
762         return 'audio/mpeg'
763
764     def item_pubdate(self, item):
765         return item.creation_timestamp
766
767     def item_extra_kwargs(self, item):
768         return {'tags': [x.name for x in item.episode.tags.all()],
769                 'soundfile': item,
770                 'episode': item.episode}
771
772 podcasts_feed = PodcastsFeed()
773
774
775 class RssNewsFeed(Feed):
776     title = settings.RADIO_NAME
777     link = '/news/'
778     description_template = 'feed/newsitem.html'
779
780     def items(self):
781         return NewsItem.objects.order_by('-date')[:20]
782
783 rss_news_feed = RssNewsFeed()
784
785 class Atom1FeedWithBaseXml(Atom1Feed):
786     def root_attributes(self):
787         root_attributes = super(Atom1FeedWithBaseXml, self).root_attributes()
788         scheme, netloc, path, params, query, fragment  = urlparse.urlparse(self.feed['feed_url'])
789         root_attributes['xml:base'] = urlparse.urlunparse((scheme, netloc, '/', params, query, fragment))
790         return root_attributes
791
792 class AtomNewsFeed(RssNewsFeed):
793     feed_type = Atom1FeedWithBaseXml
794
795 atom_news_feed = AtomNewsFeed()
796
797
798 class EmissionPodcastsFeed(PodcastsFeed):
799     description_template = 'feed/soundfile.html'
800     feed_type = RssCustomPodcastsFeed
801
802     def __call__(self, request, *args, **kwargs):
803         self.emission = Emission.objects.get(slug=kwargs.get('slug'))
804         return super(EmissionPodcastsFeed, self).__call__(request, *args, **kwargs)
805
806     def item_title(self, item):
807         if item.fragment:
808             return '%s - %s' % (item.title, item.episode.title)
809         return item.episode.title
810
811     @property
812     def title(self):
813         return self.emission.title
814
815     @property
816     def description(self):
817         return self.emission.subtitle
818
819     @property
820     def link(self):
821         return reverse('emission-view', kwargs={'slug': self.emission.slug})
822
823     def feed_extra_kwargs(self, obj):
824         return {'emission': self.emission}
825
826     def items(self):
827         return SoundFile.objects.select_related().filter(
828                 podcastable=True,
829                 episode__emission__slug=self.emission.slug).order_by('-creation_timestamp')[:20]
830
831 emission_podcasts_feed = EmissionPodcastsFeed()
832
833
834 class Party(TemplateView):
835     template_name = 'party.html'
836
837     def get_context_data(self, **kwargs):
838         context = super(Party, self).get_context_data(**kwargs)
839         t = random.choice(['newsitem']*2 + ['emission']*3 + ['soundfile']*1 + ['episode']*2)
840         focus = Focus()
841         if t == 'newsitem':
842             focus.newsitem = NewsItem.objects.exclude(
843                     image__isnull=True).exclude(image__exact='').order_by('?')[0]
844         elif t == 'emission':
845             focus.emission = Emission.objects.exclude(
846                     image__isnull=True).exclude(image__exact='').order_by('?')[0]
847         elif t == 'episode':
848             focus.episode = Episode.objects.exclude(
849                     image__isnull=True).exclude(image__exact='').order_by('?')[0]
850         elif t == 'soundfile':
851             focus.soundfile = SoundFile.objects.exclude(
852                     episode__image__isnull=True).exclude(episode__image__exact='').order_by('?')[0]
853
854         context['focus'] = focus
855
856         return context
857
858 party = Party.as_view()
859
860
861
862 class Chat(DetailView, EmissionMixin):
863     model = Emission
864     template_name = 'chat.html'
865
866 chat = cache_control(max_age=15)(Chat.as_view())