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 ''])
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')
54 class RedirectTodayView(RedirectView):
55 def get_redirect_url(self, *args, **kwargs):
56 today = datetime.datetime.today()
57 return reverse('archive_day', kwargs={
63 class TrackDetailView(DetailView):
66 def get_context_data(self, **kwargs):
67 ctx = super(TrackDetailView, self).get_context_data(**kwargs)
68 ctx['metadata_form'] = TrackMetaForm(instance=self.object)
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)
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('.')
83 class ArtistDetailView(DetailView):
87 class ArtistListView(ListView):
91 class ZoneStats(object):
92 def __init__(self, zone, from_date=None, until_date=None, **kwargs):
94 self.qs = Track.objects.filter(nonstop_zones=self.zone, **kwargs)
95 self.from_date = from_date
97 self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__gte=from_date)
99 self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__lte=until_date)
100 self.qs = self.qs.distinct()
102 def count(self, **kwargs):
103 return self.qs.filter(**kwargs).count()
105 def percentage(self, **kwargs):
109 return '%.2f%%' % (100. * self.count(**kwargs) / total)
112 return self.count(instru=True)
114 def instru_percentage(self):
115 return self.percentage(instru=True)
118 return self.count(sabam=True)
120 def sabam_percentage(self):
121 return self.percentage(sabam=True)
124 return self.count(cfwb=True)
126 def cfwb_percentage(self):
127 return self.percentage(cfwb=True)
130 return self.count(language='fr')
132 def french_percentage(self):
133 considered_tracks = self.count() - self.instru()
134 if considered_tracks == 0:
136 return '%.2f%%' % (100. * self.french() / considered_tracks)
139 return self.count(nonstopfile__creation_timestamp__gte=self.from_date)
141 def percent_new_files(self):
142 return self.percentage(nonstopfile__creation_timestamp__gte=self.from_date)
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()
150 class StatisticsView(TemplateView):
151 template_name = 'nonstop/statistics.html'
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')
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)
169 class UploadTracksView(FormView):
170 form_class = UploadTracksForm
171 template_name = 'nonstop/upload.html'
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 = []
184 with tempfile.NamedTemporaryFile(prefix='track-upload') as tmpfile:
185 tmpfile.write(f.read())
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)
191 metadatas[f.name] = metadata
193 form.add_error('tracks', _('Missing metadata in: ') + ', '.join(missing_metadata))
194 return self.form_invalid(form)
197 metadata = metadatas[f.name]
198 artist_name = metadata.get('artist')[0]
199 track_title = metadata.get('title')[0]
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])
208 default_storage.save(os.path.join('nonstop', 'tracks', filepath), content=f)
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
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()
222 messages.info(self.request, '%d new track(s)' % len(tracks))
223 return self.form_valid(form)
226 class RecentTracksView(ListView):
227 template_name = 'nonstop/recent_tracks.html'
229 def get_queryset(self):
230 return Track.objects.exclude(creation_timestamp__isnull=True).order_by('-creation_timestamp')[:50]
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
241 return HttpResponseRedirect('.')
244 class QuickLinksView(TemplateView):
245 template_name = 'nonstop/quick_links.html'
248 class SearchView(TemplateView):
249 template_name = 'nonstop/search.html'
251 def get_queryset(self):
252 queryset = Track.objects.all()
254 q = self.request.GET.get('q')
256 queryset = queryset.filter(Q(title__icontains=q.lower()) | Q(artist__name__icontains=q.lower()))
258 zone = self.request.GET.get('zone')
260 from emissions.models import Nonstop
262 queryset = queryset.filter(nonstop_zones=None)
264 queryset = queryset.filter(nonstop_zones__isnull=False).distinct()
266 queryset = queryset.filter(nonstop_zones=zone)
268 order = self.request.GET.get('order_by') or 'title'
270 if 'added_to_nonstop_timestamp' in order:
271 queryset = queryset.filter(added_to_nonstop_timestamp__isnull=False)
272 queryset = queryset.order_by(order)
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()
281 ctx['qs'] = qs.urlencode()
283 tracks = Paginator(queryset.select_related(), 20)
285 page = self.request.GET.get('page')
287 ctx['tracks'] = tracks.page(page)
288 except PageNotAnInteger:
289 ctx['tracks'] = tracks.page(1)
291 ctx['tracks'] = tracks.page(tracks.num_pages)
296 class SearchCsvView(SearchView):
297 def get(self, request, *args, **kwargs):
299 writer = csv.writer(out)
300 writer.writerow(['Title', 'Artist', 'Zones', 'Language', 'Instru', 'CFWB'])
301 for track in self.get_queryset():
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')
312 class CleanupView(TemplateView):
313 template_name = 'nonstop/cleanup.html'
315 def get_context_data(self, **kwargs):
316 ctx = super(CleanupView, self).get_context_data(**kwargs)
317 ctx['form'] = CleanupForm()
319 zone = self.request.GET.get('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]
328 def post(self, request, *args, **kwargs):
329 assert self.request.user.has_perm('nonstop.add_track')
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()
338 messages.info(self.request, 'Removed %d new track(s)' % count)
339 return HttpResponseRedirect('.')