]> git.openstreetmap.org Git - osqa.git/blob - forum/views/admin.py
Should fix the missing admin items problem.
[osqa.git] / forum / views / admin.py
1 from datetime import datetime, timedelta
2 import os, time, csv, random
3
4 from django.shortcuts import render_to_response, get_object_or_404
5 from django.core.urlresolvers import reverse
6 from django.http import HttpResponseRedirect, HttpResponse, Http404
7 from forum.http_responses import HttpResponseUnauthorized
8 from django.template import RequestContext
9 from django.utils.translation import ugettext as _
10 from django.utils import simplejson
11 from django.db import models
12 from forum.settings.base import Setting
13 from forum.forms import MaintenanceModeForm, PageForm, CreateUserForm
14 from forum.settings.forms import SettingsSetForm
15 from forum.utils import pagination, html
16 from forum.utils.mail import send_template_email
17
18 from forum.models import Question, Answer, User, Node, Action, Page, NodeState, Tag
19 from forum.models.node import NodeMetaClass
20 from forum.actions import NewPageAction, EditPageAction, PublishAction, DeleteAction, UserJoinsAction, CloseAction
21 from forum import settings
22
23 TOOLS = {}
24
25 def super_user_required(fn):
26     def wrapper(request, *args, **kwargs):
27         if request.user.is_authenticated() and request.user.is_superuser:
28             return fn(request, *args, **kwargs)
29         else:
30             return HttpResponseUnauthorized(request)
31
32     return wrapper
33
34 def staff_user_required(fn):
35     def wrapper(request, *args, **kwargs):
36         if request.user.is_authenticated() and (request.user.is_staff or request.user.is_superuser):
37             return fn(request, *args, **kwargs)
38         else:
39             return HttpResponseUnauthorized(request)
40
41     return wrapper
42
43 def admin_page_wrapper(fn, request, *args, **kwargs):
44     res = fn(request, *args, **kwargs)
45     if isinstance(res, HttpResponse):
46         return res
47
48     template, context = res
49     context['basetemplate'] = settings.DJSTYLE_ADMIN_INTERFACE and "osqaadmin/djstyle_base.html" or "osqaadmin/base.html"
50     context['allsets'] = Setting.sets
51     context['othersets'] = sorted(
52             [s for s in Setting.sets.values() if not s.name in
53             ('basic', 'users', 'email', 'paths', 'extkeys', 'repgain', 'minrep', 'voting', 'accept', 'badges', 'about', 'faq', 'sidebar',
54             'form', 'moderation', 'css', 'headandfoot', 'head', 'view', 'urls')]
55             , lambda s1, s2: s1.weight - s2.weight)
56
57     context['tools'] = [(name, fn.label) for name, fn in TOOLS.items()]
58
59     unsaved = request.session.get('previewing_settings', {})
60     context['unsaved'] = set([getattr(settings, s).set.name for s in unsaved.keys() if hasattr(settings, s)])
61
62     return render_to_response(template, context, context_instance=RequestContext(request))
63
64 def admin_page(fn):
65     @super_user_required
66     def wrapper(request, *args, **kwargs):
67         return admin_page_wrapper(fn, request, *args, **kwargs)
68
69     return wrapper
70
71 def moderation_page(fn):
72     @staff_user_required
73     def wrapper(request, *args, **kwargs):
74         return admin_page_wrapper(fn, request, *args, **kwargs)
75
76     return wrapper
77
78 def admin_tools_page(name, label):    
79     def decorator(fn):
80         fn = admin_page(fn)
81         fn.label = label
82         TOOLS[name] = fn
83
84         return fn
85     return decorator
86
87 class ActivityPaginatorContext(pagination.PaginatorContext):
88     def __init__(self):
89         super (ActivityPaginatorContext, self).__init__('ADMIN_RECENT_ACTIVITY', pagesizes=(20, 40, 80), default_pagesize=40)
90
91 @admin_page
92 def dashboard(request):
93     return ('osqaadmin/dashboard.html', pagination.paginated(request, ("recent_activity", ActivityPaginatorContext()), {
94     'settings_pack': unicode(settings.SETTINGS_PACK),
95     'statistics': get_statistics(),
96     'recent_activity': get_recent_activity(),
97     'flagged_posts': get_flagged_posts(),
98     }))
99
100 @super_user_required
101 def interface_switch(request):
102     if request.GET and request.GET.get('to', None) and request.GET['to'] in ('default', 'djstyle'):
103         settings.DJSTYLE_ADMIN_INTERFACE.set_value(request.GET['to'] == 'djstyle')
104
105     return HttpResponseRedirect(reverse('admin_index'))
106
107 @admin_page
108 def statistics(request):
109     today = datetime.now()
110     last_month = today - timedelta(days=30)
111
112     last_month_questions = Question.objects.filter_state(deleted=False).filter(added_at__gt=last_month
113                                                                                ).order_by('added_at').values_list(
114             'added_at', flat=True)
115
116     last_month_n_questions = Question.objects.filter_state(deleted=False).filter(added_at__lt=last_month).count()
117     qgraph_data = simplejson.dumps([
118     (time.mktime(d.timetuple()) * 1000, i + last_month_n_questions)
119     for i, d in enumerate(last_month_questions)
120     ])
121
122     last_month_users = User.objects.filter(date_joined__gt=last_month
123                                            ).order_by('date_joined').values_list('date_joined', flat=True)
124
125     last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
126
127     ugraph_data = simplejson.dumps([
128     (time.mktime(d.timetuple()) * 1000, i + last_month_n_users)
129     for i, d in enumerate(last_month_users)
130     ])
131
132     return 'osqaadmin/statistics.html', {
133     'graphs': [
134             {
135             'id': 'questions_graph',
136             'caption': _("Questions Graph"),
137             'data': qgraph_data
138             }, {
139             'id': 'userss_graph',
140             'caption': _("Users Graph"),
141             'data': ugraph_data
142             }
143             ]
144     }
145
146 @admin_page
147 def tools_page(request, name):
148     if not name in TOOLS:
149         raise Http404
150
151     return TOOLS[name](request)
152
153
154 @admin_page
155 def settings_set(request, set_name):
156     set = Setting.sets.get(set_name, {})
157     current_preview = request.session.get('previewing_settings', {})
158
159     if set is None:
160         raise Http404
161
162     if request.POST:
163         form = SettingsSetForm(set, data=request.POST, files=request.FILES)
164
165         if form.is_valid():
166             if 'preview' in request.POST:
167                 current_preview.update(form.cleaned_data)
168                 request.session['previewing_settings'] = current_preview
169
170                 return HttpResponseRedirect(reverse('index'))
171             else:
172                 for s in set:
173                     current_preview.pop(s.name, None)
174
175                 request.session['previewing_settings'] = current_preview
176
177                 if not 'reset' in request.POST:
178                     form.save()
179                     request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name)
180
181                     if set_name in ('minrep', 'badges', 'repgain'):
182                         settings.SETTINGS_PACK.set_value("custom")
183
184                 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
185     else:
186         form = SettingsSetForm(set, unsaved=current_preview)
187
188     return 'osqaadmin/set.html', {
189     'form': form,
190     'markdown': set.markdown,
191     }
192
193 @super_user_required
194 def get_default(request, set_name, var_name):
195     set = Setting.sets.get(set_name, None)
196     if set is None: raise Http404
197
198     setting = dict([(s.name, s) for s in set]).get(var_name, None)
199     if setting is None: raise Http404
200
201     setting.to_default()
202
203     if request.is_ajax():
204         return HttpResponse(setting.default)
205     else:
206         return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
207
208
209 def get_recent_activity():
210     return Action.objects.order_by('-action_date')
211
212 def get_flagged_posts():
213     return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
214
215 def get_statistics():
216     return {
217     'total_users': User.objects.all().count(),
218     'users_last_24': User.objects.filter(date_joined__gt=(datetime.now() - timedelta(days=1))).count(),
219     'total_questions': Question.objects.filter_state(deleted=False).count(),
220     'questions_last_24': Question.objects.filter_state(deleted=False).filter(
221             added_at__gt=(datetime.now() - timedelta(days=1))).count(),
222     'total_answers': Answer.objects.filter_state(deleted=False).count(),
223     'answers_last_24': Answer.objects.filter_state(deleted=False).filter(
224             added_at__gt=(datetime.now() - timedelta(days=1))).count(),
225     }
226
227 @super_user_required
228 def go_bootstrap(request):
229 #todo: this is the quick and dirty way of implementing a bootstrap mode
230     try:
231         from forum_modules.default_badges import settings as dbsets
232         dbsets.POPULAR_QUESTION_VIEWS.set_value(100)
233         dbsets.NOTABLE_QUESTION_VIEWS.set_value(200)
234         dbsets.FAMOUS_QUESTION_VIEWS.set_value(300)
235         dbsets.NICE_ANSWER_VOTES_UP.set_value(2)
236         dbsets.NICE_QUESTION_VOTES_UP.set_value(2)
237         dbsets.GOOD_ANSWER_VOTES_UP.set_value(4)
238         dbsets.GOOD_QUESTION_VOTES_UP.set_value(4)
239         dbsets.GREAT_ANSWER_VOTES_UP.set_value(8)
240         dbsets.GREAT_QUESTION_VOTES_UP.set_value(8)
241         dbsets.FAVORITE_QUESTION_FAVS.set_value(1)
242         dbsets.STELLAR_QUESTION_FAVS.set_value(3)
243         dbsets.DISCIPLINED_MIN_SCORE.set_value(3)
244         dbsets.PEER_PRESSURE_MAX_SCORE.set_value(-3)
245         dbsets.CIVIC_DUTY_VOTES.set_value(15)
246         dbsets.PUNDIT_COMMENT_COUNT.set_value(10)
247         dbsets.SELF_LEARNER_UP_VOTES.set_value(2)
248         dbsets.STRUNK_AND_WHITE_EDITS.set_value(10)
249         dbsets.ENLIGHTENED_UP_VOTES.set_value(2)
250         dbsets.GURU_UP_VOTES.set_value(4)
251         dbsets.NECROMANCER_UP_VOTES.set_value(2)
252         dbsets.NECROMANCER_DIF_DAYS.set_value(30)
253         dbsets.TAXONOMIST_USE_COUNT.set_value(5)
254     except:
255         pass
256
257     settings.REP_TO_VOTE_UP.set_value(0)
258     settings.REP_TO_VOTE_DOWN.set_value(15)
259     settings.REP_TO_FLAG.set_value(15)
260     settings.REP_TO_COMMENT.set_value(0)
261     settings.REP_TO_LIKE_COMMENT.set_value(0)
262     settings.REP_TO_UPLOAD.set_value(0)
263     settings.REP_TO_CREATE_TAGS.set_value(0)
264     settings.REP_TO_CLOSE_OWN.set_value(60)
265     settings.REP_TO_REOPEN_OWN.set_value(120)
266     settings.REP_TO_RETAG.set_value(150)
267     settings.REP_TO_EDIT_WIKI.set_value(200)
268     settings.REP_TO_EDIT_OTHERS.set_value(400)
269     settings.REP_TO_CLOSE_OTHERS.set_value(600)
270     settings.REP_TO_DELETE_COMMENTS.set_value(400)
271     settings.REP_TO_VIEW_FLAGS.set_value(30)
272
273     settings.INITIAL_REP.set_value(1)
274     settings.MAX_REP_BY_UPVOTE_DAY.set_value(300)
275     settings.REP_GAIN_BY_UPVOTED.set_value(15)
276     settings.REP_LOST_BY_DOWNVOTED.set_value(1)
277     settings.REP_LOST_BY_DOWNVOTING.set_value(0)
278     settings.REP_GAIN_BY_ACCEPTED.set_value(25)
279     settings.REP_GAIN_BY_ACCEPTING.set_value(5)
280     settings.REP_LOST_BY_FLAGGED.set_value(2)
281     settings.REP_LOST_BY_FLAGGED_3_TIMES.set_value(30)
282     settings.REP_LOST_BY_FLAGGED_5_TIMES.set_value(100)
283
284     settings.SETTINGS_PACK.set_value("bootstrap")
285
286     request.user.message_set.create(message=_('Bootstrap mode enabled'))
287     return HttpResponseRedirect(reverse('admin_index'))
288
289 @super_user_required
290 def go_defaults(request):
291     for setting in Setting.sets['badges']:
292         setting.to_default()
293     for setting in Setting.sets['minrep']:
294         setting.to_default()
295     for setting in Setting.sets['repgain']:
296         setting.to_default()
297
298     settings.SETTINGS_PACK.set_value("default")
299
300     request.user.message_set.create(message=_('All values reverted to defaults'))
301     return HttpResponseRedirect(reverse('admin_index'))
302
303
304 @super_user_required
305 def recalculate_denormalized(request):
306     for n in Node.objects.all():
307         n = n.leaf
308         n.score = n.votes.aggregate(score=models.Sum('value'))['score']
309         if not n.score: n.score = 0
310         n.save()
311
312     for u in User.objects.all():
313         u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
314         u.save()
315
316     request.user.message_set.create(message=_('All values recalculated'))
317     return HttpResponseRedirect(reverse('admin_index'))
318
319 @admin_page
320 def maintenance(request):
321     if request.POST:
322         if 'close' in request.POST or 'adjust' in request.POST:
323             form = MaintenanceModeForm(request.POST)
324
325             if form.is_valid():
326                 settings.MAINTAINANCE_MODE.set_value({
327                 'allow_ips': form.cleaned_data['ips'],
328                 'message': form.cleaned_data['message']})
329
330                 if 'close' in request.POST:
331                     message = _('Maintenance mode enabled')
332                 else:
333                     message = _('Settings adjusted')
334
335                 request.user.message_set.create(message=message)
336
337                 return HttpResponseRedirect(reverse('admin_maintenance'))
338         elif 'open' in request.POST:
339             settings.MAINTAINANCE_MODE.set_value(None)
340             request.user.message_set.create(message=_("Your site is now running normally"))
341             return HttpResponseRedirect(reverse('admin_maintenance'))
342     else:
343         form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
344                                             'message': _('Currently down for maintenance. We\'ll be back soon')})
345
346     return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
347                                            })
348
349
350 @moderation_page
351 def flagged_posts(request):
352     return ('osqaadmin/flagged_posts.html', {
353     'flagged_posts': get_flagged_posts(),
354     })
355
356 @admin_page
357 def static_pages(request):
358     pages = Page.objects.all()
359
360     return ('osqaadmin/static_pages.html', {
361     'pages': pages,
362     })
363
364 @admin_page
365 def edit_page(request, id=None):
366     if id:
367         page = get_object_or_404(Page, id=id)
368     else:
369         page = None
370
371     if request.POST:
372         form = PageForm(page, request.POST)
373
374         if form.is_valid():
375             if form.has_changed():
376                 if not page:
377                     page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
378                                                                                                  ).node
379                 else:
380                     EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
381                             data=form.cleaned_data)
382
383             if ('publish' in request.POST) and (not page.published):
384                 PublishAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save()
385             elif ('unpublish' in request.POST) and page.published:
386                 page.nstate.published.cancel(ip=request.META['REMOTE_ADDR'])
387
388             return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
389
390     else:
391         form = PageForm(page)
392
393     if page:
394         published = page.published
395     else:
396         published = False
397
398     return ('osqaadmin/edit_page.html', {
399     'page': page,
400     'form': form,
401     'published': published
402     })
403
404 @admin_tools_page(_('createuser'), _("Create new user"))
405 def create_user(request):
406     if request.POST:
407         form = CreateUserForm(request.POST)
408
409         if form.is_valid():
410             user_ = User(username=form.cleaned_data['username'], email=form.cleaned_data['email'])
411             user_.set_password(form.cleaned_data['password1'])
412
413             if not form.cleaned_data.get('validate_email', False):
414                 user_.email_isvalid = True
415
416             user_.save()
417             UserJoinsAction(user=user_).save()
418
419             request.user.message_set.create(message=_("New user created sucessfully. %s.") % html.hyperlink(
420                     user_.get_profile_url(), _("See %s profile") % user_.username, target="_blank"))
421
422             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'createuser'}))
423     else:
424         form = CreateUserForm()
425
426     return ('osqaadmin/createuser.html', {
427         'form': form,
428     })
429
430 class NodeManagementPaginatorContext(pagination.PaginatorContext):
431     def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=100):
432         super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=(
433             (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")),
434             (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")),
435             (_('author'), pagination.SimpleSort(_('author'), '-author__username', "")),
436             (_('author_asc'), pagination.SimpleSort(_('author_asc'), 'author__username', "")),
437             (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
438             (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
439             (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
440             (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
441             (_('act_by'), pagination.SimpleSort(_('act_by'), '-last_activity_by__username', "")),
442             (_('act_by_asc'), pagination.SimpleSort(_('act_by_asc'), 'last_activity_by__username', "")),
443         ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
444
445 @admin_tools_page(_("nodeman"), _("Bulk management"))
446 def node_management(request):
447     if request.POST:
448         params = pagination.generate_uri(request.GET, ('page',))
449
450         if "save_filter" in request.POST:
451             filter_name = request.POST.get('filter_name', _('filter'))
452             params = pagination.generate_uri(request.GET, ('page',))
453             current_filters = settings.NODE_MAN_FILTERS.value
454             current_filters.append((filter_name, params))
455             settings.NODE_MAN_FILTERS.set_value(current_filters)
456
457         elif r"execute" in request.POST:
458             selected_nodes = request.POST.getlist('_selected_node')
459
460             if selected_nodes and request.POST.get('action', None):
461                 action = request.POST['action']
462                 selected_nodes = Node.objects.filter(id__in=selected_nodes)
463
464                 message = _("No action performed")
465
466                 if action == 'delete_selected':
467                     for node in selected_nodes:
468                         if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
469                             DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
470
471                     message = _("All selected nodes marked as deleted")
472
473                 if action == 'undelete_selected':
474                     for node in selected_nodes:
475                         if node.node_type in ('question', 'answer', 'comment') and (node.nis.deleted):
476                             node.nstate.deleted.cancel(ip=request.META['REMOTE_ADDR'])
477
478                     message = _("All selected nodes undeleted")
479
480                 if action == "close_selected":
481                     for node in selected_nodes:
482                         if node.node_type == "question" and (not node.nis.closed):
483                             CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
484
485                     message = _("Selected questions were closed")
486
487                 if action == "hard_delete_selected":
488                     ids = [n.id for n in selected_nodes]
489
490                     for id in ids:
491                         try:
492                             node = Node.objects.get(id=id)
493                             node.delete()
494                         except:
495                             pass
496
497                     message = _("All selected nodes deleted")
498
499                 request.user.message_set.create(message=message)
500
501                 params = pagination.generate_uri(request.GET, ('page',))
502                 
503             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
504
505
506     nodes = Node.objects.all()
507
508     text = request.GET.get('text', '')
509     text_in = request.GET.get('text_in', 'body')
510
511     authors = request.GET.getlist('authors')
512     tags = request.GET.getlist('tags')
513
514     type_filter = request.GET.getlist('node_type')
515     state_filter = request.GET.getlist('state_type')
516     state_filter_type = request.GET.get('state_filter_type', 'any')
517
518     if type_filter:
519         nodes = nodes.filter(node_type__in=type_filter)
520
521     state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
522     state_filter = [s for s in state_filter if s in state_types]
523
524     if state_filter:
525         if state_filter_type == 'all':
526             nodes = nodes.all_states(*state_filter)
527         else:
528             nodes = nodes.any_state(*state_filter)
529
530     if (authors):
531         nodes = nodes.filter(author__id__in=authors)
532         authors = User.objects.filter(id__in=authors)
533
534     if (tags):
535         nodes = nodes.filter(tags__id__in=tags)
536         tags = Tag.objects.filter(id__in=tags)
537
538     if text:
539         text_in = request.GET.get('text_in', 'body')
540         filter = None
541
542         if text_in == 'title' or text_in == 'both':
543             filter = models.Q(title__icontains=text)
544
545         if text_in == 'body' or text_in == 'both':
546             sec_filter = models.Q(body__icontains=text)
547             if filter:
548                 filter = filter | sec_filter
549             else:
550                 filter = sec_filter
551
552         if filter:
553             nodes = nodes.filter(filter)
554
555     node_types = [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
556
557     return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
558     'nodes': nodes,
559     'text': text,
560     'text_in': text_in,
561     'type_filter': type_filter,
562     'state_filter': state_filter,
563     'state_filter_type': state_filter_type,
564     'node_types': node_types,
565     'state_types': state_types,
566     'authors': authors,
567     'tags': tags,
568     'hide_menu': True
569     }))
570
571 @super_user_required
572 def test_email_settings(request):
573     user = request.user
574
575     send_template_email([user,], 'osqaadmin/mail_test.html', { 'user' : user })
576
577     return render_to_response(
578         'osqaadmin/test_email_settings.html',
579         { 'user': user, },
580         RequestContext(request)
581     )