from datetime import datetime, timedelta import time import math 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 jsonresponse import to_json from emissions.models import Category, Emission, Episode, Diffusion, SoundFile, \ Schedule, Nonstop, NewsItem, NewsCategory from emissions.utils import whatsonair, period_program class EmissionMixin: def get_emission_context(self, emission): context = {} # get all episodes, with an additional attribute to get the date of # their first diffusion context['episodes'] = \ Episode.objects.select_related().filter(emission=emission ).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') # get all related soundfiles in a single query soundfiles = {} for soundfile in SoundFile.objects.select_related().filter(podcastable=True, fragment=False, episode__emission=emission): soundfiles[soundfile.episode_id] = soundfile # replace dynamic property by a static attribute, to avoid database # lookups for episode in context['episodes']: episode.main_sound = soundfiles.get(episode.id) 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('datetime') 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 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" schedules = Schedule.objects.select_related().order_by('datetime') days = [] for day in range(7): days.append({'schedules': [x for x in schedules if x.is_on_weekday(day+1)], 'datetime': datetime(2007, 1, day+1)}) context['days'] = days context['weekday'] = datetime.today().weekday() context['week'] = week = int(week) if week is not None else datetime.today().isocalendar()[1]-1 context['year'] = year = int(year) if year is not None else datetime.today().year context['week_first_day'] = datetime.strptime(str(year)+' '+str(week)+' 1', '%Y %U %w') context['week_last_day'] = context['week_first_day'] + timedelta(days=+6) context['week_previous'] = context['week_first_day'] + timedelta(days=-7) context['week_next'] = context['week_last_day'] 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]) else: # crossing midnight nonstops.append([nonstop.start.hour + nonstop.start.minute/60., 24, nonstop.title]) nonstops.append([0, nonstop.end.hour + nonstop.end.minute/60., nonstop.title]) 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())) 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) 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] if current_cell_schedules.issubset(same_cell_below.schedules): # 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.schedules and \ current_cell_schedules.issuperset(same_cell_below.schedules): # 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) 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['news'] = list(NewsItem.objects.all().exclude(image__isnull=True).exclude(image__exact='').order_by('-datetime')[:6]) context['emissions'] = list(Emission.objects.filter(archived=False).order_by('title')) schedules = Schedule.objects.select_related().order_by('datetime') days = [] for day in range(7): days.append({'schedules': [x for x in schedules if x.is_on_weekday(day+1)], 'datetime': datetime(2007, 1, day+1)}) context['days'] = days return context home = Home.as_view() class NewsItemDetailView(DetailView): model = NewsItem def get_context_data(self, **kwargs): context['sectionName'] = "News" context = super(NewsItemDetailView, self).get_context_data(**kwargs) context['news'] = list(NewsItem.objects.all().order_by('-datetime')[:60]) context['categories'] = list(NewsCategory.objects.all()) return context newsitem = NewsItemDetailView.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['newsImaged'] = list(NewsItem.objects.all().exclude(image__isnull=True).exclude(image__exact='').order_by('-datetime')[:3]) context['news'] = list(NewsItem.objects.all().order_by('-datetime')[:60]) context['categories'] = list(NewsCategory.objects.all()) return context news = News.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 Archives(TemplateView): template_name = 'archives.html' def get_context_data(self, **kwargs): context = super(Archives, self).get_context_data(**kwargs) context['sectionName'] = "Emissions" context['emissions'] = Emission.objects.all().filter(archived=True).order_by('title') return context archives = Archives.as_view() class Get(TemplateView): template_name = 'get.html' def get_context_data(self, **kwargs): context = super(Get, self).get_context_data(**kwargs) context['emissions'] = Emission.objects.all().order_by('title') return context get = Get.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['episodes'] = Episode.objects.filter( soundfile__podcastable=True, soundfile__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') [:60] # get all related soundfiles in a single query soundfiles = {} for soundfile in SoundFile.objects.select_related().filter(podcastable=True, fragment=False, episode__in=[x.id for x in context['episodes']]): soundfiles[soundfile.episode_id] = soundfile # replace dynamic property by a static attribute, to avoid database # lookups for episode in context['episodes']: episode.main_sound = soundfiles.get(episode.id) 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()