from datetime import datetime, timedelta, date import math import random import os import stat import time from django.core.urlresolvers import reverse from django.conf import settings from django.http import Http404 from django.views.decorators.cache import cache_control from django.views.generic.base import TemplateView from django.views.generic.detail import DetailView from django.views.decorators.csrf import csrf_exempt from django.views.generic.dates import _date_from_string from django.views.generic.dates import MonthArchiveView from django.core.paginator import Paginator from django.contrib.sites.models import Site from django.contrib.syndication.views import Feed, add_domain from django.utils.feedgenerator import Atom1Feed from haystack.query import SearchQuerySet from jsonresponse import to_json from emissions.models import Category, Emission, Episode, Diffusion, SoundFile, \ Schedule, Nonstop, NewsItem, NewsCategory, Focus from emissions.utils import whatsonair, period_program from newsletter.forms import SubscribeForm from nonstop.utils import get_current_nonstop_track from nonstop.models import SomaLogLine from panikombo.models import ItemTopik from . import utils class EmissionMixin: def get_emission_context(self, emission, episode_ids=None): context = {} # get all episodes, with an additional attribute to get the date of # their first diffusion episodes_queryset = Episode.objects.select_related() if episode_ids is not None: episodes_queryset = episodes_queryset.filter(id__in=episode_ids) else: episodes_queryset = episodes_queryset.filter(emission=emission) context['episodes'] = \ episodes_queryset.extra(select={ 'first_diffusion': 'emissions_diffusion.datetime', }, select_params=(False, True), where=['''datetime = (SELECT MIN(datetime) FROM emissions_diffusion WHERE episode_id = emissions_episode.id AND datetime <= CURRENT_TIMESTAMP)'''], tables=['emissions_diffusion'], ).order_by('-first_diffusion').distinct() context['futurEpisodes'] = \ episodes_queryset.extra(select={ 'first_diffusion': 'emissions_diffusion.datetime', }, select_params=(False, True), where=['''datetime = (SELECT MIN(datetime) FROM emissions_diffusion WHERE episode_id = emissions_episode.id AND datetime > CURRENT_TIMESTAMP)'''], tables=['emissions_diffusion'], ).order_by('first_diffusion').distinct() # get all related soundfiles in a single query soundfiles = {} if episode_ids is not None: for episode_id in episode_ids: soundfiles[episode_id] = None else: for episode in Episode.objects.filter(emission=emission): soundfiles[episode.id] = None for soundfile in SoundFile.objects.select_related().filter(podcastable=True, fragment=False, episode__emission=emission): soundfiles[soundfile.episode_id] = soundfile Episode.set_prefetched_soundfiles(soundfiles) #context['futurEpisodes'] = context['episodes'].filter(first_diffusion='2013')[0:3] return context class EmissionDetailView(DetailView, EmissionMixin): model = Emission def get_context_data(self, **kwargs): context = super(EmissionDetailView, self).get_context_data(**kwargs) context['schedules'] = Schedule.objects.select_related().filter( emission=self.object).order_by('rerun', 'datetime') context['news'] = NewsItem.objects.all().filter(emission=self.object.id).order_by('-date')[:3] try: nonstop_object = Nonstop.objects.get(slug=self.object.slug) except Nonstop.DoesNotExist: pass else: today = date.today() dates = [today - timedelta(days=x) for x in range(7)] if datetime.now().time() < nonstop_object.start: dates = dates[1:] context['nonstop'] = nonstop_object context['nonstop_dates'] = dates context.update(self.get_emission_context(self.object)) return context emission = EmissionDetailView.as_view() class EpisodeDetailView(DetailView, EmissionMixin): model = Episode def get_context_data(self, **kwargs): context = super(EpisodeDetailView, self).get_context_data(**kwargs) context['diffusions'] = Diffusion.objects.select_related().filter( episode=self.object.id).order_by('datetime') try: context['emission'] = context['episode'].emission except Emission.DoesNotExist: raise Http404() if self.kwargs.get('emission_slug') != context['emission'].slug: raise Http404() context.update(self.get_emission_context(context['emission'])) context['topiks'] = [x.topik for x in ItemTopik.objects.filter(episode=self.object)] return context episode = EpisodeDetailView.as_view() class NonstopPlaylistView(TemplateView): template_name = 'nonstop_playlist.html' def get_context_data(self, **kwargs): context = super(NonstopPlaylistView, self).get_context_data(**kwargs) context['emission'] = Emission.objects.get(slug=kwargs.get('slug')) context['date'] = date(int(kwargs.get('year')), int(kwargs.get('month')), int(kwargs.get('day'))) nonstop_object = Nonstop.objects.get(slug=kwargs.get('slug')) start = datetime( int(kwargs.get('year')), int(kwargs.get('month')), int(kwargs.get('day')), nonstop_object.start.hour, nonstop_object.start.minute) end = datetime( int(kwargs.get('year')), int(kwargs.get('month')), int(kwargs.get('day')), nonstop_object.end.hour, nonstop_object.end.minute) if end < start: end = end + timedelta(days=1) context['tracks'] = SomaLogLine.objects.filter( play_timestamp__gte=start, play_timestamp__lte=end, on_air=True).select_related() return context nonstop_playlist = NonstopPlaylistView.as_view() class EmissionEpisodesDetailView(DetailView, EmissionMixin): model = Emission template_name = 'emissions/episodes.html' def get_context_data(self, **kwargs): context = super(EmissionEpisodesDetailView, self).get_context_data(**kwargs) context['schedules'] = Schedule.objects.select_related().filter( emission=self.object).order_by('rerun', 'datetime') context['search_query'] = self.request.GET.get('q') if context['search_query']: # query string sqs = SearchQuerySet().models(Episode).filter( emission_slug_exact=self.object.slug, text=context['search_query']) episode_ids = [x.pk for x in sqs] else: episode_ids = None context.update(self.get_emission_context(self.object, episode_ids=episode_ids)) return context emissionEpisodes = EmissionEpisodesDetailView.as_view() class SoundFileEmbedView(DetailView): model = SoundFile template_name = 'soundfiles/embed.html' def get_context_data(self, **kwargs): context = super(SoundFileEmbedView, self).get_context_data(**kwargs) if self.kwargs.get('episode_slug') != self.object.episode.slug: raise Http404() if self.kwargs.get('emission_slug') != self.object.episode.emission.slug: raise Http404() context['episode'] = self.object.episode return context soundfile_embed = SoundFileEmbedView.as_view() class SoundFileDialogEmbedView(DetailView): model = SoundFile template_name = 'soundfiles/dialog-embed.html' def get_context_data(self, **kwargs): context = super(SoundFileDialogEmbedView, self).get_context_data(**kwargs) if self.kwargs.get('episode_slug') != self.object.episode.slug: raise Http404() if self.kwargs.get('emission_slug') != self.object.episode.emission.slug: raise Http404() context['episode'] = self.object.episode context['site_url'] = self.request.build_absolute_uri('/').strip('/') return context soundfile_dlg_embed = SoundFileDialogEmbedView.as_view() class ProgramView(TemplateView): template_name = 'program.html' def get_context_data(self, year=None, week=None, **kwargs): context = super(ProgramView, self).get_context_data(**kwargs) context['weekday'] = datetime.today().weekday() context['week'] = week = int(week) if week is not None else datetime.today().isocalendar()[1] context['year'] = year = int(year) if year is not None else datetime.today().isocalendar()[0] context['week_first_day'] = utils.tofirstdayinisoweek(year, week) context['week_last_day'] = context['week_first_day'] + timedelta(days=6) return context program = ProgramView.as_view() class TimeCell: nonstop = None w = 1 h = 1 time_label = None def __init__(self, i, j): self.x = i self.y = j self.schedules = [] def add_schedule(self, schedule): end_time = schedule.datetime + timedelta( minutes=schedule.get_duration()) self.time_label = '%02d:%02d-%02d:%02d' % ( schedule.datetime.hour, schedule.datetime.minute, end_time.hour, end_time.minute) self.schedules.append(schedule) def __unicode__(self): if self.schedules: return ', '.join([x.emission.title for x in self.schedules]) else: return self.nonstop def __eq__(self, other): return (unicode(self) == unicode(other) and self.time_label == other.time_label) class Grid(TemplateView): template_name = 'grid.html' def get_context_data(self, **kwargs): context = super(Grid, self).get_context_data(**kwargs) nb_lines = 2 * 24 # the cells are half hours grid = [] times = ['%02d:%02d' % (x/2, x%2*30) for x in range(nb_lines)] # start grid after the night programs times = times[2*Schedule.DAY_HOUR_START:] + times[:2*Schedule.DAY_HOUR_START] nonstops = [] for nonstop in Nonstop.objects.all(): if nonstop.start < nonstop.end: nonstops.append([nonstop.start.hour + nonstop.start.minute/60., nonstop.end.hour + nonstop.end.minute/60., nonstop.title, nonstop.slug]) else: # crossing midnight nonstops.append([nonstop.start.hour + nonstop.start.minute/60., 24, nonstop.title, nonstop.slug]) nonstops.append([0, nonstop.end.hour + nonstop.end.minute/60., nonstop.title, nonstop.slug]) nonstops.sort() for i in range(nb_lines): grid.append([]) for j in range(7): grid[-1].append(TimeCell(i, j)) nonstop = [x for x in nonstops if i>=x[0]*2 and i 1: time_cell_emissions = {} for schedule in grid[i][j].schedules: if not schedule.emission.id in time_cell_emissions: time_cell_emissions[schedule.emission.id] = [] time_cell_emissions[schedule.emission.id].append(schedule) for schedule_list in time_cell_emissions.values(): if len(schedule_list) == 1: continue # here it is, same cell, same emission, several # schedules schedule_list.sort(lambda x,y: cmp(x.get_duration(), y.get_duration())) schedule = schedule_list[0] end_time = schedule.datetime + timedelta( minutes=schedule.get_duration()) grid[i][j].time_label = '%02d:%02d-%02d:%02d' % ( schedule.datetime.hour, schedule.datetime.minute, end_time.hour, end_time.minute) for schedule in schedule_list[1:]: grid[i][j].schedules.remove(schedule) end_time = schedule.datetime + timedelta(minutes=schedule.get_duration()) schedule_list[0].time_label_extra = ', -%02d:%02d %s' % ( end_time.hour, end_time.minute, schedule.weeks_string) # merge adjacent # 1st thing is to merge cells on the same line, this will mostly catch # consecutive nonstop cells for i in range(nb_lines): for j, cell in enumerate(grid[i]): if grid[i][j] is None: continue t = 1 try: # if the cells are identical, they are removed from the # grid, and current cell width is increased while grid[i][j+t] == cell: cell.w += 1 grid[i][j+t] = None t += 1 except IndexError: pass # once we're done we remove empty cells grid[i] = [x for x in grid[i] if x is not None] # 2nd thing is to merge cells vertically, this is emissions that last # for more than 30 minutes for i in range(nb_lines): grid[i] = [x for x in grid[i] if x is not None] for j, cell in enumerate(grid[i]): if grid[i][j] is None: continue t = 1 try: while True: # we look if the next time cell has the same emissions same_cell_below = [(bj, x) for bj, x in enumerate(grid[i+cell.h]) if x == cell and x.y == cell.y and x.w == cell.w] if same_cell_below: # if the cell was identical, we remove it and # increase current cell height bj, same_cell_below = same_cell_below[0] del grid[i+cell.h][bj] cell.h += 1 else: # if the cell is different, we have a closer look # to it, so we can remove emissions that will # already be mentioned in the current cell. # # For example: # - 7am30, seuls contre tout, 1h30 # - 8am, du pied gauche & la voix de la rue, 1h # should produce: (this is case A) # | 7:30-9:00 | # | seuls contre tout | # |---------------------| # | 8:00-9:00 | # | du pied gauche | # | la voix de la rue | # # On the other hand, if all three emissions started # at 7am30, we want: (this is case B) # | 7:30-9:00 | # | seuls contre tout | # | du pied gauche | # | la voix de la rue | # that is we merge all of them, ignoring the fact # that the other emissions will stop at 8am30 current_cell_schedules = set(grid[i][j].schedules) current_cell_emissions = set([x.emission for x in current_cell_schedules]) cursor = 1 while True and current_cell_schedules: same_cell_below = [x for x in grid[i+cursor] if x.y == grid[i][j].y] if not same_cell_below: cursor += 1 continue same_cell_below = same_cell_below[0] same_cell_below_emissions = set([x.emission for x in same_cell_below.schedules]) if current_cell_emissions.issubset(same_cell_below_emissions): # this handles case A (see comment above) for schedule in current_cell_schedules: if schedule in same_cell_below.schedules: same_cell_below.schedules.remove(schedule) elif same_cell_below_emissions and \ current_cell_emissions.issuperset(same_cell_below_emissions): # this handles case B (see comment above) # we set the cell time label to the longest # period grid[i][j].time_label = same_cell_below.time_label # then we sort emissions so the longest are # put first grid[i][j].schedules.sort( lambda x, y: -cmp(x.get_duration(), y.get_duration())) # then we add individual time labels to the # other schedules for schedule in current_cell_schedules: if schedule not in same_cell_below.schedules: end_time = schedule.datetime + timedelta( minutes=schedule.get_duration()) schedule.time_label = '%02d:%02d-%02d:%02d' % ( schedule.datetime.hour, schedule.datetime.minute, end_time.hour, end_time.minute) grid[i][j].h += 1 grid[i+cursor].remove(same_cell_below) elif same_cell_below_emissions and \ current_cell_emissions.intersection(same_cell_below_emissions): same_cell_below.schedules = [x for x in same_cell_below.schedules if x.emission not in current_cell_emissions or x.get_duration() < 30] cursor += 1 break except IndexError: pass # cut night at 3am grid = grid[:42] times = times[:42] context['grid'] = grid context['times'] = times context['categories'] = Category.objects.all() context['weekdays'] = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'] return context grid = Grid.as_view() class Home(TemplateView): template_name = 'home.html' def get_context_data(self, **kwargs): context = super(Home, self).get_context_data(**kwargs) context['emissions'] = Emission.objects.filter(archived=False).order_by('-creation_timestamp')[:3] context['newsitems'] = NewsItem.objects.order_by('-date')[:3] context['soundfiles'] = SoundFile.objects.prefetch_related('episode__emission__categories').filter( podcastable=True, fragment=False) \ .select_related().extra(select={ 'first_diffusion': 'emissions_diffusion.datetime', }, select_params=(False, True), where=['''datetime = (SELECT MIN(datetime) FROM emissions_diffusion WHERE episode_id = emissions_episode.id)'''], tables=['emissions_diffusion'],).order_by('-creation_timestamp').distinct() [:3] context['newsletter_form'] = SubscribeForm() return context home = Home.as_view() class NewsItemView(DetailView): model = NewsItem def get_context_data(self, **kwargs): context = super(NewsItemView, self).get_context_data(**kwargs) context['categories'] = NewsCategory.objects.all() context['news'] = NewsItem.objects.all().order_by('-date') context['topiks'] = [x.topik for x in ItemTopik.objects.filter(newsitem=self.object)] return context newsitemview = NewsItemView.as_view() class News(TemplateView): template_name = 'news.html' def get_context_data(self, **kwargs): context = super(News, self).get_context_data(**kwargs) context['focus'] = NewsItem.objects.filter(got_focus__isnull=False).select_related('category').order_by('-date')[:10] context['news'] = NewsItem.objects.all().order_by('-date') return context news = News.as_view() class Agenda(TemplateView): template_name = 'agenda.html' def get_context_data(self, **kwargs): context = super(Agenda, self).get_context_data(**kwargs) context['agenda'] = NewsItem.objects.filter( event_date__gte=date.today()).order_by('date')[:20] context['news'] = NewsItem.objects.all().order_by('-date') context['previous_month'] = datetime.today().replace(day=1) - timedelta(days=2) return context agenda = Agenda.as_view() class AgendaByMonth(MonthArchiveView): template_name = 'agenda.html' queryset = NewsItem.objects.filter(event_date__isnull=False) allow_future = True date_field = 'event_date' month_format = '%m' def get_context_data(self, **kwargs): context = super(AgendaByMonth, self).get_context_data(**kwargs) context['agenda'] = context['object_list'] context['news'] = NewsItem.objects.all().order_by('-date') return context agenda_by_month = AgendaByMonth.as_view() class Emissions(TemplateView): template_name = 'emissions.html' def get_context_data(self, **kwargs): context = super(Emissions, self).get_context_data(**kwargs) context['emissions'] = Emission.objects.prefetch_related('categories').filter(archived=False).order_by('title') context['categories'] = Category.objects.all() return context emissions = Emissions.as_view() class EmissionsArchives(TemplateView): template_name = 'emissions/archives.html' def get_context_data(self, **kwargs): context = super(EmissionsArchives, self).get_context_data(**kwargs) context['emissions'] = Emission.objects.prefetch_related('categories').filter(archived=True).order_by('title') context['categories'] = Category.objects.all() return context emissionsArchives = EmissionsArchives.as_view() class Listen(TemplateView): template_name = 'listen.html' def get_context_data(self, **kwargs): context = super(Listen, self).get_context_data(**kwargs) context['focus'] = SoundFile.objects.prefetch_related('episode__emission__categories').filter( podcastable=True, got_focus__isnull=False) \ .select_related().extra(select={ 'first_diffusion': 'emissions_diffusion.datetime', }, select_params=(False, True), where=['''datetime = (SELECT MIN(datetime) FROM emissions_diffusion WHERE episode_id = emissions_episode.id)'''], tables=['emissions_diffusion'],).order_by('-first_diffusion').distinct() [:10] context['soundfiles'] = SoundFile.objects.prefetch_related('episode__emission__categories').filter( podcastable=True) \ .select_related().extra(select={ 'first_diffusion': 'emissions_diffusion.datetime', }, select_params=(False, True), where=['''datetime = (SELECT MIN(datetime) FROM emissions_diffusion WHERE episode_id = emissions_episode.id)'''], tables=['emissions_diffusion'],).order_by('-creation_timestamp').distinct() [:10] return context listen = Listen.as_view() @cache_control(max_age=15) @csrf_exempt @to_json('api') def onair(request): d = whatsonair() if d.get('episode'): d['episode'] = { 'title': d['episode'].title, 'url': d['episode'].get_absolute_url() } if d.get('emission'): chat_url = None if d['emission'].chat_open: chat_url = reverse('emission-chat', kwargs={'slug': d['emission'].slug}) d['emission'] = { 'title': d['emission'].title, 'url': d['emission'].get_absolute_url(), 'chat': chat_url, } if d.get('nonstop'): d['nonstop'] = { 'title': d['nonstop'].title, } d.update(get_current_nonstop_track()) if d.get('current_slot'): del d['current_slot'] return d class NewsItemDetailView(DetailView): model = NewsItem newsitem = NewsItemDetailView.as_view() class PodcastsFeed(Feed): title = 'Radio Panik - Podcasts' link = '/' description_template = 'feed/soundfile.html' def items(self): return SoundFile.objects.select_related().filter( podcastable=True).order_by('-creation_timestamp')[:5] def item_title(self, item): if item.fragment: return '%s - %s' % (item.title, item.episode.title) return item.episode.title def item_link(self, item): return item.episode.get_absolute_url() def item_enclosure_url(self, item): current_site = Site.objects.get(id=settings.SITE_ID) return add_domain(current_site.domain, item.get_format_url('mp3')) def item_enclosure_length(self, item): sound_path = item.get_format_path('mp3') try: return os.stat(sound_path)[stat.ST_SIZE] except OSError: return 0 def item_enclosure_mime_type(self, item): return 'audio/mpeg' def item_pubdate(self, item): return item.creation_timestamp podcasts_feed = PodcastsFeed() class RssNewsFeed(Feed): title = 'Radio Panik' link = '/news/' description_template = 'feed/newsitem.html' def items(self): return NewsItem.objects.order_by('-date')[:10] rss_news_feed = RssNewsFeed() class AtomNewsFeed(RssNewsFeed): feed_type = Atom1Feed atom_news_feed = AtomNewsFeed() class Party(TemplateView): template_name = 'party.html' def get_context_data(self, **kwargs): context = super(Party, self).get_context_data(**kwargs) t = random.choice(['newsitem']*2 + ['emission']*3 + ['soundfile']*1 + ['episode']*2) focus = Focus() if t == 'newsitem': focus.newsitem = NewsItem.objects.exclude( image__isnull=True).exclude(image__exact='').order_by('?')[0] elif t == 'emission': focus.emission = Emission.objects.exclude( image__isnull=True).exclude(image__exact='').order_by('?')[0] elif t == 'episode': focus.episode = Episode.objects.exclude( image__isnull=True).exclude(image__exact='').order_by('?')[0] elif t == 'soundfile': focus.soundfile = SoundFile.objects.exclude( episode__image__isnull=True).exclude(episode__image__exact='').order_by('?')[0] context['focus'] = focus return context party = Party.as_view() class Chat(DetailView, EmissionMixin): model = Emission template_name = 'chat.html' chat = Chat.as_view()