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_tracks')
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_tracks')
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_tracks')
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_context_data(self, **kwargs):
252 ctx = super(SearchView, self).get_context_data(**kwargs)
253 ctx['form'] = TrackSearchForm(self.request.GET)
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=zone)
268 qs = self.request.GET.copy()
270 ctx['qs'] = qs.urlencode()
272 order = self.request.GET.get('order_by') or 'title'
274 if 'added_to_nonstop_timestamp' in order:
275 queryset = queryset.filter(added_to_nonstop_timestamp__isnull=False)
276 queryset = queryset.order_by(order)
278 tracks = Paginator(queryset.select_related(), 20)
280 page = self.request.GET.get('page')
282 ctx['tracks'] = tracks.page(page)
283 except PageNotAnInteger:
284 ctx['tracks'] = tracks.page(1)
286 ctx['tracks'] = tracks.page(tracks.num_pages)
291 class CleanupView(TemplateView):
292 template_name = 'nonstop/cleanup.html'
294 def get_context_data(self, **kwargs):
295 ctx = super(CleanupView, self).get_context_data(**kwargs)
296 ctx['form'] = CleanupForm()
298 zone = self.request.GET.get('zone')
300 from emissions.models import Nonstop
301 ctx['zone'] = Nonstop.objects.get(id=zone)
302 ctx['count'] = Track.objects.filter(nonstop_zones=zone).count()
303 ctx['tracks'] = Track.objects.filter(nonstop_zones=zone).order_by(
304 'added_to_nonstop_timestamp').select_related()[:30]
307 def post(self, request, *args, **kwargs):
308 assert self.request.user.has_perm('nonstop.add_tracks')
310 for track_id in request.POST.getlist('track'):
311 if request.POST.get('remove-%s' % track_id):
312 track = Track.objects.get(id=track_id)
313 track.nonstop_zones.clear()
314 track.sync_nonstop_zones()
317 messages.info(self.request, 'Removed %d new track(s)' % count)
318 return HttpResponseRedirect('.')