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