1 from datetime import datetime, timedelta, date
8 from django.urls 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
21 from django.core.paginator import Paginator
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
27 from haystack.query import SearchQuerySet
29 from emissions.models import (
41 from emissions.views import EmissionEpisodeMixin
42 from emissions.utils import whatsonair, period_program
44 from newsletter.forms import SubscribeForm
45 from nonstop.utils import get_current_nonstop_track
46 from nonstop.models import SomaLogLine
48 from panikombo.models import ItemTopik
54 def get_emission_context(self, emission, episode_ids=None):
57 # get all episodes, with an additional attribute to get the date of
58 # their first diffusion
59 episodes_queryset = Episode.objects.select_related()
60 if episode_ids is not None:
61 episodes_queryset = episodes_queryset.filter(id__in=episode_ids)
63 episodes_queryset = episodes_queryset.filter(emission=emission)
65 context['episodes'] = (
66 episodes_queryset.extra(
68 'first_diffusion': 'emissions_diffusion.datetime',
70 select_params=(False, True),
72 '''datetime = (SELECT MIN(datetime)
73 FROM emissions_diffusion
74 WHERE episode_id = emissions_episode.id
75 AND datetime <= CURRENT_TIMESTAMP)'''
77 tables=['emissions_diffusion'],
79 .order_by('-first_diffusion')
83 context['all_episodes'] = (
84 episodes_queryset.extra(
86 'first_diffusion': 'emissions_diffusion.datetime',
88 select_params=(False, True),
90 '''datetime = (SELECT MIN(datetime)
91 FROM emissions_diffusion
92 WHERE episode_id = emissions_episode.id)'''
94 tables=['emissions_diffusion'],
96 .order_by('-first_diffusion')
100 context['futurEpisodes'] = (
101 episodes_queryset.extra(
103 'first_diffusion': 'emissions_diffusion.datetime',
105 select_params=(False, True),
107 '''datetime = (SELECT MIN(datetime)
108 FROM emissions_diffusion
109 WHERE episode_id = emissions_episode.id
110 AND datetime > CURRENT_TIMESTAMP)'''
112 tables=['emissions_diffusion'],
114 .order_by('first_diffusion')
118 # get all related soundfiles in a single query
120 if episode_ids is not None:
121 for episode_id in episode_ids:
122 soundfiles[episode_id] = None
124 for episode in Episode.objects.filter(emission=emission):
125 soundfiles[episode.id] = None
127 for soundfile in SoundFile.objects.select_related().filter(
128 podcastable=True, fragment=False, episode__emission=emission
130 soundfiles[soundfile.episode_id] = soundfile
132 Episode.set_prefetched_soundfiles(soundfiles)
134 # context['futurEpisodes'] = context['episodes'].filter(first_diffusion='2013')[0:3]
139 class EmissionDetailView(DetailView, EmissionMixin):
142 def get_context_data(self, **kwargs):
143 context = super(EmissionDetailView, self).get_context_data(**kwargs)
144 context['schedules'] = (
145 Schedule.objects.select_related().filter(emission=self.object).order_by('rerun', 'datetime')
148 NewsItem.objects.all()
149 .filter(emission=self.object.id)
150 .exclude(expiration_date__lt=date.today()) # expiration date
151 .exclude(date__lt=date.today() - timedelta(days=60))
152 .order_by('-date')[:3]
155 nonstop_object = Nonstop.objects.get(slug=self.object.slug)
156 except Nonstop.DoesNotExist:
160 dates = [today - timedelta(days=x) for x in range(7)]
161 if datetime.now().time() < nonstop_object.start:
163 context['nonstop'] = nonstop_object
164 context['nonstop_dates'] = dates
165 context.update(self.get_emission_context(self.object))
169 emission = EmissionDetailView.as_view()
172 class EpisodeDetailView(EmissionEpisodeMixin, DetailView, EmissionMixin):
175 def get_context_data(self, **kwargs):
176 context = super(EpisodeDetailView, self).get_context_data(**kwargs)
177 context['diffusions'] = (
178 Diffusion.objects.select_related().filter(episode=self.object.id).order_by('datetime')
181 context['emission'] = context['episode'].emission
182 except Emission.DoesNotExist:
184 if self.kwargs.get('emission_slug') != context['emission'].slug:
186 context.update(self.get_emission_context(context['emission']))
187 context['topik_pages'] = [x.page for x in ItemTopik.objects.filter(episode=self.object)]
191 episode = EpisodeDetailView.as_view()
194 class NonstopPlaylistView(TemplateView):
195 template_name = 'nonstop_playlist.html'
197 def get_context_data(self, **kwargs):
198 context = super(NonstopPlaylistView, self).get_context_data(**kwargs)
200 context['emission'] = Emission.objects.get(slug=kwargs.get('slug'))
201 except Emission.DoesNotExist:
203 context['date'] = date(int(kwargs.get('year')), int(kwargs.get('month')), int(kwargs.get('day')))
204 context['future'] = context['date'] >= date.today()
206 nonstop_object = Nonstop.objects.get(slug=kwargs.get('slug'))
208 int(kwargs.get('year')),
209 int(kwargs.get('month')),
210 int(kwargs.get('day')),
211 nonstop_object.start.hour,
212 nonstop_object.start.minute,
215 int(kwargs.get('year')),
216 int(kwargs.get('month')),
217 int(kwargs.get('day')),
218 nonstop_object.end.hour,
219 nonstop_object.end.minute,
222 end = end + timedelta(days=1)
223 context['tracks'] = SomaLogLine.objects.filter(
224 play_timestamp__gte=start, play_timestamp__lte=end, on_air=True
229 nonstop_playlist = NonstopPlaylistView.as_view()
232 class EmissionEpisodesDetailView(DetailView, EmissionMixin):
234 template_name = 'emissions/episodes.html'
236 def get_context_data(self, **kwargs):
237 context = super(EmissionEpisodesDetailView, self).get_context_data(**kwargs)
238 context['schedules'] = (
239 Schedule.objects.select_related().filter(emission=self.object).order_by('rerun', 'datetime')
242 context['search_query'] = self.request.GET.get('q')
243 if context['search_query']:
248 .filter(emission_slug_exact=self.object.slug, text=context['search_query'])
250 episode_ids = [x.pk for x in sqs]
254 context.update(self.get_emission_context(self.object, episode_ids=episode_ids))
258 emissionEpisodes = EmissionEpisodesDetailView.as_view()
261 class SoundFileEmbedView(DetailView):
263 template_name = 'soundfiles/embed.html'
265 def get_context_data(self, **kwargs):
266 context = super(SoundFileEmbedView, self).get_context_data(**kwargs)
267 if self.kwargs.get('episode_slug') != self.object.episode.slug:
269 if self.kwargs.get('emission_slug') != self.object.episode.emission.slug:
271 context['episode'] = self.object.episode
275 soundfile_embed = SoundFileEmbedView.as_view()
278 class SoundFileDialogEmbedView(DetailView):
280 template_name = 'soundfiles/dialog-embed.html'
282 def get_context_data(self, **kwargs):
283 context = super(SoundFileDialogEmbedView, self).get_context_data(**kwargs)
284 if self.kwargs.get('episode_slug') != self.object.episode.slug:
286 if self.kwargs.get('emission_slug') != self.object.episode.emission.slug:
288 context['episode'] = self.object.episode
292 soundfile_dlg_embed = SoundFileDialogEmbedView.as_view()
295 class ProgramView(TemplateView):
296 template_name = 'program.html'
298 def get_context_data(self, year=None, week=None, **kwargs):
299 context = super(ProgramView, self).get_context_data(**kwargs)
301 context['weekday'] = datetime.today().weekday()
303 context['week'] = week = int(week) if week is not None else datetime.today().isocalendar()[1]
304 context['year'] = year = int(year) if year is not None else datetime.today().isocalendar()[0]
305 if context['week'] > 53:
307 context['week_first_day'] = utils.tofirstdayinisoweek(year, week)
308 context['week_last_day'] = context['week_first_day'] + timedelta(days=6)
313 program = ProgramView.as_view()
316 @python_2_unicode_compatible
323 def __init__(self, i, j):
328 def add_schedule(self, schedule):
329 end_time = schedule.datetime + timedelta(minutes=schedule.get_duration())
330 self.time_label = '%02d:%02d-%02d:%02d' % (
331 schedule.datetime.hour,
332 schedule.datetime.minute,
336 self.schedules.append(schedule)
338 def sorted_schedules(self):
339 return sorted(self.schedules, key=lambda x: x.week_sort_key())
343 return ', '.join([x.emission.title for x in self.schedules])
347 def __eq__(self, other):
348 return force_text(self) == force_text(other) and self.time_label == other.time_label
351 class Grid(TemplateView):
352 template_name = 'grid.html'
354 def get_context_data(self, **kwargs):
355 context = super(Grid, self).get_context_data(**kwargs)
357 nb_lines = 2 * 24 # the cells are half hours
360 times = ['%02d:%02d' % (x / 2, x % 2 * 30) for x in range(nb_lines)]
361 # start grid after the night programs
363 times[2 * Schedule.DAY_HOUR_START + (1 if Schedule.DAY_MINUTE_START else 0) :]
364 + times[: 2 * Schedule.DAY_HOUR_START + (1 if Schedule.DAY_MINUTE_START else 0)]
368 for nonstop in Nonstop.objects.all():
369 if nonstop.start == nonstop.end:
371 if nonstop.start < nonstop.end:
374 nonstop.start.hour + nonstop.start.minute / 60.0,
375 nonstop.end.hour + nonstop.end.minute / 60.0,
376 nonstop.get_public_label(),
385 nonstop.start.hour + nonstop.start.minute / 60.0,
387 nonstop.get_public_label(),
395 nonstop.end.hour + nonstop.end.minute / 60.0,
396 nonstop.get_public_label(),
403 for i in range(nb_lines):
406 grid[-1].append(TimeCell(i, j))
408 nonstop = [x for x in nonstops if i >= x[0] * 2 and i < x[1] * 2][0]
409 for time_cell in grid[-1]:
410 time_cell.nonstop = nonstop[2]
411 time_cell.nonstop_slug = nonstop[3]
412 time_cell.redirect_path = nonstop[4].redirect_path
413 if nonstop[1] == Schedule.DAY_HOUR_START + Schedule.DAY_MINUTE_START / 60:
414 # the one ending at 4:30am will be cut down, so we inscribe
415 # its duration manually
416 time_cell.time_label = '%02d:00-%02d:%02d' % (
419 Schedule.DAY_MINUTE_START,
423 Schedule.objects.prefetch_related('emission__categories').select_related().order_by('datetime')
425 row_start = schedule.datetime.hour * 2 + int(math.ceil(schedule.datetime.minute / 30))
426 if schedule.get_duration() < 30:
427 # special case for an emission during 12:45-13:00
428 row_start = schedule.datetime.hour * 2 + int(math.floor(schedule.datetime.minute / 30))
429 day_no = schedule.get_weekday()
431 for step in range(int(math.ceil(schedule.get_duration() / 30.0))):
432 if grid[(row_start + step) % nb_lines][day_no] is None:
433 grid[(row_start + step) % nb_lines][day_no] = TimeCell()
434 grid[(row_start + step) % nb_lines][day_no].add_schedule(schedule)
436 # start grid after the night programs
438 grid[2 * Schedule.DAY_HOUR_START + (1 if Schedule.DAY_MINUTE_START else 0) :]
439 + grid[: 2 * Schedule.DAY_HOUR_START + (1 if Schedule.DAY_MINUTE_START else 0)]
442 # look for the case where the same emission has different schedules for
443 # the same time cell, for example if it lasts one hour the first week,
444 # and two hours the third week.
445 for i in range(nb_lines):
446 grid[i] = [x for x in grid[i] if x is not None]
447 for j, cell in enumerate(grid[i]):
448 if grid[i][j] is None:
450 if len(grid[i][j].schedules) > 1:
451 time_cell_emissions = {}
452 for schedule in grid[i][j].schedules:
453 if not schedule.emission.id in time_cell_emissions:
454 time_cell_emissions[schedule.emission.id] = []
455 time_cell_emissions[schedule.emission.id].append(schedule)
456 for schedule_list in time_cell_emissions.values():
457 if len(schedule_list) == 1:
459 # here it is, same cell, same emission, several
461 schedule_list.sort(key=lambda x: x.get_duration())
463 schedule = schedule_list[0]
464 end_time = schedule.datetime + timedelta(minutes=schedule.get_duration())
465 grid[i][j].time_label = '%02d:%02d-%02d:%02d' % (
466 schedule.datetime.hour,
467 schedule.datetime.minute,
472 schedule_list.sort(key=lambda x: x.weeks)
473 for schedule in schedule_list[1:]:
474 grid[i][j].schedules.remove(schedule)
475 end_time = schedule.datetime + timedelta(minutes=schedule.get_duration())
476 if schedule_list[0].get_duration() == schedule.get_duration():
477 # same duration, append week info
478 schedule_list[0].time_label_extra = ', %s' % (schedule.weeks_string,)
480 # different durations, also append other
482 schedule_list[0].time_label_extra = ', -%02d:%02d %s' % (
485 schedule.weeks_string,
491 # 1st thing is to merge cells on the same line, this will mostly catch
492 # consecutive nonstop cells
493 for i in range(nb_lines):
494 for j, cell in enumerate(grid[i]):
495 if grid[i][j] is None:
499 # if the cells are identical, they are removed from the
500 # grid, and current cell width is increased
501 while grid[i][j + t] == cell:
503 grid[i][j + t] = None
508 # once we're done we remove empty cells
509 grid[i] = [x for x in grid[i] if x is not None]
511 # 2nd thing is to merge cells vertically, this is emissions that last
512 # for more than 30 minutes
513 for i in range(nb_lines):
514 grid[i] = [x for x in grid[i] if x is not None]
515 for j, cell in enumerate(grid[i]):
516 if grid[i][j] is None:
521 # we look if the next time cell has the same emissions
524 for bj, x in enumerate(grid[i + cell.h])
525 if x == cell and x.y == cell.y and x.w == cell.w
528 # if the cell was identical, we remove it and
529 # increase current cell height
530 bj, same_cell_below = same_cell_below[0]
531 del grid[i + cell.h][bj]
534 # if the cell is different, we have a closer look
535 # to it, so we can remove emissions that will
536 # already be mentioned in the current cell.
539 # - 7am30, seuls contre tout, 1h30
540 # - 8am, du pied gauche & la voix de la rue, 1h
541 # should produce: (this is case A)
543 # | seuls contre tout |
544 # |---------------------|
547 # | la voix de la rue |
549 # On the other hand, if all three emissions started
550 # at 7am30, we want: (this is case B)
552 # | seuls contre tout |
554 # | la voix de la rue |
555 # that is we merge all of them, ignoring the fact
556 # that the other emissions will stop at 8am30
557 current_cell_schedules = set(grid[i][j].schedules)
558 current_cell_emissions = set([x.emission for x in current_cell_schedules])
560 while True and current_cell_schedules:
561 same_cell_below = [x for x in grid[i + cursor] if x.y == grid[i][j].y]
562 if not same_cell_below:
565 same_cell_below = same_cell_below[0]
566 same_cell_below_emissions = set(
567 [x.emission for x in same_cell_below.schedules]
570 if current_cell_emissions.issubset(same_cell_below_emissions):
571 # this handles case A (see comment above)
572 for schedule in current_cell_schedules:
573 if schedule in same_cell_below.schedules:
574 same_cell_below.schedules.remove(schedule)
575 elif same_cell_below_emissions and current_cell_emissions.issuperset(
576 same_cell_below_emissions
578 # this handles case B (see comment above)
579 # we set the cell time label to the longest
581 grid[i][j].time_label = same_cell_below.time_label
582 # then we sort emissions so the longest are
584 grid[i][j].schedules.sort(key=lambda x: -x.get_duration())
585 # then we add individual time labels to the
587 for schedule in current_cell_schedules:
588 if schedule not in same_cell_below.schedules:
589 end_time = schedule.datetime + timedelta(
590 minutes=schedule.get_duration()
592 schedule.time_label = '%02d:%02d-%02d:%02d' % (
593 schedule.datetime.hour,
594 schedule.datetime.minute,
599 grid[i + cursor].remove(same_cell_below)
600 elif same_cell_below_emissions and current_cell_emissions.intersection(
601 same_cell_below_emissions
603 same_cell_below.schedules = [
605 for x in same_cell_below.schedules
606 if x.emission not in current_cell_emissions or x.get_duration() < 30
613 # cut late night hours
617 context['grid'] = grid
618 context['times'] = times
619 context['categories'] = Category.objects.all()
620 context['weekdays'] = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
625 grid = Grid.as_view()
628 class Home(TemplateView):
629 template_name = 'home.html'
631 def get_context_data(self, **kwargs):
632 context = super(Home, self).get_context_data(**kwargs)
633 context['emissions'] = Emission.objects.filter(archived=False).order_by('-creation_timestamp')[
634 : settings.HOME_EMISSIONS_COUNT
636 context['newsitems'] = NewsItem.objects.exclude(date__gt=date.today()).order_by('-date')[
637 : settings.HOME_NEWSITEMS_COUNT
640 context['soundfiles'] = (
641 SoundFile.objects.prefetch_related('episode__emission__categories')
642 .filter(podcastable=True, fragment=False)
646 'first_diffusion': 'emissions_diffusion.datetime',
648 select_params=(False, True),
650 '''datetime = (SELECT MIN(datetime)
651 FROM emissions_diffusion
652 WHERE episode_id = emissions_episode.id)'''
654 tables=['emissions_diffusion'],
656 .order_by('-creation_timestamp')
657 .distinct()[: settings.HOME_PODCASTS_COUNT]
660 context['newsletter_form'] = SubscribeForm()
665 home = Home.as_view()
668 class NewsItemView(DetailView):
671 def get_context_data(self, **kwargs):
672 context = super(NewsItemView, self).get_context_data(**kwargs)
673 context['categories'] = NewsCategory.objects.all()
674 context['news'] = NewsItem.objects.all().order_by('-date')
675 context['topik_pages'] = [x.page for x in ItemTopik.objects.filter(newsitem=self.object)]
679 newsitemview = NewsItemView.as_view()
682 class News(TemplateView):
683 template_name = 'news.html'
685 def get_context_data(self, **kwargs):
686 context = super(News, self).get_context_data(**kwargs)
688 NewsItem.objects.exclude(date__gt=date.today()) # publication date
689 .exclude(expiration_date__lt=date.today()) # expiration date
690 .filter(got_focus__isnull=False)
691 .select_related('category')
692 .order_by('-date')[:10]
694 context['news'] = NewsItem.objects.exclude(date__gt=date.today()).order_by('-date')
698 news = News.as_view()
701 class Agenda(TemplateView):
702 template_name = 'agenda.html'
704 def get_context_data(self, **kwargs):
705 context = super(Agenda, self).get_context_data(**kwargs)
706 context['agenda'] = (
707 NewsItem.objects.exclude(date__gt=date.today())
708 .filter(event_date__gte=date.today())
709 .order_by('event_date')[:20]
711 context['news'] = NewsItem.objects.exclude(date__gt=date.today()).order_by('-date')
712 context['previous_month'] = datetime.today().replace(day=1) - timedelta(days=2)
716 agenda = Agenda.as_view()
719 class AgendaByMonth(MonthArchiveView):
720 template_name = 'agenda.html'
721 queryset = NewsItem.objects.filter(event_date__isnull=False)
723 date_field = 'event_date'
726 def get_context_data(self, **kwargs):
727 context = super(AgendaByMonth, self).get_context_data(**kwargs)
728 context['agenda'] = context['object_list']
729 context['news'] = NewsItem.objects.all().order_by('-date')
733 agenda_by_month = AgendaByMonth.as_view()
736 class Emissions(TemplateView):
737 template_name = 'emissions.html'
739 def get_queryset(self):
740 return Emission.objects.prefetch_related('categories').filter(archived=False).order_by('title')
742 def get_context_data(self, **kwargs):
743 context = super(Emissions, self).get_context_data(**kwargs)
744 context['emissions'] = self.get_queryset()
745 context['categories'] = Category.objects.all()
749 emissions = Emissions.as_view()
752 class EmissionsArchives(TemplateView):
753 template_name = 'emissions/archives.html'
755 def get_context_data(self, **kwargs):
756 context = super(EmissionsArchives, self).get_context_data(**kwargs)
757 context['emissions'] = (
758 Emission.objects.prefetch_related('categories').filter(archived=True).order_by('title')
760 context['categories'] = Category.objects.all()
764 emissionsArchives = EmissionsArchives.as_view()
767 class Listen(TemplateView):
768 template_name = 'listen.html'
770 def get_context_data(self, **kwargs):
771 context = super(Listen, self).get_context_data(**kwargs)
773 SoundFile.objects.prefetch_related('episode__emission__categories')
774 .filter(podcastable=True, got_focus__isnull=False)
778 'first_diffusion': 'emissions_diffusion.datetime',
780 select_params=(False, True),
782 '''datetime = (SELECT MIN(datetime)
783 FROM emissions_diffusion
784 WHERE episode_id = emissions_episode.id)'''
786 tables=['emissions_diffusion'],
788 .order_by('-first_diffusion')
791 context['soundfiles'] = (
792 SoundFile.objects.prefetch_related('episode__emission__categories')
793 .filter(podcastable=True)
797 'first_diffusion': 'emissions_diffusion.datetime',
799 select_params=(False, True),
801 '''datetime = (SELECT MIN(datetime)
802 FROM emissions_diffusion
803 WHERE episode_id = emissions_episode.id)'''
805 tables=['emissions_diffusion'],
807 .order_by('-creation_timestamp')
814 listen = Listen.as_view()
817 @cache_control(max_age=15)
822 d['episode'] = {'title': d['episode'].title, 'url': d['episode'].get_absolute_url()}
823 if d.get('emission'):
825 if d['emission'].chat_open:
826 chat_url = reverse('emission-chat', kwargs={'slug': d['emission'].slug})
828 'title': d['emission'].title,
829 'url': d['emission'].get_absolute_url(),
833 redirect_path = d['nonstop'].redirect_path
835 'title': d['nonstop'].get_public_label(),
838 d['nonstop']['url'] = redirect_path
839 today = datetime.today()
840 d['nonstop']['playlist_url'] = reverse(
844 'month': today.month,
846 'slug': d['current_slot'].slug,
849 d.update(get_current_nonstop_track())
850 if d.get('current_slot'):
851 del d['current_slot']
852 return JsonResponse({'data': d})
855 class NewsItemDetailView(DetailView):
859 newsitem = NewsItemDetailView.as_view()
862 class RssCustomPodcastsFeed(Rss201rev2Feed):
863 def add_root_elements(self, handler):
864 super(RssCustomPodcastsFeed, self).add_root_elements(handler)
865 emission = self.feed.get('emission')
866 if emission and emission.image and emission.image.url:
867 image_url = emission.image.url
869 image_url = settings.PODCASTS_DEFAULT_IMAGE_PATH
870 image_url = urlparse.urljoin(self.feed['link'], image_url)
871 handler.startElement('image', {})
873 handler.addQuickElement('title', emission.title)
875 handler.addQuickElement('title', settings.RADIO_NAME)
876 handler.addQuickElement('url', image_url)
877 handler.endElement('image')
878 handler.addQuickElement('itunes:explicit', 'no') # invidividual items will get their own value
879 handler.addQuickElement('itunes:image', None, {'href': image_url})
881 if emission.subtitle:
882 handler.addQuickElement('itunes:subtitle', emission.subtitle)
883 for category in emission.categories.all():
884 if category.itunes_category:
885 handler.addQuickElement('itunes:category', None, {'text': category.itunes_category})
887 handler.addQuickElement('itunes:author', settings.RADIO_NAME)
888 handler.startElement('itunes:owner', {})
889 if emission and emission.email:
890 handler.addQuickElement('itunes:email', emission.email)
892 handler.addQuickElement('itunes:email', settings.DEFAULT_FROM_EMAIL)
893 handler.addQuickElement('itunes:name', settings.RADIO_NAME)
894 handler.endElement('itunes:owner')
896 def root_attributes(self):
897 attrs = super(RssCustomPodcastsFeed, self).root_attributes()
898 attrs['xmlns:dc'] = 'http://purl.org/dc/elements/1.1/'
899 attrs['xmlns:itunes'] = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
902 def add_item_elements(self, handler, item):
903 super(RssCustomPodcastsFeed, self).add_item_elements(handler, item)
905 for tag in item.get('tags') or []:
906 handler.addQuickElement('dc:subject', tag)
907 if tag == 'explicit':
910 handler.addQuickElement('itunes:keywords', ','.join(item.get('tags')))
911 handler.addQuickElement('itunes:explicit', explicit)
912 episode = item.get('episode')
913 if episode and episode.image and episode.image.url:
914 image_url = urlparse.urljoin(self.feed['link'], episode.image.url)
915 handler.addQuickElement('itunes:image', None, {'href': image_url})
916 soundfile = item.get('soundfile')
917 if soundfile.duration:
918 handler.addQuickElement(
921 % (soundfile.duration / 3600, soundfile.duration % 3600 / 60, soundfile.duration % 60),
925 class PodcastsFeed(Feed):
926 title = '%s - Podcasts' % settings.RADIO_NAME
928 description_template = 'feed/soundfile.html'
929 feed_type = RssCustomPodcastsFeed
931 def get_feed(self, obj, request):
932 self.request = request
933 return super().get_feed(obj, request)
937 SoundFile.objects.select_related().filter(podcastable=True).order_by('-creation_timestamp')[:50]
940 def item_title(self, item):
942 return '[%s] %s - %s' % (item.episode.emission.title, item.title, item.episode.title)
943 return '[%s] %s' % (item.episode.emission.title, item.episode.title)
945 def item_link(self, item):
947 return item.episode.get_absolute_url() + '#%s' % item.id
948 return item.episode.get_absolute_url()
950 def item_enclosure_url(self, item):
951 current_site = Site.objects.get(id=settings.SITE_ID)
952 return add_domain(current_site.domain, item.get_format_url('mp3'), self.request.is_secure())
954 def item_enclosure_length(self, item):
955 sound_path = item.get_format_path('mp3')
957 return os.stat(sound_path)[stat.ST_SIZE]
961 def item_enclosure_mime_type(self, item):
964 def item_pubdate(self, item):
965 return item.creation_timestamp
967 def item_extra_kwargs(self, item):
968 return {'tags': [x.name for x in item.episode.tags.all()], 'soundfile': item, 'episode': item.episode}
971 podcasts_feed = PodcastsFeed()
974 class RssNewsFeed(Feed):
975 title = settings.RADIO_NAME
977 description_template = 'feed/newsitem.html'
980 return NewsItem.objects.order_by('-date')[:20]
983 rss_news_feed = RssNewsFeed()
986 class Atom1FeedWithBaseXml(Atom1Feed):
987 def root_attributes(self):
988 root_attributes = super(Atom1FeedWithBaseXml, self).root_attributes()
989 scheme, netloc, path, params, query, fragment = urlparse.urlparse(self.feed['feed_url'])
990 root_attributes['xml:base'] = urlparse.urlunparse((scheme, netloc, '/', params, query, fragment))
991 return root_attributes
994 class AtomNewsFeed(RssNewsFeed):
995 feed_type = Atom1FeedWithBaseXml
998 atom_news_feed = AtomNewsFeed()
1001 class EmissionPodcastsFeed(PodcastsFeed):
1002 description_template = 'feed/soundfile.html'
1003 feed_type = RssCustomPodcastsFeed
1005 def __call__(self, request, *args, **kwargs):
1006 self.emission = Emission.objects.get(slug=kwargs.get('slug'))
1007 return super(EmissionPodcastsFeed, self).__call__(request, *args, **kwargs)
1009 def item_title(self, item):
1011 return '%s - %s' % (item.title, item.episode.title)
1012 return item.episode.title
1016 return self.emission.title
1019 def description(self):
1020 return self.emission.subtitle
1024 return reverse('emission-view', kwargs={'slug': self.emission.slug})
1026 def feed_extra_kwargs(self, obj):
1027 return {'emission': self.emission}
1031 SoundFile.objects.select_related()
1032 .filter(podcastable=True, episode__emission__slug=self.emission.slug)
1033 .order_by('-creation_timestamp')[:50]
1037 emission_podcasts_feed = EmissionPodcastsFeed()
1040 class Party(TemplateView):
1041 template_name = 'party.html'
1043 def get_context_data(self, **kwargs):
1044 context = super(Party, self).get_context_data(**kwargs)
1045 t = random.choice(['newsitem'] * 2 + ['emission'] * 3 + ['soundfile'] * 1 + ['episode'] * 2)
1049 NewsItem.objects.exclude(image__isnull=True).exclude(image__exact='').order_by('?')[0]
1051 elif t == 'emission':
1053 Emission.objects.exclude(image__isnull=True).exclude(image__exact='').order_by('?')[0]
1055 elif t == 'episode':
1057 Episode.objects.exclude(image__isnull=True).exclude(image__exact='').order_by('?')[0]
1059 elif t == 'soundfile':
1061 SoundFile.objects.exclude(episode__image__isnull=True)
1062 .exclude(episode__image__exact='')
1066 context['focus'] = focus
1071 party = Party.as_view()
1074 class Chat(DetailView, EmissionMixin):
1076 template_name = 'chat.html'
1079 chat = cache_control(max_age=15)(Chat.as_view())