9 from django.core.files.storage import default_storage
10 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
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.six import StringIO
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.track.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 if created or nonstop_file.track is None or not nonstop_file.track.file_exists():
221 nonstop_file.track = track
224 pass # do not create a duplicated nonstop file object
225 if request.POST.get('nonstop_zone'):
226 track.nonstop_zones.add(
227 Nonstop.objects.get(id=request.POST.get('nonstop_zone')))
228 track.sync_nonstop_zones()
230 messages.info(self.request, '%d new track(s)' % len(tracks))
231 return self.form_valid(form)
234 class RecentTracksView(ListView):
235 template_name = 'nonstop/recent_tracks.html'
237 def get_queryset(self):
238 return Track.objects.exclude(creation_timestamp__isnull=True).order_by('-creation_timestamp')[:50]
240 def post(self, request, *args, **kwargs):
241 assert self.request.user.has_perm('nonstop.add_track')
242 for track_id in request.POST.getlist('track'):
243 track = Track.objects.get(id=track_id)
244 track.language = request.POST.get('lang-%s' % track_id, '')
245 track.instru = 'instru-%s' % track_id in request.POST
246 track.sabam = 'sabam-%s' % track_id in request.POST
247 track.cfwb = 'cfwb-%s' % track_id in request.POST
249 return HttpResponseRedirect('.')
252 class QuickLinksView(TemplateView):
253 template_name = 'nonstop/quick_links.html'
256 class SearchView(TemplateView):
257 template_name = 'nonstop/search.html'
259 def get_queryset(self):
260 queryset = Track.objects.all()
262 q = self.request.GET.get('q')
264 queryset = queryset.filter(Q(title__icontains=q.lower()) | Q(artist__name__icontains=q.lower()))
266 zone = self.request.GET.get('zone')
268 from emissions.models import Nonstop
270 queryset = queryset.filter(nonstop_zones=None)
272 queryset = queryset.filter(nonstop_zones__isnull=False).distinct()
274 queryset = queryset.filter(nonstop_zones=zone)
276 order = self.request.GET.get('order_by') or 'title'
278 if 'added_to_nonstop_timestamp' in order:
279 queryset = queryset.filter(added_to_nonstop_timestamp__isnull=False)
280 queryset = queryset.order_by(order)
283 def get_context_data(self, **kwargs):
284 ctx = super(SearchView, self).get_context_data(**kwargs)
285 ctx['form'] = TrackSearchForm(self.request.GET)
286 queryset = self.get_queryset()
287 qs = self.request.GET.copy()
289 ctx['qs'] = qs.urlencode()
291 tracks = Paginator(queryset.select_related(), 20)
293 page = self.request.GET.get('page')
295 ctx['tracks'] = tracks.page(page)
296 except PageNotAnInteger:
297 ctx['tracks'] = tracks.page(1)
299 ctx['tracks'] = tracks.page(tracks.num_pages)
304 class SearchCsvView(SearchView):
305 def get(self, request, *args, **kwargs):
307 writer = csv.writer(out)
308 writer.writerow(['Title', 'Artist', 'Zones', 'Language', 'Instru', 'CFWB', 'Ajout'])
309 for track in self.get_queryset():
311 track.title.encode('utf-8', 'replace') if track.title else 'Inconnu',
312 track.artist.name.encode('utf-8', 'replace') if (track.artist and track.artist.name) else 'Inconnu',
313 ' + '.join([x.title.encode('utf-8') for x in track.nonstop_zones.all()]),
314 track.language or '',
315 track.instru and 'instru' or '',
316 track.cfwb and 'cfwb' or '',
317 track.added_to_nonstop_timestamp.strftime('%Y-%m-%d %H:%M') if track.added_to_nonstop_timestamp else '',
319 return HttpResponse(out.getvalue(), content_type='text/csv; charset=utf-8')
322 class CleanupView(TemplateView):
323 template_name = 'nonstop/cleanup.html'
325 def get_context_data(self, **kwargs):
326 ctx = super(CleanupView, self).get_context_data(**kwargs)
327 ctx['form'] = CleanupForm()
329 zone = self.request.GET.get('zone')
331 from emissions.models import Nonstop
332 ctx['zone'] = Nonstop.objects.get(id=zone)
333 ctx['count'] = Track.objects.filter(nonstop_zones=zone).count()
334 ctx['tracks'] = Track.objects.filter(nonstop_zones=zone).order_by(
335 'added_to_nonstop_timestamp').select_related()[:30]
338 def post(self, request, *args, **kwargs):
339 assert self.request.user.has_perm('nonstop.add_track')
341 for track_id in request.POST.getlist('track'):
342 if request.POST.get('remove-%s' % track_id):
343 track = Track.objects.get(id=track_id)
344 track.nonstop_zones.clear()
345 track.sync_nonstop_zones()
348 messages.info(self.request, 'Removed %d new track(s)' % count)
349 return HttpResponseRedirect('.')
352 class AddDiffusionView(DetailView):
355 def get(self, request, *args, **kwargs):
356 diffusion = self.get_object()
357 episode = diffusion.episode
359 utils.add_diffusion(diffusion)
360 except utils.DuplicateDiffusionSlot:
361 messages.error(self.request, _('soma slot already in use, the diffusion could not be added.'))
363 messages.info(self.request, _('%s added to soma') % episode.emission.title)
364 return HttpResponseRedirect(reverse('episode-view', kwargs={
365 'emission_slug': episode.emission.slug,
366 'slug': episode.slug}))