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