1 from datetime import datetime, timedelta
2 import os, time, csv, random
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
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
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)
29 return HttpResponseUnauthorized(request)
35 def wrapper(request, *args, **kwargs):
36 res = fn(request, *args, **kwargs)
37 if isinstance(res, HttpResponse):
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)
49 context['tools'] = TOOLS
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)])
54 return render_to_response(template, context, context_instance=RequestContext(request))
58 def admin_tools_page(name, label):
67 class ActivityPaginatorContext(pagination.PaginatorContext):
69 super (ActivityPaginatorContext, self).__init__('ADMIN_RECENT_ACTIVITY', pagesizes=(20, 40, 80), default_pagesize=40)
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(),
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')
85 return HttpResponseRedirect(reverse('admin_index'))
88 def statistics(request):
89 today = datetime.now()
90 last_month = today - timedelta(days=30)
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)
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)
102 last_month_users = User.objects.filter(date_joined__gt=last_month
103 ).order_by('date_joined').values_list('date_joined', flat=True)
105 last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
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)
112 return 'osqaadmin/statistics.html', {
115 'id': 'questions_graph',
116 'caption': _("Questions Graph"),
119 'id': 'userss_graph',
120 'caption': _("Users Graph"),
127 def tools_page(request, name):
128 if not name in TOOLS:
131 return TOOLS[name](request)
135 def settings_set(request, set_name):
136 set = Setting.sets.get(set_name, {})
137 current_preview = request.session.get('previewing_settings', {})
143 form = SettingsSetForm(set, data=request.POST, files=request.FILES)
146 if 'preview' in request.POST:
147 current_preview.update(form.cleaned_data)
148 request.session['previewing_settings'] = current_preview
150 return HttpResponseRedirect(reverse('index'))
153 current_preview.pop(s.name, None)
155 request.session['previewing_settings'] = current_preview
157 if not 'reset' in request.POST:
159 request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name)
161 if set_name in ('minrep', 'badges', 'repgain'):
162 settings.SETTINGS_PACK.set_value("custom")
164 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
166 form = SettingsSetForm(set, unsaved=current_preview)
168 return 'osqaadmin/set.html', {
170 'markdown': set.markdown,
174 def get_default(request, set_name, var_name):
175 set = Setting.sets.get(set_name, None)
176 if set is None: raise Http404
178 setting = dict([(s.name, s) for s in set]).get(var_name, None)
179 if setting is None: raise Http404
183 if request.is_ajax():
184 return HttpResponse(setting.default)
186 return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
189 def get_recent_activity():
190 return Action.objects.order_by('-action_date')
192 def get_flagged_posts():
193 return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
195 def get_statistics():
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(),
208 def go_bootstrap(request):
209 #todo: this is the quick and dirty way of implementing a bootstrap mode
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)
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)
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)
264 settings.SETTINGS_PACK.set_value("bootstrap")
266 request.user.message_set.create(message=_('Bootstrap mode enabled'))
267 return HttpResponseRedirect(reverse('admin_index'))
270 def go_defaults(request):
271 for setting in Setting.sets['badges']:
273 for setting in Setting.sets['minrep']:
275 for setting in Setting.sets['repgain']:
278 settings.SETTINGS_PACK.set_value("default")
280 request.user.message_set.create(message=_('All values reverted to defaults'))
281 return HttpResponseRedirect(reverse('admin_index'))
285 def recalculate_denormalized(request):
286 for n in Node.objects.all():
288 n.score = n.votes.aggregate(score=models.Sum('value'))['score']
289 if not n.score: n.score = 0
292 for u in User.objects.all():
293 u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
296 request.user.message_set.create(message=_('All values recalculated'))
297 return HttpResponseRedirect(reverse('admin_index'))
300 def maintenance(request):
302 if 'close' in request.POST or 'adjust' in request.POST:
303 form = MaintenanceModeForm(request.POST)
306 settings.MAINTAINANCE_MODE.set_value({
307 'allow_ips': form.cleaned_data['ips'],
308 'message': form.cleaned_data['message']})
310 if 'close' in request.POST:
311 message = _('Maintenance mode enabled')
313 message = _('Settings adjusted')
315 request.user.message_set.create(message=message)
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'))
323 form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
324 'message': _('Currently down for maintenance. We\'ll be back soon')})
326 return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
331 def flagged_posts(request):
332 return ('osqaadmin/flagged_posts.html', {
333 'flagged_posts': get_flagged_posts(),
337 def static_pages(request):
338 pages = Page.objects.all()
340 return ('osqaadmin/static_pages.html', {
345 def edit_page(request, id=None):
347 page = get_object_or_404(Page, id=id)
352 form = PageForm(page, request.POST)
355 if form.has_changed():
357 page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
360 EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
361 data=form.cleaned_data)
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'])
368 return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
371 form = PageForm(page)
374 published = page.published
378 return ('osqaadmin/edit_page.html', {
381 'published': published
384 @admin_tools_page(_('createuser'), _("Create new user"))
385 def create_user(request):
387 form = CreateUserForm(request.POST)
390 user_ = User(username=form.cleaned_data['username'], email=form.cleaned_data['email'])
391 user_.set_password(form.cleaned_data['password1'])
393 if not form.cleaned_data.get('validate_email', False):
394 user_.email_isvalid = True
397 UserJoinsAction(user=user_).save()
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"))
402 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'createuser'}))
404 form = CreateUserForm()
406 return ('osqaadmin/createuser.html', {
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)
419 @admin_tools_page(_("nodeman"), _("Node management"))
420 def node_management(request):
422 selected_nodes = request.POST.getlist('_selected_node')
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)
428 message = _("No action performed")
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()
435 message = _("All selected nodes marked as deleted")
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()
442 message = _("Selected questions were closed")
444 if action == "hard_delete_selected":
445 ids = [n.id for n in selected_nodes]
449 node = Node.objects.get(id=id)
454 message = _("All selected nodes deleted")
456 request.user.message_set.create(message=message)
457 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}))
460 nodes = Node.objects.all()
463 filter_form = NodeManFilterForm(request.GET)
465 filter_form = NodeManFilterForm({'node_type': 'all', 'state_type': 'any'})
467 authors = request.GET.getlist('authors')
468 tags = request.GET.getlist('tags')
470 if filter_form.is_valid():
471 data = filter_form.cleaned_data
473 if data['node_type'] != 'all':
474 nodes = nodes.filter(node_type=data['node_type'])
476 if (data['state_type'] != 'any'):
477 nodes = nodes.filter_state(**{str(data['state_type']): True})
480 nodes = nodes.filter(author__id__in=authors)
481 authors = User.objects.filter(id__in=authors)
484 nodes = nodes.filter(tags__id__in=tags)
485 tags = Tag.objects.filter(id__in=tags)
490 if data['text_in'] == 'title' or data['text_in'] == 'both':
491 filter = models.Q(title__icontains=data['text'])
493 if data['text_in'] == 'body' or data['text_in'] == 'both':
494 sec_filter = models.Q(body__icontains=data['text'])
496 filter = filter | sec_filter
501 nodes = nodes.filter(filter)
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')
506 return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
508 'node_types': node_types,
509 'state_types': state_types,
510 'filter_form': filter_form,