from datetime import datetime, timedelta import math import os import stat import time from django.conf import settings 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.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 import fiber.views from haystack.query import SearchQuerySet from jsonresponse import to_json from emissions.models import Category, Emission, Episode, Diffusion, SoundFile, \ Schedule, Nonstop, NewsItem, NewsCategory from emissions.utils import whatsonair, period_program from newsletter.forms import SubscribeForm 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)'''], 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['sectionName'] = "Emissions" 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] 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['sectionName'] = "Emissions" context['diffusions'] = Diffusion.objects.select_related().filter(episode=self.object.id) context['soundfiles'] = SoundFile.objects.select_related().filter(episode=self.object.id) context['emission'] = Emission.objects.get(slug=self.kwargs.get('emission_slug')) context.update(self.get_emission_context(context['emission'])) return context episode = EpisodeDetailView.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['sectionName'] = "Emissions" 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 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['sectionName'] = "Emissions" 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) context['sectionName'] = "Emissions" 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['sectionName'] = "Home" context['focus'] = NewsItem.objects.select_related('category').filter( focus=True).exclude(image__isnull=True).exclude(image__exact='').order_by('?')[:12] context['emissions'] = Emission.objects.filter(archived=False, creation_timestamp__gte=datetime(2013, 9, 13)).order_by('title') 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('-first_diffusion').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['sectionName'] = "News" context['focus'] = list(NewsItem.objects.select_related('category').filter(focus=True).exclude(image__isnull=True).exclude(image__exact='').order_by('-date')[0:9]) context['categories'] = NewsCategory.objects.all() context['news'] = NewsItem.objects.all().exclude(image__isnull=True).exclude(image__exact='').order_by('-date')[:10] 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['sectionName'] = "News" context['focus'] = list(NewsItem.objects.select_related('category').filter(focus=True).exclude(image__isnull=True).exclude(image__exact='').order_by('-date')[0:9]) context['news'] = NewsItem.objects.all().exclude(image__isnull=True).exclude(image__exact='').order_by('-date')[:30] return context news = News.as_view() class NewsArchives(TemplateView): template_name = 'news/archives.html' def get_context_data(self, **kwargs): context = super(NewsArchives, self).get_context_data(**kwargs) context['sectionName'] = "News" context['search_query'] = self.request.GET.get('q') sqs = SearchQuerySet().models(NewsItem) if context['search_query']: # query string sqs = sqs.filter(text=context['search_query']) else: sqs = sqs.order_by('-date') sqs = sqs.load_all() context['results'] = sqs return context newsArchives = NewsArchives.as_view() class Emissions(TemplateView): template_name = 'emissions.html' def get_context_data(self, **kwargs): context = super(Emissions, self).get_context_data(**kwargs) context['sectionName'] = "Emissions" 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['sectionName'] = "Emissions" 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['sectionName'] = "Listen" 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('-first_diffusion').distinct() [:10] context['categories'] = Category.objects.all() return context listen = Listen.as_view() @cache_control(max_age=25) @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'): d['emission'] = { 'title': d['emission'].title, 'url': d['emission'].get_absolute_url() } if d.get('nonstop'): d['nonstop'] = { 'title': d['nonstop'].title, } 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 FiberPageView(fiber.views.FiberTemplateView): def get_context_data(self, **kwargs): context = super(FiberPageView, self).get_context_data(**kwargs) context['sectionName'] = 'About' return context fiber_page = FiberPageView.as_view() 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()