[Migration] use migrated users
[mygpo.git] / mygpo / administration / views.py
blob81ffead799347d8ac7b45855892faa0021d821d5
1 import re
2 import socket
3 from itertools import count, chain
4 from collections import Counter
5 from datetime import datetime
7 import django
8 from django.shortcuts import render
9 from django.contrib import messages
10 from django.core.urlresolvers import reverse
11 from django.core.cache import cache
12 from django.http import HttpResponseRedirect
13 from django.template.loader import render_to_string
14 from django.template import RequestContext
15 from django.utils.translation import ugettext as _
16 from django.contrib.sites.models import RequestSite
17 from django.contrib.auth import get_user_model
18 from django.views.generic import TemplateView
19 from django.utils.decorators import method_decorator
20 from django.conf import settings
22 from mygpo.podcasts.models import Podcast, Episode
23 from mygpo.administration.auth import require_staff
24 from mygpo.administration.group import PodcastGrouper
25 from mygpo.maintenance.merge import PodcastMerger, IncorrectMergeException
26 from mygpo.administration.clients import UserAgentStats, ClientStats
27 from mygpo.administration.tasks import merge_podcasts
28 from mygpo.utils import get_git_head
29 from mygpo.users.models import UserProxy
30 from mygpo.api.httpresponse import JsonResponse
31 from mygpo.celery import celery
32 from mygpo.db.couchdb import get_userdata_database
33 from mygpo.db.couchdb.user import activate_user, add_published_objs
36 class InvalidPodcast(Exception):
37 """ raised when we try to merge a podcast that doesn't exist """
39 class AdminView(TemplateView):
41 @method_decorator(require_staff)
42 def dispatch(self, *args, **kwargs):
43 return super(AdminView, self).dispatch(*args, **kwargs)
46 class Overview(AdminView):
47 template_name = 'admin/overview.html'
50 class HostInfo(AdminView):
51 """ shows host information for diagnosis """
53 template_name = 'admin/hostinfo.html'
55 def get(self, request):
56 commit, msg = get_git_head()
57 base_dir = settings.BASE_DIR
58 hostname = socket.gethostname()
59 django_version = django.VERSION
61 main_db = get_userdata_database()
63 db_tasks = main_db.server.active_tasks()
65 i = celery.control.inspect()
66 scheduled = i.scheduled()
67 if not scheduled:
68 num_celery_tasks = None
69 else:
70 num_celery_tasks = sum(len(node) for node in scheduled.values())
72 feed_queue_status = self._get_feed_queue_status()
74 return self.render_to_response({
75 'git_commit': commit,
76 'git_msg': msg,
77 'base_dir': base_dir,
78 'hostname': hostname,
79 'django_version': django_version,
80 'main_db': main_db.uri,
81 'db_tasks': db_tasks,
82 'num_celery_tasks': num_celery_tasks,
83 'feed_queue_status': feed_queue_status,
86 def _get_feed_queue_status(self):
87 now = datetime.utcnow()
88 next_podcast = Podcast.objects.order_by_next_update().first()
90 delta = (next_podcast.next_update - now)
91 delta_mins = delta.total_seconds() / 60
92 return delta_mins
95 class MergeSelect(AdminView):
96 template_name = 'admin/merge-select.html'
98 def get(self, request):
99 num = int(request.GET.get('podcasts', 2))
100 urls = [''] * num
102 return self.render_to_response({
103 'urls': urls,
107 class MergeBase(AdminView):
109 def _get_podcasts(self, request):
110 podcasts = []
111 for n in count():
112 podcast_url = request.POST.get('feed%d' % n, None)
113 if podcast_url is None:
114 break
116 if not podcast_url:
117 continue
119 p = Podcast.objects.get(urls__url=podcast_url)
120 podcasts.append(p)
122 return podcasts
125 class MergeVerify(MergeBase):
127 template_name = 'admin/merge-grouping.html'
129 def post(self, request):
131 try:
132 podcasts = self._get_podcasts(request)
134 grouper = PodcastGrouper(podcasts)
136 get_features = lambda (e_id, e): ((e.url, e.title), e_id)
138 num_groups = grouper.group(get_features)
141 except InvalidPodcast as ip:
142 messages.error(request,
143 _('No podcast with URL {url}').format(url=str(ip)))
145 podcasts = []
146 num_groups = []
148 return self.render_to_response({
149 'podcasts': podcasts,
150 'groups': num_groups,
154 class MergeProcess(MergeBase):
156 RE_EPISODE = re.compile(r'episode_([0-9a-fA-F]{32})')
158 def post(self, request):
160 try:
161 podcasts = self._get_podcasts(request)
163 except InvalidPodcast as ip:
164 messages.error(request,
165 _('No podcast with URL {url}').format(url=str(ip)))
167 grouper = PodcastGrouper(podcasts)
169 features = {}
170 for key, feature in request.POST.items():
171 m = self.RE_EPISODE.match(key)
172 if m:
173 episode_id = m.group(1)
174 features[episode_id] = feature
176 get_features = lambda (e_id, e): (features.get(e_id, e_id), e_id)
178 num_groups = grouper.group(get_features)
180 if 'renew' in request.POST:
181 return render(request, 'admin/merge-grouping.html', {
182 'podcasts': podcasts,
183 'groups': num_groups,
187 elif 'merge' in request.POST:
189 podcast_ids = [p.get_id() for p in podcasts]
190 num_groups = list(num_groups)
192 res = merge_podcasts.delay(podcast_ids, num_groups)
194 return HttpResponseRedirect(reverse('admin-merge-status',
195 args=[res.task_id]))
198 class MergeStatus(AdminView):
199 """ Displays the status of the merge operation """
201 template_name = 'admin/task-status.html'
203 def get(self, request, task_id):
204 result = merge_podcasts.AsyncResult(task_id)
206 if not result.ready():
207 return self.render_to_response({
208 'ready': False,
211 # clear cache to make merge result visible
212 # TODO: what to do with multiple frontends?
213 cache.clear()
215 try:
216 actions, podcast = result.get()
218 except IncorrectMergeException as ime:
219 messages.error(request, str(ime))
220 return HttpResponseRedirect(reverse('admin-merge'))
222 return self.render_to_response({
223 'ready': True,
224 'actions': actions.items(),
225 'podcast': podcast,
230 class UserAgentStatsView(AdminView):
231 template_name = 'admin/useragents.html'
233 def get(self, request):
235 uas = UserAgentStats()
236 useragents = uas.get_entries()
238 return self.render_to_response({
239 'useragents': useragents.most_common(),
240 'max_users': uas.max_users,
241 'total': uas.total_users,
245 class ClientStatsView(AdminView):
246 template_name = 'admin/clients.html'
248 def get(self, request):
250 cs = ClientStats()
251 clients = cs.get_entries()
253 return self.render_to_response({
254 'clients': clients.most_common(),
255 'max_users': cs.max_users,
256 'total': cs.total_users,
260 class ClientStatsJsonView(AdminView):
261 def get(self, request):
263 cs = ClientStats()
264 clients = cs.get_entries()
266 return JsonResponse(map(self.to_dict, clients.most_common()))
268 def to_dict(self, res):
269 obj, count = res
271 if not isinstance(obj, tuple):
272 return obj, count
274 return obj._asdict(), count
277 class StatsView(AdminView):
278 """ shows general stats as HTML page """
280 template_name = 'admin/stats.html'
282 def _get_stats(self):
283 return {
284 'podcasts': Podcast.objects.count_fast(),
285 'episodes': Episode.objects.count_fast(),
286 'users': UserProxy.objects.count_fast(),
289 def get(self, request):
290 stats = self._get_stats()
291 return self.render_to_response({
292 'stats': stats,
296 class StatsJsonView(StatsView):
297 """ provides general stats as JSON """
299 def get(self, request):
300 stats = self._get_stats()
301 return JsonResponse(stats)
304 class ActivateUserView(AdminView):
305 """ Lets admins manually activate users """
307 template_name = 'admin/activate-user.html'
309 def get(self, request):
310 return self.render_to_response({})
312 def post(self, request):
314 User = get_user_model()
315 username = request.POST.get('username')
316 email = request.POST.get('email')
318 if not (username or email):
319 messages.error(request,
320 _('Provide either username or email address'))
321 return HttpResponseRedirect(reverse('admin-activate-user'))
323 user = None
325 if username:
326 user = User.objects.get(username=username)
328 if email and not user:
329 user = User.objects.get(email=email)
331 if not user:
332 messages.error(request, _('No user found'))
333 return HttpResponseRedirect(reverse('admin-activate-user'))
335 activate_user(user)
336 messages.success(request,
337 _('User {username} ({email}) activated'.format(
338 username=user.username, email=user.email)))
339 return HttpResponseRedirect(reverse('admin-activate-user'))
343 class MakePublisherInput(AdminView):
344 """ Get all information necessary for making someone publisher """
346 template_name = 'admin/make-publisher-input.html'
349 class MakePublisher(AdminView):
350 """ Assign publisher permissions """
352 template_name = 'admin/make-publisher-result.html'
354 def post(self, request):
355 username = request.POST.get('username')
356 user = User.objects.get(username=username)
357 if user is None:
358 messages.error(request, 'User "{username}" not found'.format(username=username))
359 return HttpResponseRedirect(reverse('admin-make-publisher-input'))
361 feeds = request.POST.get('feeds')
362 feeds = feeds.split()
363 podcasts = set()
365 for feed in feeds:
366 try:
367 podcast = Podcast.objects.get(urls__url=feed)
368 except Podcast.DoesNotExist:
369 messages.warning(request, 'Podcast with URL {feed} not found'.format(feed=feed))
370 continue
372 podcasts.add(podcast)
374 self.set_publisher(request, user, podcasts)
375 self.send_mail(request, user, podcasts)
376 return HttpResponseRedirect(reverse('admin-make-publisher-result'))
378 def set_publisher(self, request, user, podcasts):
379 podcast_ids = set(p.get_id() for p in podcasts)
380 add_published_objs(user, podcast_ids)
381 messages.success(request, 'Set publisher permissions for {count} podcasts'.format(count=len(podcast_ids)))
383 def send_mail(self, request, user, podcasts):
384 site = RequestSite(request)
385 msg = render_to_string('admin/make-publisher-mail.txt', {
386 'user': user,
387 'podcasts': podcasts,
388 'support_url': settings.SUPPORT_URL,
389 'site': site,
391 context_instance=RequestContext(request))
392 subj = get_email_subject(site, _('Publisher Permissions'))
394 user.email_user(subj, msg)
395 messages.success(request, 'Sent email to user "{username}"'.format(username=user.username))
398 class MakePublisherResult(AdminView):
399 template_name = 'make-publisher-result.html'
402 def get_email_subject(site, txt):
403 return '[{domain}] {txt}'.format(domain=site.domain, txt=txt)