94b0db0b31cb7ae57c384269319280ba9e002333
[mygpo.git] / mygpo / admin / views.py
blob94b0db0b31cb7ae57c384269319280ba9e002333
1 import re
2 import socket
3 from itertools import count
4 from collections import Counter
6 import django
7 from django.shortcuts import render
8 from django.contrib import messages
9 from django.core.urlresolvers import reverse
10 from django.core.cache import cache
11 from django.http import HttpResponseRedirect
12 from django.utils.translation import ugettext as _
13 from django.views.generic import TemplateView
14 from django.utils.decorators import method_decorator
15 from django.conf import settings
17 from mygpo.admin.auth import require_staff
18 from mygpo.admin.group import PodcastGrouper
19 from mygpo.maintenance.merge import PodcastMerger, IncorrectMergeException
20 from mygpo.users.models import User
21 from mygpo.admin.clients import UserAgentStats, ClientStats
22 from mygpo.admin.tasks import merge_podcasts
23 from mygpo.utils import get_git_head
24 from mygpo.api.httpresponse import JsonResponse
25 from mygpo.cel import celery
26 from mygpo.db.couchdb import get_main_database
27 from mygpo.db.couchdb.episode import episode_count, filetype_stats
28 from mygpo.db.couchdb.podcast import podcast_count, podcast_for_url
31 class InvalidPodcast(Exception):
32 """ raised when we try to merge a podcast that doesn't exist """
34 class AdminView(TemplateView):
36 @method_decorator(require_staff)
37 def dispatch(self, *args, **kwargs):
38 return super(AdminView, self).dispatch(*args, **kwargs)
41 class Overview(AdminView):
42 template_name = 'admin/overview.html'
45 class HostInfo(AdminView):
46 """ shows host information for diagnosis """
48 template_name = 'admin/hostinfo.html'
50 def get(self, request):
51 commit, msg = get_git_head()
52 base_dir = settings.BASE_DIR
53 hostname = socket.gethostname()
54 django_version = django.VERSION
56 main_db = get_main_database()
58 db_tasks = main_db.server.active_tasks()
60 i = celery.control.inspect()
61 num_celery_tasks = len(i.scheduled() or [])
63 return self.render_to_response({
64 'git_commit': commit,
65 'git_msg': msg,
66 'base_dir': base_dir,
67 'hostname': hostname,
68 'django_version': django_version,
69 'main_db': main_db.uri,
70 'db_tasks': db_tasks,
71 'num_celery_tasks': num_celery_tasks,
76 class MergeSelect(AdminView):
77 template_name = 'admin/merge-select.html'
79 def get(self, request):
80 num = int(request.GET.get('podcasts', 2))
81 urls = [''] * num
83 return self.render_to_response({
84 'urls': urls,
88 class MergeBase(AdminView):
90 def _get_podcasts(self, request):
91 podcasts = []
92 for n in count():
93 podcast_url = request.POST.get('feed%d' % n, None)
94 if podcast_url is None:
95 break
97 if not podcast_url:
98 continue
100 podcast = podcast_for_url(podcast_url)
102 if not podcast:
103 raise InvalidPodcast(podcast_url)
105 podcasts.append(podcast_for_url(podcast_url))
107 return podcasts
110 class MergeVerify(MergeBase):
112 template_name = 'admin/merge-grouping.html'
114 def post(self, request):
116 try:
117 podcasts = self._get_podcasts(request)
119 except InvalidPodcast as ip:
120 messages.error(request,
121 _('No podcast with URL {url}').format(url=str(ip)))
123 grouper = PodcastGrouper(podcasts)
125 get_features = lambda (e_id, e): ((e.url, e.title), e_id)
127 num_groups = grouper.group(get_features)
129 return self.render_to_response({
130 'podcasts': podcasts,
131 'groups': num_groups,
135 class MergeProcess(MergeBase):
137 RE_EPISODE = re.compile(r'episode_([0-9a-fA-F]{32})')
139 def post(self, request):
141 try:
142 podcasts = self._get_podcasts(request)
144 except InvalidPodcast as ip:
145 messages.error(request,
146 _('No podcast with URL {url}').format(url=str(ip)))
148 grouper = PodcastGrouper(podcasts)
150 features = {}
151 for key, feature in request.POST.items():
152 m = self.RE_EPISODE.match(key)
153 if m:
154 episode_id = m.group(1)
155 features[episode_id] = feature
157 get_features = lambda (e_id, e): (features[e_id], e_id)
159 num_groups = grouper.group(get_features)
161 if 'renew' in request.POST:
162 return render(request, 'admin/merge-grouping.html', {
163 'podcasts': podcasts,
164 'groups': num_groups,
168 elif 'merge' in request.POST:
170 podcast_ids = [p.get_id() for p in podcasts]
171 num_groups = list(num_groups)
173 res = merge_podcasts.delay(podcast_ids, num_groups)
175 return HttpResponseRedirect(reverse('admin-merge-status',
176 args=[res.task_id]))
179 class MergeStatus(AdminView):
180 """ Displays the status of the merge operation """
182 template_name = 'admin/merge-status.html'
184 def get(self, request, task_id):
185 result = merge_podcasts.AsyncResult(task_id)
187 if not result.ready():
188 return self.render_to_response({
189 'ready': False,
192 # clear cache to make merge result visible
193 # TODO: what to do with multiple frontends?
194 cache.clear()
196 try:
197 actions, podcast = result.get()
199 except IncorrectMergeException as ime:
200 messages.error(request, str(ime))
201 return HttpResponseRedirect(reverse('admin-merge'))
203 return render(request, 'admin/merge-status.html', {
204 'ready': True,
205 'actions': actions.items(),
206 'podcast': podcast,
211 class UserAgentStatsView(AdminView):
212 template_name = 'admin/useragents.html'
214 def get(self, request):
216 uas = UserAgentStats()
217 useragents = uas.get_entries()
219 return self.render_to_response({
220 'useragents': useragents.most_common(),
221 'max_users': uas.max_users,
222 'total': uas.total_users,
226 class ClientStatsView(AdminView):
227 template_name = 'admin/clients.html'
229 def get(self, request):
231 cs = ClientStats()
232 clients = cs.get_entries()
234 return self.render_to_response({
235 'clients': clients.most_common(),
236 'max_users': cs.max_users,
237 'total': cs.total_users,
241 class ClientStatsJsonView(AdminView):
242 def get(self, request):
244 cs = ClientStats()
245 clients = cs.get_entries()
247 return JsonResponse(map(self.to_dict, clients.most_common()))
249 def to_dict(self, res):
250 obj, count = res
252 if not isinstance(obj, tuple):
253 return obj, count
255 return obj._asdict(), count
258 class StatsView(AdminView):
259 """ shows general stats as HTML page """
261 template_name = 'admin/stats.html'
263 def _get_stats(self):
264 return {
265 'podcasts': podcast_count(),
266 'episodes': episode_count(),
267 'users': User.count(),
270 def get(self, request):
271 stats = self._get_stats()
272 return self.render_to_response({
273 'stats': stats,
277 class StatsJsonView(StatsView):
278 """ provides general stats as JSON """
280 def get(self, request):
281 stats = self._get_stats()
282 return JsonResponse(stats)
285 class FiletypeStatsView(AdminView):
287 template_name = 'admin/filetypes.html'
289 def get(self, request):
290 stats = filetype_stats()
292 if len(stats):
293 max_num = stats.most_common(1)[0][1]
294 else:
295 max_num = 0
297 return self.render_to_response({
298 'max_num': max_num,
299 'stats': stats.most_common(),