import copy import csv import datetime from cStringIO import StringIO import os import tempfile import mutagen from django.core.files.storage import default_storage from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.urlresolvers import reverse from django.contrib import messages from django.db.models import Q from django.http import HttpResponse, HttpResponseRedirect from django.utils.translation import ugettext_lazy as _ from django.views.generic.base import RedirectView, TemplateView from django.views.generic.dates import DayArchiveView from django.views.generic.detail import DetailView from django.views.generic.edit import FormView from django.views.generic.list import ListView from .forms import UploadTracksForm, TrackMetaForm, TrackSearchForm, CleanupForm from .models import SomaLogLine, Track, Artist, NonstopFile from emissions.models import Nonstop class SomaDayArchiveView(DayArchiveView): queryset = SomaLogLine.objects.all() date_field = "play_timestamp" make_object_list = True allow_future = False month_format = '%m' class SomaDayArchiveCsvView(SomaDayArchiveView): def render_to_response(self, context, **response_kwargs): out = StringIO() writer = csv.writer(out) for line in context['object_list']: if line.filepath.track: writer.writerow([line.play_timestamp.strftime('%Y-%m-%d %H:%M'), line.filepath.short.encode('utf-8', 'replace'), line.filepath.track.title.encode('utf-8', 'replace'), line.filepath.track.artist.name.encode('utf-8', 'replace'), line.filepath.track.language, line.filepath.track.instru and 'instru' or '', line.filepath.track.cfwb and 'cfwb' or '']) else: writer.writerow([line.play_timestamp.strftime('%Y-%m-%d %H:%M'), line.filepath.short.encode('utf-8', 'replace')]) return HttpResponse(out.getvalue(), content_type='text/csv; charset=utf-8') class RedirectTodayView(RedirectView): def get_redirect_url(self, *args, **kwargs): today = datetime.datetime.today() return reverse('archive_day', kwargs={ 'year': today.year, 'month': today.month, 'day': today.day}) class TrackDetailView(DetailView): model = Track def get_context_data(self, **kwargs): ctx = super(TrackDetailView, self).get_context_data(**kwargs) ctx['metadata_form'] = TrackMetaForm(instance=self.object) return ctx def post(self, request, *args, **kwargs): assert self.request.user.has_perm('nonstop.add_track') instance = self.get_object() old_nonstop_zones = copy.copy(instance.nonstop_zones.all()) form = TrackMetaForm(request.POST, instance=instance) form.save() new_nonstop_zones = self.get_object().nonstop_zones.all() if set(old_nonstop_zones) != set(new_nonstop_zones): instance.sync_nonstop_zones() return HttpResponseRedirect('.') class ArtistDetailView(DetailView): model = Artist class ArtistListView(ListView): model = Artist class ZoneStats(object): def __init__(self, zone, from_date=None, until_date=None, **kwargs): self.zone = zone self.qs = Track.objects.filter(nonstop_zones=self.zone, **kwargs) self.from_date = from_date if from_date: self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__gte=from_date) if until_date: self.qs = self.qs.filter(nonstopfile__somalogline__play_timestamp__lte=until_date) self.qs = self.qs.distinct() def count(self, **kwargs): return self.qs.filter(**kwargs).count() def percentage(self, **kwargs): total = self.count() if total == 0: return '-' return '%.2f%%' % (100. * self.count(**kwargs) / total) def instru(self): return self.count(instru=True) def instru_percentage(self): return self.percentage(instru=True) def sabam(self): return self.count(sabam=True) def sabam_percentage(self): return self.percentage(sabam=True) def cfwb(self): return self.count(cfwb=True) def cfwb_percentage(self): return self.percentage(cfwb=True) def french(self): return self.count(language='fr') def french_percentage(self): considered_tracks = self.count() - self.instru() if considered_tracks == 0: return '-' return '%.2f%%' % (100. * self.french() / considered_tracks) def new_files(self): return self.count(nonstopfile__creation_timestamp__gte=self.from_date) def percent_new_files(self): return self.percentage(nonstopfile__creation_timestamp__gte=self.from_date) def parse_date(date): if date.endswith('d'): return datetime.datetime.today() + datetime.timedelta(int(date.rstrip('d'))) return datetime.datetime.strptime(date, '%Y-%m-%d').date() class StatisticsView(TemplateView): template_name = 'nonstop/statistics.html' def get_context_data(self, **kwargs): context = super(StatisticsView, self).get_context_data(**kwargs) context['zones'] = Nonstop.objects.all().order_by('start') kwargs = {} if 'from' in self.request.GET: kwargs['from_date'] = parse_date(self.request.GET['from']) context['from_date'] = kwargs['from_date'] if 'until' in self.request.GET: kwargs['until_date'] = parse_date(self.request.GET['until']) if 'onair' in self.request.GET: kwargs['nonstopfile__somalogline__on_air'] = True for zone in context['zones']: zone.stats = ZoneStats(zone, **kwargs) return context class UploadTracksView(FormView): form_class = UploadTracksForm template_name = 'nonstop/upload.html' success_url = '.' def post(self, request, *args, **kwargs): assert self.request.user.has_perm('nonstop.add_track') form_class = self.get_form_class() form = self.get_form(form_class) tracks = request.FILES.getlist('tracks') if not form.is_valid(): return self.form_invalid(form) missing_metadata = [] metadatas = {} for f in tracks: with tempfile.NamedTemporaryFile(prefix='track-upload') as tmpfile: tmpfile.write(f.read()) f.seek(0) metadata = mutagen.File(tmpfile.name, easy=True) if not metadata or not metadata.get('artist') or not metadata.get('title'): missing_metadata.append(f.name) else: metadatas[f.name] = metadata if missing_metadata: form.add_error('tracks', _('Missing metadata in: ') + ', '.join(missing_metadata)) return self.form_invalid(form) for f in tracks: metadata = metadatas[f.name] artist_name = metadata.get('artist')[0] track_title = metadata.get('title')[0] monthdir = datetime.datetime.today().strftime('%Y-%m') filepath = u'%s/%s - %s - %s%s' % (monthdir, datetime.datetime.today().strftime('%y%m%d'), artist_name[:50].replace('/', ' ').strip(), track_title[:80].replace('/', ' ').strip(), os.path.splitext(f.name)[-1]).encode('utf-8') default_storage.save(os.path.join('nonstop', 'tracks', filepath), content=f) nonstop_file = NonstopFile() nonstop_file.set_track_filepath(filepath) artist, created = Artist.objects.get_or_create(name=artist_name) track, created = Track.objects.get_or_create(title=track_title, artist=artist, defaults={'uploader': self.request.user}) nonstop_file.track = track nonstop_file.save() if request.POST.get('nonstop_zone'): track.nonstop_zones.add( Nonstop.objects.get(id=request.POST.get('nonstop_zone'))) nonstop_file.track.sync_nonstop_zones() messages.info(self.request, '%d new track(s)' % len(tracks)) return self.form_valid(form) class RecentTracksView(ListView): template_name = 'nonstop/recent_tracks.html' def get_queryset(self): return Track.objects.exclude(creation_timestamp__isnull=True).order_by('-creation_timestamp')[:50] def post(self, request, *args, **kwargs): assert self.request.user.has_perm('nonstop.add_track') for track_id in request.POST.getlist('track'): track = Track.objects.get(id=track_id) track.language = request.POST.get('lang-%s' % track_id, '') track.instru = 'instru-%s' % track_id in request.POST track.sabam = 'sabam-%s' % track_id in request.POST track.cfwb = 'cfwb-%s' % track_id in request.POST track.save() return HttpResponseRedirect('.') class QuickLinksView(TemplateView): template_name = 'nonstop/quick_links.html' class SearchView(TemplateView): template_name = 'nonstop/search.html' def get_queryset(self): queryset = Track.objects.all() q = self.request.GET.get('q') if q: queryset = queryset.filter(Q(title__icontains=q.lower()) | Q(artist__name__icontains=q.lower())) zone = self.request.GET.get('zone') if zone: from emissions.models import Nonstop if zone == 'none': queryset = queryset.filter(nonstop_zones=None) elif zone == 'any': queryset = queryset.filter(nonstop_zones__isnull=False).distinct() else: queryset = queryset.filter(nonstop_zones=zone) order = self.request.GET.get('order_by') or 'title' if order: if 'added_to_nonstop_timestamp' in order: queryset = queryset.filter(added_to_nonstop_timestamp__isnull=False) queryset = queryset.order_by(order) return queryset def get_context_data(self, **kwargs): ctx = super(SearchView, self).get_context_data(**kwargs) ctx['form'] = TrackSearchForm(self.request.GET) queryset = self.get_queryset() qs = self.request.GET.copy() qs.pop('page', None) ctx['qs'] = qs.urlencode() tracks = Paginator(queryset.select_related(), 20) page = self.request.GET.get('page') try: ctx['tracks'] = tracks.page(page) except PageNotAnInteger: ctx['tracks'] = tracks.page(1) except EmptyPage: ctx['tracks'] = tracks.page(tracks.num_pages) return ctx class SearchCsvView(SearchView): def get(self, request, *args, **kwargs): out = StringIO() writer = csv.writer(out) writer.writerow(['Title', 'Artist', 'Zones', 'Language', 'Instru', 'CFWB']) for track in self.get_queryset(): writer.writerow([ track.title.encode('utf-8', 'replace') if track.title else 'Inconnu', track.artist.name.encode('utf-8', 'replace') if (track.artist and track.artist.name) else 'Inconnu', ' + '.join([x.title.encode('utf-8') for x in track.nonstop_zones.all()]), track.language or '', track.instru and 'instru' or '', track.cfwb and 'cfwb' or '']) return HttpResponse(out.getvalue(), content_type='text/csv; charset=utf-8') class CleanupView(TemplateView): template_name = 'nonstop/cleanup.html' def get_context_data(self, **kwargs): ctx = super(CleanupView, self).get_context_data(**kwargs) ctx['form'] = CleanupForm() zone = self.request.GET.get('zone') if zone: from emissions.models import Nonstop ctx['zone'] = Nonstop.objects.get(id=zone) ctx['count'] = Track.objects.filter(nonstop_zones=zone).count() ctx['tracks'] = Track.objects.filter(nonstop_zones=zone).order_by( 'added_to_nonstop_timestamp').select_related()[:30] return ctx def post(self, request, *args, **kwargs): assert self.request.user.has_perm('nonstop.add_track') count = 0 for track_id in request.POST.getlist('track'): if request.POST.get('remove-%s' % track_id): track = Track.objects.get(id=track_id) track.nonstop_zones.clear() track.sync_nonstop_zones() count += 1 if count: messages.info(self.request, 'Removed %d new track(s)' % count) return HttpResponseRedirect('.')