remove backported Counter class
[mygpo.git] / mygpo / admin / views.py
blobb74ba2fab81155eb532f95cb48f39801ea80b090
1 import re
2 from itertools import count
3 from collections import Counter
5 from django.shortcuts import render
6 from django.contrib import messages
7 from django.core.urlresolvers import reverse
8 from django.http import HttpResponseRedirect
9 from django.utils.translation import ugettext as _
10 from django.views.generic import TemplateView
11 from django.utils.decorators import method_decorator
13 from mygpo.admin.auth import require_staff
14 from mygpo.admin.group import PodcastGrouper
15 from mygpo.maintenance.merge import PodcastMerger, IncorrectMergeException
16 from mygpo.users.models import User
17 from mygpo.admin.clients import UserAgentStats, ClientStats
18 from mygpo.admin.tasks import merge_podcasts
19 from mygpo.api.httpresponse import JsonResponse
20 from mygpo.db.couchdb.episode import episode_count
21 from mygpo.db.couchdb.podcast import podcast_count, podcast_for_url
24 class InvalidPodcast(Exception):
25 """ raised when we try to merge a podcast that doesn't exist """
27 class AdminView(TemplateView):
29 @method_decorator(require_staff)
30 def dispatch(self, *args, **kwargs):
31 return super(AdminView, self).dispatch(*args, **kwargs)
34 class Overview(AdminView):
35 template_name = 'admin/overview.html'
38 class MergeSelect(AdminView):
39 template_name = 'admin/merge-select.html'
41 def get(self, request):
42 num = int(request.GET.get('podcasts', 2))
43 urls = [''] * num
45 return self.render_to_response({
46 'urls': urls,
50 class MergeBase(AdminView):
52 def _get_podcasts(self, request):
53 podcasts = []
54 for n in count():
55 podcast_url = request.POST.get('feed%d' % n, None)
56 if podcast_url is None:
57 break
59 if not podcast_url:
60 continue
62 podcast = podcast_for_url(podcast_url)
64 if not podcast:
65 raise InvalidPodcast(podcast_url)
67 podcasts.append(podcast_for_url(podcast_url))
69 return podcasts
72 class MergeVerify(MergeBase):
74 template_name = 'admin/merge-grouping.html'
76 def post(self, request):
78 try:
79 podcasts = self._get_podcasts(request)
81 except InvalidPodcast as ip:
82 messages.error(request,
83 _('No podcast with URL {url}').format(url=str(ip)))
85 grouper = PodcastGrouper(podcasts)
87 get_features = lambda (e_id, e): ((e.url, e.title), e_id)
89 num_groups = grouper.group(get_features)
91 return self.render_to_response({
92 'podcasts': podcasts,
93 'groups': num_groups,
97 class MergeProcess(MergeBase):
99 RE_EPISODE = re.compile(r'episode_([0-9a-fA-F]{32})')
101 def post(self, request):
103 try:
104 podcasts = self._get_podcasts(request)
106 except InvalidPodcast as ip:
107 messages.error(request,
108 _('No podcast with URL {url}').format(url=str(ip)))
110 grouper = PodcastGrouper(podcasts)
112 features = {}
113 for key, feature in request.POST.items():
114 m = self.RE_EPISODE.match(key)
115 if m:
116 episode_id = m.group(1)
117 features[episode_id] = feature
119 get_features = lambda (e_id, e): (features[e_id], e_id)
121 num_groups = grouper.group(get_features)
123 if 'renew' in request.POST:
124 return render(request, 'admin/merge-grouping.html', {
125 'podcasts': podcasts,
126 'groups': num_groups,
130 elif 'merge' in request.POST:
132 podcast_ids = [p.get_id() for p in podcasts]
133 num_groups = list(num_groups)
135 res = merge_podcasts.delay(podcast_ids, num_groups)
137 return HttpResponseRedirect(reverse('admin-merge-status',
138 args=[res.task_id]))
141 class MergeStatus(AdminView):
142 """ Displays the status of the merge operation """
144 template_name = 'admin/merge-status.html'
146 def get(self, request, task_id):
147 result = merge_podcasts.AsyncResult(task_id)
149 if not result.ready():
150 return self.render_to_response({
151 'ready': False,
155 try:
156 actions, podcast = result.get()
158 except IncorrectMergeException as ime:
159 messages.error(request, str(ime))
160 return HttpResponseRedirect(reverse('admin-merge'))
162 return render(request, 'admin/merge-status.html', {
163 'ready': True,
164 'actions': actions.items(),
165 'podcast': podcast,
170 class UserAgentStatsView(AdminView):
171 template_name = 'admin/useragents.html'
173 def get(self, request):
175 uas = UserAgentStats()
176 useragents = uas.get_entries()
178 return self.render_to_response({
179 'useragents': useragents.most_common(),
180 'max_users': uas.max_users,
181 'total': uas.total_users,
185 class ClientStatsView(AdminView):
186 template_name = 'admin/clients.html'
188 def get(self, request):
190 cs = ClientStats()
191 clients = cs.get_entries()
193 return self.render_to_response({
194 'clients': clients.most_common(),
195 'max_users': cs.max_users,
196 'total': cs.total_users,
200 class ClientStatsJsonView(AdminView):
201 def get(self, request):
203 cs = ClientStats()
204 clients = cs.get_entries()
206 return JsonResponse(map(self.to_dict, clients.most_common()))
208 def to_dict(self, res):
209 obj, count = res
211 if not isinstance(obj, tuple):
212 return obj, count
214 return obj._asdict(), count
217 class StatsView(AdminView):
218 """ shows general stats as HTML page """
220 template_name = 'admin/stats.html'
222 def _get_stats(self):
223 return {
224 'podcasts': podcast_count(),
225 'episodes': episode_count(),
226 'users': User.count(),
229 def get(self, request):
230 stats = self._get_stats()
231 return self.render_to_response({
232 'stats': stats,
236 class StatsJsonView(StatsView):
237 """ provides general stats as JSON """
239 def get(self, request):
240 stats = self._get_stats()
241 return JsonResponse(stats)