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, Diffusion
30 class SomaDayArchiveView(DayArchiveView):
31 queryset = SomaLogLine.objects.all()
32 date_field = "play_timestamp"
33 make_object_list = True
38 class SomaDayArchiveCsvView(SomaDayArchiveView):
39 def render_to_response(self, context, **response_kwargs):
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.added_to_nonstop_timestamp 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')
59 class RedirectTodayView(RedirectView):
60 def get_redirect_url(self, *args, **kwargs):
61 today = datetime.datetime.today()
62 return reverse('archive_day', kwargs={
68 class TrackDetailView(DetailView):
71 def get_context_data(self, **kwargs):
72 ctx = super(TrackDetailView, self).get_context_data(**kwargs)
73 ctx['metadata_form'] = TrackMetaForm(instance=self.object)
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)
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('.')
88 class ArtistDetailView(DetailView):
92 class ArtistListView(ListView):
96 class ZoneStats(object):
97 def __init__(self, zone, from_date=None, until_date=None, **kwargs):
99 self.qs = Track.objects.filter(nonstop_zones=self.zone, **kwargs)
100 self.from_date = from_date
102 self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__gte=from_date)
104 self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__lte=until_date)
105 self.qs = self.qs.distinct()
107 def count(self, **kwargs):
108 return self.qs.filter(**kwargs).count()
110 def percentage(self, **kwargs):
114 return '%.2f%%' % (100. * self.count(**kwargs) / total)
117 return self.count(instru=True)
119 def instru_percentage(self):
120 return self.percentage(instru=True)
123 return self.count(sabam=True)
125 def sabam_percentage(self):
126 return self.percentage(sabam=True)
129 return self.count(cfwb=True)
131 def cfwb_percentage(self):
132 return self.percentage(cfwb=True)
135 return self.count(language='fr')
137 def french_percentage(self):
138 considered_tracks = self.count() - self.instru()
139 if considered_tracks == 0:
141 return '%.2f%%' % (100. * self.french() / considered_tracks)
144 return self.count(nonstopfile__creation_timestamp__gte=self.from_date)
146 def percent_new_files(self):
147 return self.percentage(nonstopfile__creation_timestamp__gte=self.from_date)
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()
155 class StatisticsView(TemplateView):
156 template_name = 'nonstop/statistics.html'
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')
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)
174 class UploadTracksView(FormView):
175 form_class = UploadTracksForm
176 template_name = 'nonstop/upload.html'
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 = []
189 with tempfile.NamedTemporaryFile(prefix='track-upload') as tmpfile:
190 tmpfile.write(f.read())
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)
196 metadatas[f.name] = metadata
198 form.add_error('tracks', _('Missing metadata in: ') + ', '.join(missing_metadata))
199 return self.form_invalid(form)
202 metadata = metadatas[f.name]
203 artist_name = metadata.get('artist')[0]
204 track_title = metadata.get('title')[0]
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])
213 default_storage.save(os.path.join('nonstop', 'tracks', filepath), content=f)
215 nonstop_file = NonstopFile()
216 nonstop_file.set_track_filepath(filepath)
217 artist, created = Artist.objects.get_or_create(name=artist_name)
218 track, created = Track.objects.get_or_create(title=track_title, artist=artist,
219 defaults={'uploader': self.request.user})
220 nonstop_file.track = track
222 if request.POST.get('nonstop_zone'):
223 track.nonstop_zones.add(
224 Nonstop.objects.get(id=request.POST.get('nonstop_zone')))
225 nonstop_file.track.sync_nonstop_zones()
227 messages.info(self.request, '%d new track(s)' % len(tracks))
228 return self.form_valid(form)
231 class RecentTracksView(ListView):
232 template_name = 'nonstop/recent_tracks.html'
234 def get_queryset(self):
235 return Track.objects.exclude(creation_timestamp__isnull=True).order_by('-creation_timestamp')[:50]
237 def post(self, request, *args, **kwargs):
238 assert self.request.user.has_perm('nonstop.add_track')
239 for track_id in request.POST.getlist('track'):
240 track = Track.objects.get(id=track_id)
241 track.language = request.POST.get('lang-%s' % track_id, '')
242 track.instru = 'instru-%s' % track_id in request.POST
243 track.sabam = 'sabam-%s' % track_id in request.POST
244 track.cfwb = 'cfwb-%s' % track_id in request.POST
246 return HttpResponseRedirect('.')
249 class QuickLinksView(TemplateView):
250 template_name = 'nonstop/quick_links.html'
253 class SearchView(TemplateView):
254 template_name = 'nonstop/search.html'
256 def get_queryset(self):
257 queryset = Track.objects.all()
259 q = self.request.GET.get('q')
261 queryset = queryset.filter(Q(title__icontains=q.lower()) | Q(artist__name__icontains=q.lower()))
263 zone = self.request.GET.get('zone')
265 from emissions.models import Nonstop
267 queryset = queryset.filter(nonstop_zones=None)
269 queryset = queryset.filter(nonstop_zones__isnull=False).distinct()
271 queryset = queryset.filter(nonstop_zones=zone)
273 order = self.request.GET.get('order_by') or 'title'
275 if 'added_to_nonstop_timestamp' in order:
276 queryset = queryset.filter(added_to_nonstop_timestamp__isnull=False)
277 queryset = queryset.order_by(order)
280 def get_context_data(self, **kwargs):
281 ctx = super(SearchView, self).get_context_data(**kwargs)
282 ctx['form'] = TrackSearchForm(self.request.GET)
283 queryset = self.get_queryset()
284 qs = self.request.GET.copy()
286 ctx['qs'] = qs.urlencode()
288 tracks = Paginator(queryset.select_related(), 20)
290 page = self.request.GET.get('page')
292 ctx['tracks'] = tracks.page(page)
293 except PageNotAnInteger:
294 ctx['tracks'] = tracks.page(1)
296 ctx['tracks'] = tracks.page(tracks.num_pages)
301 class SearchCsvView(SearchView):
302 def get(self, request, *args, **kwargs):
304 writer = csv.writer(out)
305 writer.writerow(['Title', 'Artist', 'Zones', 'Language', 'Instru', 'CFWB', 'Ajout'])
306 for track in self.get_queryset():
308 track.title.encode('utf-8', 'replace') if track.title else 'Inconnu',
309 track.artist.name.encode('utf-8', 'replace') if (track.artist and track.artist.name) else 'Inconnu',
310 ' + '.join([x.title.encode('utf-8') for x in track.nonstop_zones.all()]),
311 track.language or '',
312 track.instru and 'instru' or '',
313 track.cfwb and 'cfwb' or '',
314 track.added_to_nonstop_timestamp.strftime('%Y-%m-%d %H:%M') if track.added_to_nonstop_timestamp else '',
316 return HttpResponse(out.getvalue(), content_type='text/csv; charset=utf-8')
319 class CleanupView(TemplateView):
320 template_name = 'nonstop/cleanup.html'
322 def get_context_data(self, **kwargs):
323 ctx = super(CleanupView, self).get_context_data(**kwargs)
324 ctx['form'] = CleanupForm()
326 zone = self.request.GET.get('zone')
328 from emissions.models import Nonstop
329 ctx['zone'] = Nonstop.objects.get(id=zone)
330 ctx['count'] = Track.objects.filter(nonstop_zones=zone).count()
331 ctx['tracks'] = Track.objects.filter(nonstop_zones=zone).order_by(
332 'added_to_nonstop_timestamp').select_related()[:30]
335 def post(self, request, *args, **kwargs):
336 assert self.request.user.has_perm('nonstop.add_track')
338 for track_id in request.POST.getlist('track'):
339 if request.POST.get('remove-%s' % track_id):
340 track = Track.objects.get(id=track_id)
341 track.nonstop_zones.clear()
342 track.sync_nonstop_zones()
345 messages.info(self.request, 'Removed %d new track(s)' % count)
346 return HttpResponseRedirect('.')
349 class AddDiffusionView(DetailView):
352 def get(self, request, *args, **kwargs):
353 diffusion = self.get_object()
354 utils.add_diffusion(diffusion)
355 episode = diffusion.episode
356 messages.info(self.request, _('%s added to soma') % episode.emission.title)
357 return HttpResponseRedirect(reverse('episode-view', kwargs={
358 'emission_slug': episode.emission.slug,
359 'slug': episode.slug}))