fix access to undefiend variable
[mygpo.git] / mygpo / admin / views.py
blobe9822542fc8890672a81ad04ee7d32fe0366556c
1 import re
2 import socket
3 from itertools import count, chain
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, unify_slugs
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.user import activate_user
28 from mygpo.db.couchdb.episode import episode_count, filetype_stats
29 from mygpo.db.couchdb.podcast import podcast_count, podcast_for_url
32 class InvalidPodcast(Exception):
33 """ raised when we try to merge a podcast that doesn't exist """
35 class AdminView(TemplateView):
37 @method_decorator(require_staff)
38 def dispatch(self, *args, **kwargs):
39 return super(AdminView, self).dispatch(*args, **kwargs)
42 class Overview(AdminView):
43 template_name = 'admin/overview.html'
46 class HostInfo(AdminView):
47 """ shows host information for diagnosis """
49 template_name = 'admin/hostinfo.html'
51 def get(self, request):
52 commit, msg = get_git_head()
53 base_dir = settings.BASE_DIR
54 hostname = socket.gethostname()
55 django_version = django.VERSION
57 main_db = get_main_database()
59 db_tasks = main_db.server.active_tasks()
61 i = celery.control.inspect()
62 scheduled = i.scheduled()
63 if not scheduled:
64 num_celery_tasks = None
65 else:
66 num_celery_tasks = sum(len(node) for node in scheduled.values())
68 return self.render_to_response({
69 'git_commit': commit,
70 'git_msg': msg,
71 'base_dir': base_dir,
72 'hostname': hostname,
73 'django_version': django_version,
74 'main_db': main_db.uri,
75 'db_tasks': db_tasks,
76 'num_celery_tasks': num_celery_tasks,
81 class MergeSelect(AdminView):
82 template_name = 'admin/merge-select.html'
84 def get(self, request):
85 num = int(request.GET.get('podcasts', 2))
86 urls = [''] * num
88 return self.render_to_response({
89 'urls': urls,
93 class MergeBase(AdminView):
95 def _get_podcasts(self, request):
96 podcasts = []
97 for n in count():
98 podcast_url = request.POST.get('feed%d' % n, None)
99 if podcast_url is None:
100 break
102 if not podcast_url:
103 continue
105 podcast = podcast_for_url(podcast_url)
107 if not podcast:
108 raise InvalidPodcast(podcast_url)
110 podcasts.append(podcast_for_url(podcast_url))
112 return podcasts
115 class MergeVerify(MergeBase):
117 template_name = 'admin/merge-grouping.html'
119 def post(self, request):
121 try:
122 podcasts = self._get_podcasts(request)
124 grouper = PodcastGrouper(podcasts)
126 get_features = lambda (e_id, e): ((e.url, e.title), e_id)
128 num_groups = grouper.group(get_features)
131 except InvalidPodcast as ip:
132 messages.error(request,
133 _('No podcast with URL {url}').format(url=str(ip)))
135 podcasts = []
136 num_groups = []
138 return self.render_to_response({
139 'podcasts': podcasts,
140 'groups': num_groups,
144 class MergeProcess(MergeBase):
146 RE_EPISODE = re.compile(r'episode_([0-9a-fA-F]{32})')
148 def post(self, request):
150 try:
151 podcasts = self._get_podcasts(request)
153 except InvalidPodcast as ip:
154 messages.error(request,
155 _('No podcast with URL {url}').format(url=str(ip)))
157 grouper = PodcastGrouper(podcasts)
159 features = {}
160 for key, feature in request.POST.items():
161 m = self.RE_EPISODE.match(key)
162 if m:
163 episode_id = m.group(1)
164 features[episode_id] = feature
166 get_features = lambda (e_id, e): (features[e_id], e_id)
168 num_groups = grouper.group(get_features)
170 if 'renew' in request.POST:
171 return render(request, 'admin/merge-grouping.html', {
172 'podcasts': podcasts,
173 'groups': num_groups,
177 elif 'merge' in request.POST:
179 podcast_ids = [p.get_id() for p in podcasts]
180 num_groups = list(num_groups)
182 res = merge_podcasts.delay(podcast_ids, num_groups)
184 return HttpResponseRedirect(reverse('admin-merge-status',
185 args=[res.task_id]))
188 class MergeStatus(AdminView):
189 """ Displays the status of the merge operation """
191 template_name = 'admin/task-status.html'
193 def get(self, request, task_id):
194 result = merge_podcasts.AsyncResult(task_id)
196 if not result.ready():
197 return self.render_to_response({
198 'ready': False,
201 # clear cache to make merge result visible
202 # TODO: what to do with multiple frontends?
203 cache.clear()
205 try:
206 actions, podcast = result.get()
208 except IncorrectMergeException as ime:
209 messages.error(request, str(ime))
210 return HttpResponseRedirect(reverse('admin-merge'))
212 return self.render_to_response({
213 'ready': True,
214 'actions': actions.items(),
215 'podcast': podcast,
220 class UserAgentStatsView(AdminView):
221 template_name = 'admin/useragents.html'
223 def get(self, request):
225 uas = UserAgentStats()
226 useragents = uas.get_entries()
228 return self.render_to_response({
229 'useragents': useragents.most_common(),
230 'max_users': uas.max_users,
231 'total': uas.total_users,
235 class ClientStatsView(AdminView):
236 template_name = 'admin/clients.html'
238 def get(self, request):
240 cs = ClientStats()
241 clients = cs.get_entries()
243 return self.render_to_response({
244 'clients': clients.most_common(),
245 'max_users': cs.max_users,
246 'total': cs.total_users,
250 class ClientStatsJsonView(AdminView):
251 def get(self, request):
253 cs = ClientStats()
254 clients = cs.get_entries()
256 return JsonResponse(map(self.to_dict, clients.most_common()))
258 def to_dict(self, res):
259 obj, count = res
261 if not isinstance(obj, tuple):
262 return obj, count
264 return obj._asdict(), count
267 class StatsView(AdminView):
268 """ shows general stats as HTML page """
270 template_name = 'admin/stats.html'
272 def _get_stats(self):
273 return {
274 'podcasts': podcast_count(),
275 'episodes': episode_count(),
276 'users': User.count(),
279 def get(self, request):
280 stats = self._get_stats()
281 return self.render_to_response({
282 'stats': stats,
286 class StatsJsonView(StatsView):
287 """ provides general stats as JSON """
289 def get(self, request):
290 stats = self._get_stats()
291 return JsonResponse(stats)
294 class FiletypeStatsView(AdminView):
296 template_name = 'admin/filetypes.html'
298 def get(self, request):
299 stats = filetype_stats()
301 if len(stats):
302 max_num = stats.most_common(1)[0][1]
303 else:
304 max_num = 0
306 return self.render_to_response({
307 'max_num': max_num,
308 'stats': stats.most_common(),
312 class ActivateUserView(AdminView):
313 """ Lets admins manually activate users """
315 template_name = 'admin/activate-user.html'
317 def get(self, request):
318 return self.render_to_response({})
320 def post(self, request):
322 username = request.POST.get('username')
323 email = request.POST.get('email')
325 if not (username or email):
326 messages.error(request,
327 _('Provide either username or email address'))
328 return HttpResponseRedirect(reverse('admin-activate-user'))
330 user = None
332 if username:
333 user = User.get_user(username, is_active=None)
335 if email and not user:
336 user = User.get_user_by_email(email, is_active=None)
338 if not user:
339 messages.error(request, _('No user found'))
340 return HttpResponseRedirect(reverse('admin-activate-user'))
342 activate_user(user)
343 messages.success(request,
344 _('User {username} ({email}) activated'.format(
345 username=user.username, email=user.email)))
346 return HttpResponseRedirect(reverse('admin-activate-user'))
350 class UnifyDuplicateSlugsSelect(AdminView):
351 """ select a podcast for which to unify slugs """
352 template_name = 'admin/unify-slugs-select.html'
355 class UnifyDuplicateSlugs(AdminView):
356 """ start slug-unification task """
358 def post(self, request):
359 podcast_url = request.POST.get('feed')
360 podcast = podcast_for_url(podcast_url)
362 if not podcast:
363 messages.error(request, _('Podcast with URL "%s" does not exist' %
364 (podcast_url,)))
365 return HttpResponseRedirect(reverse('admin-unify-slugs-select'))
367 res = unify_slugs.delay(podcast)
368 return HttpResponseRedirect(reverse('admin-unify-slugs-status',
369 args=[res.task_id]))
372 class UnifySlugsStatus(AdminView):
373 """ Displays the status of the unify-slugs operation """
375 template_name = 'admin/task-status.html'
377 def get(self, request, task_id):
378 result = merge_podcasts.AsyncResult(task_id)
380 if not result.ready():
381 return self.render_to_response({
382 'ready': False,
385 # clear cache to make merge result visible
386 # TODO: what to do with multiple frontends?
387 cache.clear()
389 actions, podcast = result.get()
391 return self.render_to_response({
392 'ready': True,
393 'actions': actions.items(),
394 'podcast': podcast,