]> git.0d.be Git - django-panik-nonstop.git/blob - nonstop/views.py
move soma scheduling to a management command
[django-panik-nonstop.git] / nonstop / views.py
1 import copy
2 import csv
3 import datetime
4 import os
5 import tempfile
6
7 import mutagen
8
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
22
23 from .forms import UploadTracksForm, TrackMetaForm, TrackSearchForm, CleanupForm
24 from .models import (SomaLogLine, Track, Artist, NonstopFile, StreamedDiffusion, Jingle,
25         Stream, LOCAL_BASE_PATH)
26 from emissions.models import Nonstop, Diffusion
27
28 from . import utils
29
30
31 class SomaDayArchiveView(DayArchiveView):
32     queryset = SomaLogLine.objects.all()
33     date_field = "play_timestamp"
34     make_object_list = True
35     allow_future = False
36     month_format = '%m'
37
38
39 class SomaDayArchiveCsvView(SomaDayArchiveView):
40     def render_to_response(self, context, **response_kwargs):
41         out = StringIO()
42         writer = csv.writer(out)
43         for line in context['object_list']:
44             if line.filepath.track:
45                 writer.writerow([line.play_timestamp.strftime('%Y-%m-%d %H:%M'),
46                     line.filepath.short,
47                     line.filepath.track.title,
48                     line.filepath.track.artist.name,
49                     line.filepath.track.language,
50                     line.filepath.track.instru and 'instru' or '',
51                     line.filepath.track.cfwb and 'cfwb' or '',
52                     line.filepath.track.added_to_nonstop_timestamp.strftime('%Y-%m-%d %H:%M') if line.filepath.track.added_to_nonstop_timestamp else '',
53                     ])
54             else:
55                 writer.writerow([line.play_timestamp.strftime('%Y-%m-%d %H:%M'),
56                                 line.filepath.short])
57         return HttpResponse(out.getvalue(), content_type='text/csv; charset=utf-8')
58
59
60 class RedirectTodayView(RedirectView):
61     def get_redirect_url(self, *args, **kwargs):
62         today = datetime.datetime.today()
63         return reverse('archive_day', kwargs={
64                         'year': today.year,
65                         'month': today.month,
66                         'day': today.day})
67
68
69 class TrackDetailView(DetailView):
70     model = Track
71
72     def get_context_data(self, **kwargs):
73         ctx = super(TrackDetailView, self).get_context_data(**kwargs)
74         ctx['metadata_form'] = TrackMetaForm(instance=self.object)
75         return ctx
76
77     def post(self, request, *args, **kwargs):
78         assert self.request.user.has_perm('nonstop.add_track')
79         instance = self.get_object()
80         old_nonstop_zones = copy.copy(instance.nonstop_zones.all())
81         form = TrackMetaForm(request.POST, instance=instance)
82         form.save()
83         new_nonstop_zones = self.get_object().nonstop_zones.all()
84         if set(old_nonstop_zones) != set(new_nonstop_zones):
85             instance.sync_nonstop_zones()
86         return HttpResponseRedirect('.')
87
88
89 class ArtistDetailView(DetailView):
90     model = Artist
91
92
93 class ArtistListView(ListView):
94     model = Artist
95
96
97 class ZoneStats(object):
98     def __init__(self, zone, from_date=None, until_date=None, **kwargs):
99         self.zone = zone
100         self.qs = Track.objects.filter(nonstop_zones=self.zone, **kwargs)
101         self.from_date = from_date
102         if from_date:
103             self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__gte=from_date)
104         if until_date:
105             self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__lte=until_date)
106         self.qs = self.qs.distinct()
107
108     def count(self, **kwargs):
109         return self.qs.filter(**kwargs).count()
110
111     def percentage(self, **kwargs):
112         total = self.count()
113         if total == 0:
114             return '-'
115         return '%.2f%%' % (100. * self.count(**kwargs) / total)
116
117     def instru(self):
118         return self.count(instru=True)
119
120     def instru_percentage(self):
121         return self.percentage(instru=True)
122
123     def sabam(self):
124         return self.count(sabam=True)
125
126     def sabam_percentage(self):
127         return self.percentage(sabam=True)
128
129     def cfwb(self):
130         return self.count(cfwb=True)
131
132     def cfwb_percentage(self):
133         return self.percentage(cfwb=True)
134
135     def french(self):
136         return self.count(language='fr')
137
138     def french_percentage(self):
139         considered_tracks = self.count() - self.instru()
140         if considered_tracks == 0:
141             return '-'
142         return '%.2f%%' % (100. * self.french() / considered_tracks)
143
144     def new_files(self):
145         return self.count(nonstopfile__creation_timestamp__gte=self.from_date)
146
147     def percent_new_files(self):
148         return self.percentage(nonstopfile__creation_timestamp__gte=self.from_date)
149
150
151 def parse_date(date):
152     if date.endswith('d'):
153         return datetime.datetime.today() + datetime.timedelta(int(date.rstrip('d')))
154     return datetime.datetime.strptime(date, '%Y-%m-%d').date()
155
156 class StatisticsView(TemplateView):
157     template_name = 'nonstop/statistics.html'
158
159     def get_context_data(self, **kwargs):
160         context = super(StatisticsView, self).get_context_data(**kwargs)
161         context['zones'] = Nonstop.objects.all().order_by('start')
162         kwargs = {}
163         if 'from' in self.request.GET:
164             kwargs['from_date'] = parse_date(self.request.GET['from'])
165             context['from_date'] = kwargs['from_date']
166         if 'until' in self.request.GET:
167             kwargs['until_date'] = parse_date(self.request.GET['until'])
168         if 'onair' in self.request.GET:
169             kwargs['nonstopfile__somalogline__on_air'] = True
170         for zone in context['zones']:
171             zone.stats = ZoneStats(zone, **kwargs)
172         return context
173
174
175 class UploadTracksView(FormView):
176     form_class = UploadTracksForm
177     template_name = 'nonstop/upload.html'
178     success_url = '.'
179
180     def post(self, request, *args, **kwargs):
181         assert self.request.user.has_perm('nonstop.add_track')
182         form_class = self.get_form_class()
183         form = self.get_form(form_class)
184         tracks = request.FILES.getlist('tracks')
185         if not form.is_valid():
186             return self.form_invalid(form)
187         missing_metadata = []
188         metadatas = {}
189         for f in tracks:
190             with tempfile.NamedTemporaryFile(prefix='track-upload') as tmpfile:
191                 tmpfile.write(f.read())
192                 f.seek(0)
193                 metadata = mutagen.File(tmpfile.name, easy=True)
194             if not metadata or not metadata.get('artist') or not metadata.get('title'):
195                 missing_metadata.append(f.name)
196             else:
197                 metadatas[f.name] = metadata
198         if missing_metadata:
199             form.add_error('tracks', _('Missing metadata in: ') + ', '.join(missing_metadata))
200             return self.form_invalid(form)
201
202         for f in tracks:
203             metadata = metadatas[f.name]
204             artist_name = metadata.get('artist')[0]
205             track_title = metadata.get('title')[0]
206
207             monthdir = datetime.datetime.today().strftime('%Y-%m')
208             filepath = '%s/%s - %s - %s%s' % (monthdir,
209                 datetime.datetime.today().strftime('%y%m%d'),
210                 artist_name[:50].replace('/', ' ').strip(),
211                 track_title[:80].replace('/', ' ').strip(),
212                 os.path.splitext(f.name)[-1])
213
214             artist, created = Artist.objects.get_or_create(name=artist_name)
215             track, created = Track.objects.get_or_create(title=track_title, artist=artist,
216                     defaults={'uploader': self.request.user})
217             if created or not track.file_exists():
218                 default_storage.save(os.path.join('nonstop', 'tracks', filepath), content=f)
219                 nonstop_file = NonstopFile()
220                 nonstop_file.set_track_filepath(filepath)
221                 nonstop_file.track = track
222                 nonstop_file.save()
223             else:
224                 # don't keep duplicated file and do not create a duplicated nonstop file object
225                 pass
226             if request.POST.get('nonstop_zone'):
227                 track.nonstop_zones.add(
228                         Nonstop.objects.get(id=request.POST.get('nonstop_zone')))
229             track.sync_nonstop_zones()
230
231         messages.info(self.request, '%d new track(s)' % len(tracks))
232         return self.form_valid(form)
233
234
235 class RecentTracksView(ListView):
236     template_name = 'nonstop/recent_tracks.html'
237
238     def get_queryset(self):
239         return Track.objects.exclude(creation_timestamp__isnull=True).order_by('-creation_timestamp')[:50]
240
241     def post(self, request, *args, **kwargs):
242         assert self.request.user.has_perm('nonstop.add_track')
243         for track_id in request.POST.getlist('track'):
244             track = Track.objects.get(id=track_id)
245             track.language = request.POST.get('lang-%s' % track_id, '')
246             track.instru = 'instru-%s' % track_id in request.POST
247             track.sabam = 'sabam-%s' % track_id in request.POST
248             track.cfwb = 'cfwb-%s' % track_id in request.POST
249             track.save()
250         return HttpResponseRedirect('.')
251
252
253 class QuickLinksView(TemplateView):
254     template_name = 'nonstop/quick_links.html'
255
256
257 class SearchView(TemplateView):
258     template_name = 'nonstop/search.html'
259
260     def get_queryset(self):
261         queryset = Track.objects.all()
262
263         q = self.request.GET.get('q')
264         if q:
265             queryset = queryset.filter(Q(title__icontains=q.lower()) | Q(artist__name__icontains=q.lower()))
266
267         zone = self.request.GET.get('zone')
268         if zone:
269             from emissions.models import Nonstop
270             if zone == 'none':
271                 queryset = queryset.filter(nonstop_zones=None)
272             elif zone == 'any':
273                 queryset = queryset.filter(nonstop_zones__isnull=False).distinct()
274             else:
275                 queryset = queryset.filter(nonstop_zones=zone)
276
277         order = self.request.GET.get('order_by') or 'title'
278         if order:
279             if 'added_to_nonstop_timestamp' in order:
280                 queryset = queryset.filter(added_to_nonstop_timestamp__isnull=False)
281             queryset = queryset.order_by(order)
282         return queryset
283
284     def get_context_data(self, **kwargs):
285         ctx = super(SearchView, self).get_context_data(**kwargs)
286         ctx['form'] = TrackSearchForm(self.request.GET)
287         queryset = self.get_queryset()
288         qs = self.request.GET.copy()
289         qs.pop('page', None)
290         ctx['qs'] = qs.urlencode()
291
292         tracks = Paginator(queryset.select_related(), 20)
293
294         page = self.request.GET.get('page')
295         try:
296             ctx['tracks'] = tracks.page(page)
297         except PageNotAnInteger:
298             ctx['tracks'] = tracks.page(1)
299         except EmptyPage:
300             ctx['tracks'] = tracks.page(tracks.num_pages)
301
302         return ctx
303
304
305 class SearchCsvView(SearchView):
306     def get(self, request, *args, **kwargs):
307         out = StringIO()
308         writer = csv.writer(out)
309         writer.writerow(['Title', 'Artist', 'Zones', 'Language', 'Instru', 'CFWB', 'Ajout'])
310         for track in self.get_queryset():
311             writer.writerow([
312                 track.title if track.title else 'Inconnu',
313                 track.artist.name if (track.artist and track.artist.name) else 'Inconnu',
314                 ' + '.join([x.title for x in track.nonstop_zones.all()]),
315                 track.language or '',
316                 track.instru and 'instru' or '',
317                 track.cfwb and 'cfwb' or '',
318                 track.added_to_nonstop_timestamp.strftime('%Y-%m-%d %H:%M') if track.added_to_nonstop_timestamp else '',
319                 ])
320         return HttpResponse(out.getvalue(), content_type='text/csv; charset=utf-8')
321
322
323 class CleanupView(TemplateView):
324     template_name = 'nonstop/cleanup.html'
325
326     def get_context_data(self, **kwargs):
327         ctx = super(CleanupView, self).get_context_data(**kwargs)
328         ctx['form'] = CleanupForm()
329
330         zone = self.request.GET.get('zone')
331         if zone:
332             from emissions.models import Nonstop
333             ctx['zone'] = Nonstop.objects.get(id=zone)
334             ctx['count'] = Track.objects.filter(nonstop_zones=zone).count()
335             ctx['tracks'] = Track.objects.filter(nonstop_zones=zone).order_by(
336                             'added_to_nonstop_timestamp').select_related()[:30]
337         return ctx
338
339     def post(self, request, *args, **kwargs):
340         assert self.request.user.has_perm('nonstop.add_track')
341         count = 0
342         for track_id in request.POST.getlist('track'):
343             if request.POST.get('remove-%s' % track_id):
344                 track = Track.objects.get(id=track_id)
345                 track.nonstop_zones.clear()
346                 track.sync_nonstop_zones()
347                 count += 1
348         if count:
349             messages.info(self.request, 'Removed %d new track(s)' % count)
350         return HttpResponseRedirect('.')
351
352
353 class AddSomaDiffusionView(CreateView):
354     model = StreamedDiffusion
355     fields = ['jingle', 'stream']
356     template_name = 'nonstop/streamed-diffusion.html'
357
358     @property
359     def fields(self):
360         diffusion = Diffusion.objects.get(id=self.kwargs['pk'])
361         fields = ['jingle']
362         if not diffusion.episode.soundfile_set.filter(fragment=False).exists():
363             fields.append('stream')
364         return fields
365
366     def get_initial(self):
367         initial = super(AddSomaDiffusionView, self).get_initial()
368         initial['jingle'] = Jingle.objects.filter(default_for_streams=True).first()
369         initial['stream'] = Stream.objects.all().first()
370         return initial
371
372     def form_valid(self, form):
373         form.instance.diffusion_id = self.kwargs['pk']
374         episode = form.instance.diffusion.episode
375         if 'stream' in self.fields and form.instance.stream_id is None:
376             messages.error(self.request, _('missing stream'))
377             return HttpResponseRedirect(reverse('episode-view', kwargs={
378                 'emission_slug': episode.emission.slug,
379                 'slug': episode.slug}))
380         response = super(AddSomaDiffusionView, self).form_valid(form)
381         messages.info(self.request, _('%s added to schedule') % episode.emission.title)
382         return response
383
384     def get_success_url(self):
385         diffusion = Diffusion.objects.get(id=self.kwargs['pk'])
386         episode = diffusion.episode
387         messages.info(self.request, _('%s removed from schedule') % episode.emission.title)
388         return reverse('episode-view', kwargs={
389             'emission_slug': episode.emission.slug,
390             'slug': episode.slug})
391
392
393 class DelSomaDiffusionView(RedirectView):
394     def get_redirect_url(self, pk):
395         soma_diffusion = StreamedDiffusion.objects.filter(diffusion_id=pk).first()
396         episode = soma_diffusion.episode
397         StreamedDiffusion.objects.filter(diffusion_id=pk).delete()
398         return reverse('episode-view', kwargs={
399             'emission_slug': episode.emission.slug,
400             'slug': episode.slug})
401
402
403 def jingle_audio_view(request, *args, **kwargs):
404     jingle = Jingle.objects.get(id=kwargs['pk'])
405     return FileResponse(open(os.path.join(LOCAL_BASE_PATH, 'SPOTS', jingle.filepath), 'rb'))