[Migration] fix issues with users from Django Auth
[mygpo.git] / mygpo / directory / views.py
blobac8f3092980fc229f512f0fdb0257c8bdcdbcf51
1 from __future__ import division
3 from itertools import imap as map
4 from math import ceil
5 from collections import Counter
7 from django.http import HttpResponseNotFound, Http404, HttpResponseRedirect
8 from django.core.urlresolvers import reverse
9 from django.shortcuts import render
10 from django.db.models import Count
11 from django.contrib.sites.models import RequestSite
12 from django.views.decorators.cache import cache_control
13 from django.views.decorators.vary import vary_on_cookie
14 from django.views.generic import ListView
15 from django.utils.decorators import method_decorator
16 from django.views.generic.base import View, TemplateView
17 from django.contrib.auth.decorators import login_required
18 from django.contrib import messages
19 from django.utils.translation import ugettext as _
20 from django.contrib.auth import get_user_model
22 from feedservice.parse.models import ParserException
23 from feedservice.parse import FetchFeedException
25 from mygpo.core.proxy import proxy_object
26 from mygpo.podcasts.models import Podcast, Episode
27 from mygpo.directory.search import search_podcasts
28 from mygpo.web.utils import process_lang_params, get_language_names, \
29 get_page_list, get_podcast_link_target, sanitize_language_codes
30 from mygpo.directory.tags import Topics
31 from mygpo.users.settings import FLATTR_TOKEN
32 from mygpo.data.feeddownloader import PodcastUpdater, NoEpisodesException
33 from mygpo.data.tasks import update_podcasts
34 from mygpo.db.couchdb.directory import category_for_tag
35 from mygpo.db.couchdb.podcastlist import random_podcastlists, \
36 podcastlist_count, podcastlists_by_rating
39 class ToplistView(TemplateView):
40 """ Generic Top List view """
42 @method_decorator(vary_on_cookie)
43 @method_decorator(cache_control(private=True))
44 def dispatch(self, *args, **kwargs):
45 """ Only used for applying decorators """
46 return super(ToplistView, self).dispatch(*args, **kwargs)
48 def all_languages(self):
49 """ Returns all 2-letter language codes that are used by podcasts.
51 It filters obviously invalid strings, but does not check if any
52 of these codes is contained in ISO 639. """
54 query = Podcast.objects.exclude(language__isnull=True)
55 query = query.distinct('language').values('language')
57 langs = [o['language'] for o in query]
58 langs = sorted(sanitize_language_codes(langs))
60 return get_language_names(langs)
62 def language(self):
63 """ Currently selected language """
64 return process_lang_params(self.request)
66 def site(self):
67 """ Current site for constructing absolute links """
68 return RequestSite(self.request)
71 class PodcastToplistView(ToplistView):
72 """ Most subscribed podcasts """
74 template_name = 'toplist.html'
76 def get_context_data(self, num=100):
77 context = super(PodcastToplistView, self).get_context_data()
79 entries = Podcast.objects.all()\
80 .prefetch_related('slugs')\
81 .toplist(self.language())[:num]
82 context['entries'] = entries
84 context['max_subscribers'] = max([0] + [p.subscriber_count() for p in entries])
86 return context
89 class EpisodeToplistView(ToplistView):
90 """ Most listened-to episodes """
92 template_name = 'episode_toplist.html'
94 def get_context_data(self, num=100):
95 context = super(EpisodeToplistView, self).get_context_data()
97 entries = Episode.objects.all()\
98 .select_related('podcast')\
99 .prefetch_related('slugs', 'podcast__slugs')\
100 .toplist(self.language())[:num]
101 context['entries'] = entries
103 # Determine maximum listener amount (or 0 if no entries exist)
104 context['max_listeners'] = max([0]+[e.listeners for e in entries])
106 return context
109 class Carousel(View):
110 """ A carousel demo """
112 @method_decorator(cache_control(private=True))
113 @method_decorator(vary_on_cookie)
114 def get(self, request):
116 return render(request, 'carousel.html', {
117 # evaluated lazyly, cached by template
118 'topics': Topics(),
122 class Directory(View):
123 """ The main directory page """
125 @method_decorator(cache_control(private=True))
126 @method_decorator(vary_on_cookie)
127 def get(self, request):
129 return render(request, 'directory.html', {
131 # evaluated lazyly, cached by template
132 'topics': Topics(),
133 'podcastlists': self.get_random_list(),
134 'random_podcast': Podcast.objects.all().random().first(),
138 def get_random_list(self, podcasts_per_list=5):
139 User = get_user_model()
140 random_list = next(random_podcastlists(), None)
141 list_owner = None
142 if random_list:
143 podcast_ids = random_list.podcasts[:podcasts_per_list]
144 random_list = proxy_object(random_list)
145 random_list.more_podcasts = max(0, len(random_list.podcasts) - podcasts_per_list)
146 random_list.podcasts = Podcast.objects.filter(id__in=podcast_ids)
148 try:
149 random_list.user = User.objects.get(profile__uuid=random_list.user)
150 except User.DoesNotExist:
151 return
153 yield random_list
156 @cache_control(private=True)
157 @vary_on_cookie
158 def category(request, category, page_size=20):
159 category = category_for_tag(category)
160 if not category:
161 return HttpResponseNotFound()
163 # Make sure page request is an int. If not, deliver first page.
164 try:
165 page = int(request.GET.get('page', '1'))
166 except ValueError:
167 page = 1
169 entries = category.get_podcasts( (page-1) * page_size, page*page_size )
170 podcasts = filter(None, entries)
171 num_pages = int(ceil(len(category.podcasts) / page_size))
173 page_list = get_page_list(1, num_pages, page, 15)
175 return render(request, 'category.html', {
176 'entries': podcasts,
177 'category': category.label,
178 'page_list': page_list,
183 RESULTS_PER_PAGE=20
185 @cache_control(private=True)
186 @vary_on_cookie
187 def search(request, template='search.html', args={}):
189 if 'q' in request.GET:
190 q = request.GET.get('q', '').encode('utf-8')
192 try:
193 page = int(request.GET.get('page', 1))
194 except ValueError:
195 page = 1
197 start = RESULTS_PER_PAGE*(page-1)
198 results = search_podcasts(q)
199 total = len(results)
200 num_pages = int(ceil(total / RESULTS_PER_PAGE))
201 results = results[start:start+RESULTS_PER_PAGE]
203 page_list = get_page_list(1, num_pages, page, 15)
205 else:
206 results = []
207 q = None
208 page_list = []
210 max_subscribers = max([p.subscribers for p in results] + [0])
212 current_site = RequestSite(request)
214 return render(request, template, dict(
215 q= q,
216 results= results,
217 page_list= page_list,
218 max_subscribers= max_subscribers,
219 domain= current_site.domain,
220 **args
225 @cache_control(private=True)
226 @vary_on_cookie
227 def podcast_lists(request, page_size=20):
229 # Make sure page request is an int. If not, deliver first page.
230 try:
231 page = int(request.GET.get('page', '1'))
232 except ValueError:
233 page = 1
235 lists = podcastlists_by_rating(skip=(page-1) * page_size, limit=page_size)
238 def _prepare_list(l):
239 User = get_user_model()
240 try:
241 user = User.objects.get(profile__uuid=l.user)
242 except User.DoesNotExist:
243 return None
245 l = proxy_object(l)
246 l.username = user.username if user else ''
247 return l
249 lists = filter(None, map(_prepare_list, lists))
251 num_pages = int(ceil(podcastlist_count() / float(page_size)))
253 page_list = get_page_list(1, num_pages, page, 15)
255 return render(request, 'podcast_lists.html', {
256 'lists': lists,
257 'page_list': page_list,
262 class MissingPodcast(View):
263 """ Check if a podcast is missing """
265 @method_decorator(login_required)
266 def get(self, request):
268 site = RequestSite(request)
270 # check if we're doing a query
271 url = request.GET.get('q', None)
273 if not url:
274 podcast = None
275 can_add = False
277 else:
278 try:
279 podcast = Podcast.objects.get(urls__url=url)
280 can_add = False
282 except Podcast.DoesNotExist:
283 # check if we could add a podcast for the given URL
284 podcast = False
285 updater = PodcastUpdater()
287 try:
288 can_add = updater.verify_podcast_url(url)
290 except (ParserException, FetchFeedException,
291 NoEpisodesException) as ex:
292 can_add = False
293 messages.error(request, unicode(ex))
295 return render(request, 'missing.html', {
296 'site': site,
297 'q': url,
298 'podcast': podcast,
299 'can_add': can_add,
303 class AddPodcast(View):
304 """ Add a missing podcast"""
306 @method_decorator(login_required)
307 @method_decorator(cache_control(private=True))
308 @method_decorator(vary_on_cookie)
309 def post(self, request):
311 url = request.POST.get('url', None)
313 if not url:
314 raise Http404
316 res = update_podcasts.delay([url])
318 return HttpResponseRedirect(reverse('add-podcast-status',
319 args=[res.task_id]))
322 class AddPodcastStatus(TemplateView):
323 """ Status of adding a podcast """
325 template_name = 'directory/add-podcast-status.html'
327 def get(self, request, task_id):
328 result = update_podcasts.AsyncResult(task_id)
330 if not result.ready():
331 return self.render_to_response({
332 'ready': False,
335 try:
336 podcasts = result.get()
337 messages.success(request, _('%d podcasts added' % len(podcasts)))
339 except (ParserException, FetchFeedException,
340 NoEpisodesException) as ex:
341 messages.error(request, str(ex))
342 podcast = None
344 return self.render_to_response({
345 'ready': True,
346 'podcasts': podcasts,
350 class PodcastListView(ListView):
351 """ A generic podcast list view """
353 paginate_by = 15
354 context_object_name = 'podcasts'
356 @method_decorator(vary_on_cookie)
357 @method_decorator(cache_control(private=True))
358 def dispatch(self, *args, **kwargs):
359 """ Only used for applying decorators """
360 return super(PodcastListView, self).dispatch(*args, **kwargs)
362 @property
363 def _page(self):
364 """ The current page
366 There seems to be no other pre-defined method for getting the current
367 page, see
368 https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/#multipleobjectmixin
370 return self.get_context_data()['page_obj']
372 def page_list(self, page_size=15):
373 """ Return a list of pages, eg [1, 2, 3, '...', 6, 7, 8] """
374 page = self._page
375 return get_page_list(1,
376 page.paginator.num_pages,
377 page.number,
378 page.paginator.per_page,
381 def max_subscribers(self):
382 """ Maximum subscribers of the podcasts on this page """
383 page = self._page
384 podcasts = page.object_list
385 return max([p.subscriber_count() for p in podcasts] + [0])
388 class FlattrPodcastList(PodcastListView):
389 """ Lists podcasts that have Flattr payment URLs """
391 template_name = 'flattr-podcasts.html'
393 def get_queryset(self):
394 return Podcast.objects.all().flattr()
396 def get_context_data(self, num=100):
397 context = super(FlattrPodcastList, self).get_context_data()
398 context['flattr_auth'] = (self.request.user.is_authenticated()
399 # and bool(self.request.user.get_wksetting(FLATTR_TOKEN))
401 return context
404 class LicensePodcastList(PodcastListView):
405 """ Lists podcasts with a given license """
407 template_name = 'directory/license-podcasts.html'
409 def get_queryset(self):
410 return Podcast.objects.all().license(self.license_url)
412 @property
413 def license_url(self):
414 return self.kwargs['license_url']
417 class LicenseList(TemplateView):
418 """ Lists all podcast licenses """
420 template_name = 'directory/licenses.html'
422 def licenses(self):
423 """ Returns all podcast licenses """
424 query = Podcast.objects.exclude(license__isnull=True)
425 values = query.values("license").annotate(Count("id")).order_by()
427 counter = Counter({l['license']: l['id__count'] for l in values})
428 return counter.most_common()