9 from django.core.files.storage import default_storage
10 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
11 from django.core.urlresolvers import reverse
12 from django.contrib import messages
13 from django.db.models import Q
14 from django.http import HttpResponse, HttpResponseRedirect, FileResponse
15 from django.utils.six import StringIO
16 from django.utils.translation import ugettext_lazy as _
17 from django.views.generic.base import RedirectView, TemplateView
18 from django.views.generic.dates import DayArchiveView
19 from django.views.generic.detail import DetailView
20 from django.views.generic.edit import CreateView, FormView
21 from django.views.generic.list import ListView
23 from .forms import UploadTracksForm, TrackMetaForm, TrackSearchForm, CleanupForm, AddDiffusionForm
24 from .models import SomaLogLine, Track, Artist, NonstopFile, StreamedDiffusion, Jingle, LOCAL_BASE_PATH
25 from emissions.models import Nonstop, Diffusion
30 class SomaDayArchiveView(DayArchiveView):
31 queryset = SomaLogLine.objects.all()
32 date_field = "play_timestamp"
33 make_object_list = True
38 class SomaDayArchiveCsvView(SomaDayArchiveView):
39 def render_to_response(self, context, **response_kwargs):
41 writer = csv.writer(out)
42 for line in context['object_list']:
43 if line.filepath.track:
44 writer.writerow([line.play_timestamp.strftime('%Y-%m-%d %H:%M'),
45 line.filepath.short.encode('utf-8', 'replace'),
46 line.filepath.track.title.encode('utf-8', 'replace'),
47 line.filepath.track.artist.name.encode('utf-8', 'replace'),
48 line.filepath.track.language,
49 line.filepath.track.instru and 'instru' or '',
50 line.filepath.track.cfwb and 'cfwb' or '',
51 line.filepath.track.added_to_nonstop_timestamp.strftime('%Y-%m-%d %H:%M') if line.filepath.track.added_to_nonstop_timestamp else '',
54 writer.writerow([line.play_timestamp.strftime('%Y-%m-%d %H:%M'),
55 line.filepath.short.encode('utf-8', 'replace')])
56 return HttpResponse(out.getvalue(), content_type='text/csv; charset=utf-8')
59 class RedirectTodayView(RedirectView):
60 def get_redirect_url(self, *args, **kwargs):
61 today = datetime.datetime.today()
62 return reverse('archive_day', kwargs={
68 class TrackDetailView(DetailView):
71 def get_context_data(self, **kwargs):
72 ctx = super(TrackDetailView, self).get_context_data(**kwargs)
73 ctx['metadata_form'] = TrackMetaForm(instance=self.object)
76 def post(self, request, *args, **kwargs):
77 assert self.request.user.has_perm('nonstop.add_track')
78 instance = self.get_object()
79 old_nonstop_zones = copy.copy(instance.nonstop_zones.all())
80 form = TrackMetaForm(request.POST, instance=instance)
82 new_nonstop_zones = self.get_object().nonstop_zones.all()
83 if set(old_nonstop_zones) != set(new_nonstop_zones):
84 instance.sync_nonstop_zones()
85 return HttpResponseRedirect('.')
88 class ArtistDetailView(DetailView):
92 class ArtistListView(ListView):
96 class ZoneStats(object):
97 def __init__(self, zone, from_date=None, until_date=None, **kwargs):
99 self.qs = Track.objects.filter(nonstop_zones=self.zone, **kwargs)
100 self.from_date = from_date
102 self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__gte=from_date)
104 self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__lte=until_date)
105 self.qs = self.qs.distinct()
107 def count(self, **kwargs):
108 return self.qs.filter(**kwargs).count()
110 def percentage(self, **kwargs):
114 return '%.2f%%' % (100. * self.count(**kwargs) / total)
117 return self.count(instru=True)
119 def instru_percentage(self):
120 return self.percentage(instru=True)
123 return self.count(sabam=True)
125 def sabam_percentage(self):
126 return self.percentage(sabam=True)
129 return self.count(cfwb=True)
131 def cfwb_percentage(self):
132 return self.percentage(cfwb=True)
135 return self.count(language='fr')
137 def french_percentage(self):
138 considered_tracks = self.count() - self.instru()
139 if considered_tracks == 0:
141 return '%.2f%%' % (100. * self.french() / considered_tracks)
144 return self.count(nonstopfile__creation_timestamp__gte=self.from_date)
146 def percent_new_files(self):
147 return self.percentage(nonstopfile__creation_timestamp__gte=self.from_date)
150 def parse_date(date):
151 if date.endswith('d'):
152 return datetime.datetime.today() + datetime.timedelta(int(date.rstrip('d')))
153 return datetime.datetime.strptime(date, '%Y-%m-%d').date()
155 class StatisticsView(TemplateView):
156 template_name = 'nonstop/statistics.html'
158 def get_context_data(self, **kwargs):
159 context = super(StatisticsView, self).get_context_data(**kwargs)
160 context['zones'] = Nonstop.objects.all().order_by('start')
162 if 'from' in self.request.GET:
163 kwargs['from_date'] = parse_date(self.request.GET['from'])
164 context['from_date'] = kwargs['from_date']
165 if 'until' in self.request.GET:
166 kwargs['until_date'] = parse_date(self.request.GET['until'])
167 if 'onair' in self.request.GET:
168 kwargs['nonstopfile__somalogline__on_air'] = True
169 for zone in context['zones']:
170 zone.stats = ZoneStats(zone, **kwargs)
174 class UploadTracksView(FormView):
175 form_class = UploadTracksForm
176 template_name = 'nonstop/upload.html'
179 def post(self, request, *args, **kwargs):
180 assert self.request.user.has_perm('nonstop.add_track')
181 form_class = self.get_form_class()
182 form = self.get_form(form_class)
183 tracks = request.FILES.getlist('tracks')
184 if not form.is_valid():
185 return self.form_invalid(form)
186 missing_metadata = []
189 with tempfile.NamedTemporaryFile(prefix='track-upload') as tmpfile:
190 tmpfile.write(f.read())
192 metadata = mutagen.File(tmpfile.name, easy=True)
193 if not metadata or not metadata.get('artist') or not metadata.get('title'):
194 missing_metadata.append(f.name)
196 metadatas[f.name] = metadata
198 form.add_error('tracks', _('Missing metadata in: ') + ', '.join(missing_metadata))
199 return self.form_invalid(form)
202 metadata = metadatas[f.name]
203 artist_name = metadata.get('artist')[0]
204 track_title = metadata.get('title')[0]
206 monthdir = datetime.datetime.today().strftime('%Y-%m')
207 filepath = '%s/%s - %s - %s%s' % (monthdir,
208 datetime.datetime.today().strftime('%y%m%d'),
209 artist_name[:50].replace('/', ' ').strip(),
210 track_title[:80].replace('/', ' ').strip(),
211 os.path.splitext(f.name)[-1])
213 artist, created = Artist.objects.get_or_create(name=artist_name)
214 track, created = Track.objects.get_or_create(title=track_title, artist=artist,
215 defaults={'uploader': self.request.user})
216 if created or not track.file_exists():
217 default_storage.save(os.path.join('nonstop', 'tracks', filepath), content=f)
218 nonstop_file = NonstopFile()
219 nonstop_file.set_track_filepath(filepath)
220 nonstop_file.track = track
223 # don't keep duplicated file and do not create a duplicated nonstop file object
225 if request.POST.get('nonstop_zone'):
226 track.nonstop_zones.add(
227 Nonstop.objects.get(id=request.POST.get('nonstop_zone')))
228 track.sync_nonstop_zones()
230 messages.info(self.request, '%d new track(s)' % len(tracks))
231 return self.form_valid(form)
234 class RecentTracksView(ListView):
235 template_name = 'nonstop/recent_tracks.html'
237 def get_queryset(self):
238 return Track.objects.exclude(creation_timestamp__isnull=True).order_by('-creation_timestamp')[:50]
240 def post(self, request, *args, **kwargs):
241 assert self.request.user.has_perm('nonstop.add_track')
242 for track_id in request.POST.getlist('track'):
243 track = Track.objects.get(id=track_id)
244 track.language = request.POST.get('lang-%s' % track_id, '')
245 track.instru = 'instru-%s' % track_id in request.POST
246 track.sabam = 'sabam-%s' % track_id in request.POST
247 track.cfwb = 'cfwb-%s' % track_id in request.POST
249 return HttpResponseRedirect('.')
252 class QuickLinksView(TemplateView):
253 template_name = 'nonstop/quick_links.html'
256 class SearchView(TemplateView):
257 template_name = 'nonstop/search.html'
259 def get_queryset(self):
260 queryset = Track.objects.all()
262 q = self.request.GET.get('q')
264 queryset = queryset.filter(Q(title__icontains=q.lower()) | Q(artist__name__icontains=q.lower()))
266 zone = self.request.GET.get('zone')
268 from emissions.models import Nonstop
270 queryset = queryset.filter(nonstop_zones=None)
272 queryset = queryset.filter(nonstop_zones__isnull=False).distinct()
274 queryset = queryset.filter(nonstop_zones=zone)
276 order = self.request.GET.get('order_by') or 'title'
278 if 'added_to_nonstop_timestamp' in order:
279 queryset = queryset.filter(added_to_nonstop_timestamp__isnull=False)
280 queryset = queryset.order_by(order)
283 def get_context_data(self, **kwargs):
284 ctx = super(SearchView, self).get_context_data(**kwargs)
285 ctx['form'] = TrackSearchForm(self.request.GET)
286 queryset = self.get_queryset()
287 qs = self.request.GET.copy()
289 ctx['qs'] = qs.urlencode()
291 tracks = Paginator(queryset.select_related(), 20)
293 page = self.request.GET.get('page')
295 ctx['tracks'] = tracks.page(page)
296 except PageNotAnInteger:
297 ctx['tracks'] = tracks.page(1)
299 ctx['tracks'] = tracks.page(tracks.num_pages)
304 class SearchCsvView(SearchView):
305 def get(self, request, *args, **kwargs):
307 writer = csv.writer(out)
308 writer.writerow(['Title', 'Artist', 'Zones', 'Language', 'Instru', 'CFWB', 'Ajout'])
309 for track in self.get_queryset():
311 track.title.encode('utf-8', 'replace') if track.title else 'Inconnu',
312 track.artist.name.encode('utf-8', 'replace') if (track.artist and track.artist.name) else 'Inconnu',
313 ' + '.join([x.title.encode('utf-8') for x in track.nonstop_zones.all()]),
314 track.language or '',
315 track.instru and 'instru' or '',
316 track.cfwb and 'cfwb' or '',
317 track.added_to_nonstop_timestamp.strftime('%Y-%m-%d %H:%M') if track.added_to_nonstop_timestamp else '',
319 return HttpResponse(out.getvalue(), content_type='text/csv; charset=utf-8')
322 class CleanupView(TemplateView):
323 template_name = 'nonstop/cleanup.html'
325 def get_context_data(self, **kwargs):
326 ctx = super(CleanupView, self).get_context_data(**kwargs)
327 ctx['form'] = CleanupForm()
329 zone = self.request.GET.get('zone')
331 from emissions.models import Nonstop
332 ctx['zone'] = Nonstop.objects.get(id=zone)
333 ctx['count'] = Track.objects.filter(nonstop_zones=zone).count()
334 ctx['tracks'] = Track.objects.filter(nonstop_zones=zone).order_by(
335 'added_to_nonstop_timestamp').select_related()[:30]
338 def post(self, request, *args, **kwargs):
339 assert self.request.user.has_perm('nonstop.add_track')
341 for track_id in request.POST.getlist('track'):
342 if request.POST.get('remove-%s' % track_id):
343 track = Track.objects.get(id=track_id)
344 track.nonstop_zones.clear()
345 track.sync_nonstop_zones()
348 messages.info(self.request, 'Removed %d new track(s)' % count)
349 return HttpResponseRedirect('.')
352 class AddDiffusionView(FormView):
353 template_name = 'nonstop/add-diffusion.html'
354 form_class = AddDiffusionForm
356 def get_initial(self):
357 initial = super(AddDiffusionView, self).get_initial()
358 jingle = Jingle.objects.filter(default_for_initial_diffusions=True).first()
360 initial['jingle'] = jingle.id
363 def form_valid(self, form):
364 diffusion = Diffusion.objects.get(id=self.kwargs['pk'])
366 utils.add_diffusion(diffusion, jingle_id=form.cleaned_data.get('jingle'))
367 except utils.DuplicateDiffusionSlot:
368 messages.error(self.request, _('soma slot already in use, the diffusion could not be added.'))
369 except utils.SomaException as e:
370 messages.error(self.request, _('technical soma error (%s)') % e)
372 messages.info(self.request, _('%s added to soma') % episode.emission.title)
374 return super(AddDiffusionView, self).form_valid(form)
376 def get_success_url(self):
377 diffusion = Diffusion.objects.get(id=self.kwargs['pk'])
378 episode = diffusion.episode
379 return reverse('episode-view', kwargs={
380 'emission_slug': episode.emission.slug,
381 'slug': episode.slug})
385 class AddStreamedDiffusionView(CreateView):
386 model = StreamedDiffusion
387 fields = ['jingle', 'stream']
388 template_name = 'nonstop/streamed-diffusion.html'
390 def get_initial(self):
391 initial = super(AddStreamedDiffusionView, self).get_initial()
392 initial['jingle'] = Jingle.objects.filter(default_for_streams=True).first()
395 def form_valid(self, form):
396 form.instance.diffusion_id = self.kwargs['pk']
397 response = super(AddStreamedDiffusionView, self).form_valid(form)
399 utils.add_diffusion(form.instance.diffusion)
400 except utils.SomaException as e:
401 messages.error(self.request, _('technical soma error (%s)') % e)
403 messages.info(self.request, _('%s added to soma') % episode.emission.title)
406 def get_success_url(self):
407 diffusion = Diffusion.objects.get(id=self.kwargs['pk'])
408 episode = diffusion.episode
409 return reverse('episode-view', kwargs={
410 'emission_slug': episode.emission.slug,
411 'slug': episode.slug})
414 def jingle_audio_view(request, *args, **kwargs):
415 jingle = Jingle.objects.get(id=kwargs['pk'])
416 return FileResponse(open(os.path.join(LOCAL_BASE_PATH, 'SPOTS', jingle.filepath), 'rb'))