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