]> git.0d.be Git - django-panik-nonstop.git/blob - nonstop/views.py
fix display of post message on successful soma programming
[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, AddDiffusionForm
24 from .models import SomaLogLine, Track, Artist, NonstopFile, StreamedDiffusion, Jingle, LOCAL_BASE_PATH
25 from emissions.models import Nonstop, Diffusion
26
27 from . import utils
28
29
30 class SomaDayArchiveView(DayArchiveView):
31     queryset = SomaLogLine.objects.all()
32     date_field = "play_timestamp"
33     make_object_list = True
34     allow_future = False
35     month_format = '%m'
36
37
38 class SomaDayArchiveCsvView(SomaDayArchiveView):
39     def render_to_response(self, context, **response_kwargs):
40         out = StringIO()
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 '',
52                     ])
53             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')
57
58
59 class RedirectTodayView(RedirectView):
60     def get_redirect_url(self, *args, **kwargs):
61         today = datetime.datetime.today()
62         return reverse('archive_day', kwargs={
63                         'year': today.year,
64                         'month': today.month,
65                         'day': today.day})
66
67
68 class TrackDetailView(DetailView):
69     model = Track
70
71     def get_context_data(self, **kwargs):
72         ctx = super(TrackDetailView, self).get_context_data(**kwargs)
73         ctx['metadata_form'] = TrackMetaForm(instance=self.object)
74         return ctx
75
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)
81         form.save()
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('.')
86
87
88 class ArtistDetailView(DetailView):
89     model = Artist
90
91
92 class ArtistListView(ListView):
93     model = Artist
94
95
96 class ZoneStats(object):
97     def __init__(self, zone, from_date=None, until_date=None, **kwargs):
98         self.zone = zone
99         self.qs = Track.objects.filter(nonstop_zones=self.zone, **kwargs)
100         self.from_date = from_date
101         if from_date:
102             self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__gte=from_date)
103         if until_date:
104             self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__lte=until_date)
105         self.qs = self.qs.distinct()
106
107     def count(self, **kwargs):
108         return self.qs.filter(**kwargs).count()
109
110     def percentage(self, **kwargs):
111         total = self.count()
112         if total == 0:
113             return '-'
114         return '%.2f%%' % (100. * self.count(**kwargs) / total)
115
116     def instru(self):
117         return self.count(instru=True)
118
119     def instru_percentage(self):
120         return self.percentage(instru=True)
121
122     def sabam(self):
123         return self.count(sabam=True)
124
125     def sabam_percentage(self):
126         return self.percentage(sabam=True)
127
128     def cfwb(self):
129         return self.count(cfwb=True)
130
131     def cfwb_percentage(self):
132         return self.percentage(cfwb=True)
133
134     def french(self):
135         return self.count(language='fr')
136
137     def french_percentage(self):
138         considered_tracks = self.count() - self.instru()
139         if considered_tracks == 0:
140             return '-'
141         return '%.2f%%' % (100. * self.french() / considered_tracks)
142
143     def new_files(self):
144         return self.count(nonstopfile__creation_timestamp__gte=self.from_date)
145
146     def percent_new_files(self):
147         return self.percentage(nonstopfile__creation_timestamp__gte=self.from_date)
148
149
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()
154
155 class StatisticsView(TemplateView):
156     template_name = 'nonstop/statistics.html'
157
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')
161         kwargs = {}
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)
171         return context
172
173
174 class UploadTracksView(FormView):
175     form_class = UploadTracksForm
176     template_name = 'nonstop/upload.html'
177     success_url = '.'
178
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 = []
187         metadatas = {}
188         for f in tracks:
189             with tempfile.NamedTemporaryFile(prefix='track-upload') as tmpfile:
190                 tmpfile.write(f.read())
191                 f.seek(0)
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)
195             else:
196                 metadatas[f.name] = metadata
197         if missing_metadata:
198             form.add_error('tracks', _('Missing metadata in: ') + ', '.join(missing_metadata))
199             return self.form_invalid(form)
200
201         for f in tracks:
202             metadata = metadatas[f.name]
203             artist_name = metadata.get('artist')[0]
204             track_title = metadata.get('title')[0]
205
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])
212
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
221                 nonstop_file.save()
222             else:
223                 # don't keep duplicated file and do not create a duplicated nonstop file object
224                 pass
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()
229
230         messages.info(self.request, '%d new track(s)' % len(tracks))
231         return self.form_valid(form)
232
233
234 class RecentTracksView(ListView):
235     template_name = 'nonstop/recent_tracks.html'
236
237     def get_queryset(self):
238         return Track.objects.exclude(creation_timestamp__isnull=True).order_by('-creation_timestamp')[:50]
239
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
248             track.save()
249         return HttpResponseRedirect('.')
250
251
252 class QuickLinksView(TemplateView):
253     template_name = 'nonstop/quick_links.html'
254
255
256 class SearchView(TemplateView):
257     template_name = 'nonstop/search.html'
258
259     def get_queryset(self):
260         queryset = Track.objects.all()
261
262         q = self.request.GET.get('q')
263         if q:
264             queryset = queryset.filter(Q(title__icontains=q.lower()) | Q(artist__name__icontains=q.lower()))
265
266         zone = self.request.GET.get('zone')
267         if zone:
268             from emissions.models import Nonstop
269             if zone == 'none':
270                 queryset = queryset.filter(nonstop_zones=None)
271             elif zone == 'any':
272                 queryset = queryset.filter(nonstop_zones__isnull=False).distinct()
273             else:
274                 queryset = queryset.filter(nonstop_zones=zone)
275
276         order = self.request.GET.get('order_by') or 'title'
277         if order:
278             if 'added_to_nonstop_timestamp' in order:
279                 queryset = queryset.filter(added_to_nonstop_timestamp__isnull=False)
280             queryset = queryset.order_by(order)
281         return queryset
282
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()
288         qs.pop('page', None)
289         ctx['qs'] = qs.urlencode()
290
291         tracks = Paginator(queryset.select_related(), 20)
292
293         page = self.request.GET.get('page')
294         try:
295             ctx['tracks'] = tracks.page(page)
296         except PageNotAnInteger:
297             ctx['tracks'] = tracks.page(1)
298         except EmptyPage:
299             ctx['tracks'] = tracks.page(tracks.num_pages)
300
301         return ctx
302
303
304 class SearchCsvView(SearchView):
305     def get(self, request, *args, **kwargs):
306         out = StringIO()
307         writer = csv.writer(out)
308         writer.writerow(['Title', 'Artist', 'Zones', 'Language', 'Instru', 'CFWB', 'Ajout'])
309         for track in self.get_queryset():
310             writer.writerow([
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 '',
318                 ])
319         return HttpResponse(out.getvalue(), content_type='text/csv; charset=utf-8')
320
321
322 class CleanupView(TemplateView):
323     template_name = 'nonstop/cleanup.html'
324
325     def get_context_data(self, **kwargs):
326         ctx = super(CleanupView, self).get_context_data(**kwargs)
327         ctx['form'] = CleanupForm()
328
329         zone = self.request.GET.get('zone')
330         if 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]
336         return ctx
337
338     def post(self, request, *args, **kwargs):
339         assert self.request.user.has_perm('nonstop.add_track')
340         count = 0
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()
346                 count += 1
347         if count:
348             messages.info(self.request, 'Removed %d new track(s)' % count)
349         return HttpResponseRedirect('.')
350
351
352 class AddDiffusionView(FormView):
353     template_name = 'nonstop/add-diffusion.html'
354     form_class = AddDiffusionForm
355
356     def get_initial(self):
357         initial = super(AddDiffusionView, self).get_initial()
358         jingle = Jingle.objects.filter(default_for_initial_diffusions=True).first()
359         if jingle:
360             initial['jingle'] = jingle.id
361         return initial
362
363     def form_valid(self, form):
364         diffusion = Diffusion.objects.get(id=self.kwargs['pk'])
365         episode = diffusion.episode
366         try:
367             utils.add_diffusion(diffusion, jingle_id=form.cleaned_data.get('jingle'))
368         except utils.DuplicateDiffusionSlot:
369             messages.error(self.request, _('soma slot already in use, the diffusion could not be added.'))
370         except utils.SomaException as e:
371             messages.error(self.request, _('technical soma error (%s)') % e)
372         else:
373             messages.info(self.request, _('%s added to soma') % episode.emission.title)
374
375         return super(AddDiffusionView, self).form_valid(form)
376
377     def get_success_url(self):
378         diffusion = Diffusion.objects.get(id=self.kwargs['pk'])
379         episode = diffusion.episode
380         return reverse('episode-view', kwargs={
381             'emission_slug': episode.emission.slug,
382             'slug': episode.slug})
383
384
385
386 class AddStreamedDiffusionView(CreateView):
387     model = StreamedDiffusion
388     fields = ['jingle', 'stream']
389     template_name = 'nonstop/streamed-diffusion.html'
390
391     def get_initial(self):
392         initial = super(AddStreamedDiffusionView, self).get_initial()
393         initial['jingle'] = Jingle.objects.filter(default_for_streams=True).first()
394         return initial
395
396     def form_valid(self, form):
397         form.instance.diffusion_id = self.kwargs['pk']
398         response = super(AddStreamedDiffusionView, self).form_valid(form)
399         episode = form.instance.diffusion.episode
400         try:
401             utils.add_diffusion(form.instance.diffusion)
402         except utils.SomaException as e:
403             messages.error(self.request, _('technical soma error (%s)') % e)
404         else:
405             messages.info(self.request, _('%s added to soma') % episode.emission.title)
406         return response
407
408     def get_success_url(self):
409         diffusion = Diffusion.objects.get(id=self.kwargs['pk'])
410         episode = diffusion.episode
411         return reverse('episode-view', kwargs={
412             'emission_slug': episode.emission.slug,
413             'slug': episode.slug})
414
415
416 def jingle_audio_view(request, *args, **kwargs):
417     jingle = Jingle.objects.get(id=kwargs['pk'])
418     return FileResponse(open(os.path.join(LOCAL_BASE_PATH, 'SPOTS', jingle.filepath), 'rb'))