4 from cStringIO import StringIO
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
23 from .forms import UploadTracksForm, TrackMetaForm, TrackSearchForm, CleanupForm
24 from .models import SomaLogLine, Track, Artist, NonstopFile
25 from emissions.models import Nonstop
27 class SomaDayArchiveView(DayArchiveView):
28 queryset = SomaLogLine.objects.all()
29 date_field = "play_timestamp"
30 make_object_list = True
35 class SomaDayArchiveCsvView(SomaDayArchiveView):
36 def render_to_response(self, context, **response_kwargs):
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 line.filepath.track.added_to_nonstop_timestamp.strftime('%Y-%m-%d %H:%M') if line.filepath.added_to_nonstop_timestamp else '',
51 writer.writerow([line.play_timestamp.strftime('%Y-%m-%d %H:%M'),
52 line.filepath.short.encode('utf-8', 'replace')])
53 return HttpResponse(out.getvalue(), content_type='text/csv; charset=utf-8')
56 class RedirectTodayView(RedirectView):
57 def get_redirect_url(self, *args, **kwargs):
58 today = datetime.datetime.today()
59 return reverse('archive_day', kwargs={
65 class TrackDetailView(DetailView):
68 def get_context_data(self, **kwargs):
69 ctx = super(TrackDetailView, self).get_context_data(**kwargs)
70 ctx['metadata_form'] = TrackMetaForm(instance=self.object)
73 def post(self, request, *args, **kwargs):
74 assert self.request.user.has_perm('nonstop.add_track')
75 instance = self.get_object()
76 old_nonstop_zones = copy.copy(instance.nonstop_zones.all())
77 form = TrackMetaForm(request.POST, instance=instance)
79 new_nonstop_zones = self.get_object().nonstop_zones.all()
80 if set(old_nonstop_zones) != set(new_nonstop_zones):
81 instance.sync_nonstop_zones()
82 return HttpResponseRedirect('.')
85 class ArtistDetailView(DetailView):
89 class ArtistListView(ListView):
93 class ZoneStats(object):
94 def __init__(self, zone, from_date=None, until_date=None, **kwargs):
96 self.qs = Track.objects.filter(nonstop_zones=self.zone, **kwargs)
97 self.from_date = from_date
99 self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__gte=from_date)
101 self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__lte=until_date)
102 self.qs = self.qs.distinct()
104 def count(self, **kwargs):
105 return self.qs.filter(**kwargs).count()
107 def percentage(self, **kwargs):
111 return '%.2f%%' % (100. * self.count(**kwargs) / total)
114 return self.count(instru=True)
116 def instru_percentage(self):
117 return self.percentage(instru=True)
120 return self.count(sabam=True)
122 def sabam_percentage(self):
123 return self.percentage(sabam=True)
126 return self.count(cfwb=True)
128 def cfwb_percentage(self):
129 return self.percentage(cfwb=True)
132 return self.count(language='fr')
134 def french_percentage(self):
135 considered_tracks = self.count() - self.instru()
136 if considered_tracks == 0:
138 return '%.2f%%' % (100. * self.french() / considered_tracks)
141 return self.count(nonstopfile__creation_timestamp__gte=self.from_date)
143 def percent_new_files(self):
144 return self.percentage(nonstopfile__creation_timestamp__gte=self.from_date)
147 def parse_date(date):
148 if date.endswith('d'):
149 return datetime.datetime.today() + datetime.timedelta(int(date.rstrip('d')))
150 return datetime.datetime.strptime(date, '%Y-%m-%d').date()
152 class StatisticsView(TemplateView):
153 template_name = 'nonstop/statistics.html'
155 def get_context_data(self, **kwargs):
156 context = super(StatisticsView, self).get_context_data(**kwargs)
157 context['zones'] = Nonstop.objects.all().order_by('start')
159 if 'from' in self.request.GET:
160 kwargs['from_date'] = parse_date(self.request.GET['from'])
161 context['from_date'] = kwargs['from_date']
162 if 'until' in self.request.GET:
163 kwargs['until_date'] = parse_date(self.request.GET['until'])
164 if 'onair' in self.request.GET:
165 kwargs['nonstopfile__somalogline__on_air'] = True
166 for zone in context['zones']:
167 zone.stats = ZoneStats(zone, **kwargs)
171 class UploadTracksView(FormView):
172 form_class = UploadTracksForm
173 template_name = 'nonstop/upload.html'
176 def post(self, request, *args, **kwargs):
177 assert self.request.user.has_perm('nonstop.add_track')
178 form_class = self.get_form_class()
179 form = self.get_form(form_class)
180 tracks = request.FILES.getlist('tracks')
181 if not form.is_valid():
182 return self.form_invalid(form)
183 missing_metadata = []
186 with tempfile.NamedTemporaryFile(prefix='track-upload') as tmpfile:
187 tmpfile.write(f.read())
189 metadata = mutagen.File(tmpfile.name, easy=True)
190 if not metadata or not metadata.get('artist') or not metadata.get('title'):
191 missing_metadata.append(f.name)
193 metadatas[f.name] = metadata
195 form.add_error('tracks', _('Missing metadata in: ') + ', '.join(missing_metadata))
196 return self.form_invalid(form)
199 metadata = metadatas[f.name]
200 artist_name = metadata.get('artist')[0]
201 track_title = metadata.get('title')[0]
203 monthdir = datetime.datetime.today().strftime('%Y-%m')
204 filepath = '%s/%s - %s - %s%s' % (monthdir,
205 datetime.datetime.today().strftime('%y%m%d'),
206 artist_name[:50].replace('/', ' ').strip(),
207 track_title[:80].replace('/', ' ').strip(),
208 os.path.splitext(f.name)[-1])
210 default_storage.save(os.path.join('nonstop', 'tracks', filepath), content=f)
212 nonstop_file = NonstopFile()
213 nonstop_file.set_track_filepath(filepath)
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 nonstop_file.track = track
219 if request.POST.get('nonstop_zone'):
220 track.nonstop_zones.add(
221 Nonstop.objects.get(id=request.POST.get('nonstop_zone')))
222 nonstop_file.track.sync_nonstop_zones()
224 messages.info(self.request, '%d new track(s)' % len(tracks))
225 return self.form_valid(form)
228 class RecentTracksView(ListView):
229 template_name = 'nonstop/recent_tracks.html'
231 def get_queryset(self):
232 return Track.objects.exclude(creation_timestamp__isnull=True).order_by('-creation_timestamp')[:50]
234 def post(self, request, *args, **kwargs):
235 assert self.request.user.has_perm('nonstop.add_track')
236 for track_id in request.POST.getlist('track'):
237 track = Track.objects.get(id=track_id)
238 track.language = request.POST.get('lang-%s' % track_id, '')
239 track.instru = 'instru-%s' % track_id in request.POST
240 track.sabam = 'sabam-%s' % track_id in request.POST
241 track.cfwb = 'cfwb-%s' % track_id in request.POST
243 return HttpResponseRedirect('.')
246 class QuickLinksView(TemplateView):
247 template_name = 'nonstop/quick_links.html'
250 class SearchView(TemplateView):
251 template_name = 'nonstop/search.html'
253 def get_queryset(self):
254 queryset = Track.objects.all()
256 q = self.request.GET.get('q')
258 queryset = queryset.filter(Q(title__icontains=q.lower()) | Q(artist__name__icontains=q.lower()))
260 zone = self.request.GET.get('zone')
262 from emissions.models import Nonstop
264 queryset = queryset.filter(nonstop_zones=None)
266 queryset = queryset.filter(nonstop_zones__isnull=False).distinct()
268 queryset = queryset.filter(nonstop_zones=zone)
270 order = self.request.GET.get('order_by') or 'title'
272 if 'added_to_nonstop_timestamp' in order:
273 queryset = queryset.filter(added_to_nonstop_timestamp__isnull=False)
274 queryset = queryset.order_by(order)
277 def get_context_data(self, **kwargs):
278 ctx = super(SearchView, self).get_context_data(**kwargs)
279 ctx['form'] = TrackSearchForm(self.request.GET)
280 queryset = self.get_queryset()
281 qs = self.request.GET.copy()
283 ctx['qs'] = qs.urlencode()
285 tracks = Paginator(queryset.select_related(), 20)
287 page = self.request.GET.get('page')
289 ctx['tracks'] = tracks.page(page)
290 except PageNotAnInteger:
291 ctx['tracks'] = tracks.page(1)
293 ctx['tracks'] = tracks.page(tracks.num_pages)
298 class SearchCsvView(SearchView):
299 def get(self, request, *args, **kwargs):
301 writer = csv.writer(out)
302 writer.writerow(['Title', 'Artist', 'Zones', 'Language', 'Instru', 'CFWB', 'Ajout'])
303 for track in self.get_queryset():
305 track.title.encode('utf-8', 'replace') if track.title else 'Inconnu',
306 track.artist.name.encode('utf-8', 'replace') if (track.artist and track.artist.name) else 'Inconnu',
307 ' + '.join([x.title.encode('utf-8') for x in track.nonstop_zones.all()]),
308 track.language or '',
309 track.instru and 'instru' or '',
310 track.cfwb and 'cfwb' or '',
311 track.added_to_nonstop_timestamp.strftime('%Y-%m-%d %H:%M') if track.added_to_nonstop_timestamp else '',
313 return HttpResponse(out.getvalue(), content_type='text/csv; charset=utf-8')
316 class CleanupView(TemplateView):
317 template_name = 'nonstop/cleanup.html'
319 def get_context_data(self, **kwargs):
320 ctx = super(CleanupView, self).get_context_data(**kwargs)
321 ctx['form'] = CleanupForm()
323 zone = self.request.GET.get('zone')
325 from emissions.models import Nonstop
326 ctx['zone'] = Nonstop.objects.get(id=zone)
327 ctx['count'] = Track.objects.filter(nonstop_zones=zone).count()
328 ctx['tracks'] = Track.objects.filter(nonstop_zones=zone).order_by(
329 'added_to_nonstop_timestamp').select_related()[:30]
332 def post(self, request, *args, **kwargs):
333 assert self.request.user.has_perm('nonstop.add_track')
335 for track_id in request.POST.getlist('track'):
336 if request.POST.get('remove-%s' % track_id):
337 track = Track.objects.get(id=track_id)
338 track.nonstop_zones.clear()
339 track.sync_nonstop_zones()
342 messages.info(self.request, 'Removed %d new track(s)' % count)
343 return HttpResponseRedirect('.')