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