]> git.openstreetmap.org Git - osqa.git/blob - forum/views/users.py
fix breach in award points that allows user to award infinite points
[osqa.git] / forum / views / users.py
1 from forum.models import User
2 from django.db.models import Q, Count
3 from django.core.paginator import Paginator, EmptyPage, InvalidPage
4 from django.template.defaultfilters import slugify
5 from django.contrib.contenttypes.models import ContentType
6 from django.core.urlresolvers import reverse
7 from django.shortcuts import render_to_response, get_object_or_404
8 from django.template import RequestContext
9 from django.http import HttpResponse, HttpResponseRedirect, Http404
10 from forum.http_responses import HttpResponseUnauthorized
11 from django.utils.translation import ugettext as _
12 from django.utils.http import urlquote_plus
13 from django.utils.html import strip_tags
14 from django.utils.encoding import smart_unicode
15 from django.core.urlresolvers import reverse, NoReverseMatch
16 from forum.forms import *
17 from forum.utils.html import sanitize_html
18 from forum.modules import decorate, ReturnImediatelyException
19 from datetime import datetime, date
20 from forum.actions import EditProfileAction, FavoriteAction, BonusRepAction, SuspendAction, ReportAction
21 from forum.modules import ui
22 from forum.utils import pagination
23 from forum.views.readers import QuestionListPaginatorContext, AnswerPaginatorContext
24 from forum.settings import ONLINE_USERS
25
26 from django.contrib import messages
27
28 import json 
29 import time
30 import datetime
31 import decorators
32
33 class UserReputationSort(pagination.SimpleSort):
34     def apply(self, objects):
35         return objects.order_by('-is_active', self.order_by)
36
37 class UserListPaginatorContext(pagination.PaginatorContext):
38     def __init__(self, pagesizes=(20, 35, 60), default_pagesize=35):
39         super (UserListPaginatorContext, self).__init__('USERS_LIST', sort_methods=(
40             (_('reputation'), UserReputationSort(_('reputation'), '-reputation', _("sorted by reputation"))),
41             (_('newest'), pagination.SimpleSort(_('recent'), '-date_joined', _("newest members"))),
42             (_('last'), pagination.SimpleSort(_('oldest'), 'date_joined', _("oldest members"))),
43             (_('name'), pagination.SimpleSort(_('by username'), 'username', _("sorted by username"))),
44         ), pagesizes=pagesizes, default_pagesize=default_pagesize)
45
46 class SubscriptionListPaginatorContext(pagination.PaginatorContext):
47     def __init__(self):
48         super (SubscriptionListPaginatorContext, self).__init__('SUBSCRIPTION_LIST', pagesizes=(5, 10, 20), default_pagesize=20)
49
50 class UserAnswersPaginatorContext(pagination.PaginatorContext):
51     def __init__(self):
52         super (UserAnswersPaginatorContext, self).__init__('USER_ANSWER_LIST', sort_methods=(
53             (_('oldest'), pagination.SimpleSort(_('oldest answers'), 'added_at', _("oldest answers will be shown first"))),
54             (_('newest'), pagination.SimpleSort(_('newest answers'), '-added_at', _("newest answers will be shown first"))),
55             (_('votes'), pagination.SimpleSort(_('popular answers'), '-score', _("most voted answers will be shown first"))),
56         ), default_sort=_('votes'), pagesizes=(5, 10, 20), default_pagesize=20, prefix=_('answers'))
57
58 USERS_PAGE_SIZE = 35# refactor - move to some constants file
59
60 @decorators.render('users/users.html', 'users', _('users'), weight=200)
61 def users(request):
62     suser = request.REQUEST.get('q', "")
63     users = User.objects.all()
64
65     if suser != "":
66         users = users.filter(username__icontains=suser)
67
68     return pagination.paginated(request, ('users', UserListPaginatorContext()), {
69         "users" : users,
70         "suser" : suser,
71     })
72
73
74 @decorators.render('users/online_users.html', 'online_users', _('Online Users'), weight=200, tabbed=False)
75 def online_users(request):
76     suser = request.REQUEST.get('q', "")
77
78     sort = ""
79     if request.GET.get("sort", None):
80         try:
81             sort = int(request.GET["sort"])
82         except ValueError:
83             logging.error('Found invalid sort "%s", loading %s, refered by %s' % (
84                 request.GET.get("sort", ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')
85             ))
86             raise Http404()
87
88     page = 0
89     if request.GET.get("page", None):
90         try:
91             page = int(request.GET["page"])
92         except ValueError:
93             logging.error('Found invalid page "%s", loading %s, refered by %s' % (
94                 request.GET.get("page", ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')
95             ))
96             raise Http404()
97
98     pagesize = 10
99     if request.GET.get("pagesize", None):
100         try:
101             pagesize = int(request.GET["pagesize"])
102         except ValueError:
103             logging.error('Found invalid pagesize "%s", loading %s, refered by %s' % (
104                 request.GET.get("pagesize", ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')
105             ))
106             raise Http404()
107
108
109     users = None
110     if sort == "reputation":
111         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.reputation)
112     elif sort == "newest" :
113         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.newest)
114     elif sort == "last":
115         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.last)
116     elif sort == "name":
117         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.name)
118     elif sort == "oldest":
119         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.oldest)
120     elif sort == "newest":
121         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.newest)
122     elif sort == "votes":
123         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.votes)
124     else:
125         users = sorted(ONLINE_USERS.iteritems(), key=lambda x: x[1])
126
127     return render_to_response('users/online_users.html', {
128         "users" : users,
129         "suser" : suser,
130         "sort" : sort,
131         "page" : page,
132         "pageSize" : pagesize,
133     })
134
135
136 def edit_user(request, id, slug):
137     user = get_object_or_404(User, id=id)
138     if not (request.user.is_superuser or request.user == user):
139         return HttpResponseUnauthorized(request)
140     if request.method == "POST":
141         form = EditUserForm(user, request.POST)
142         if form.is_valid():
143             new_email = sanitize_html(form.cleaned_data['email'])
144
145             if new_email != user.email:
146                 user.email = new_email
147                 user.email_isvalid = False
148
149                 try:
150                     hash = ValidationHash.objects.get(user=request.user, type='email')
151                     hash.delete()
152                 except:
153                     pass
154
155             if settings.EDITABLE_SCREEN_NAME:
156                 user.username = sanitize_html(form.cleaned_data['username'])
157             user.real_name = sanitize_html(form.cleaned_data['realname'])
158             user.website = sanitize_html(form.cleaned_data['website'])
159             user.location = sanitize_html(form.cleaned_data['city'])
160             user.date_of_birth = form.cleaned_data['birthday']
161             if user.date_of_birth == "None":
162                 user.date_of_birth = datetime(1900, 1, 1, 0, 0)
163             user.about = sanitize_html(form.cleaned_data['about'])
164
165             user.save()
166             EditProfileAction(user=user, ip=request.META['REMOTE_ADDR']).save()
167
168             messages.info(request, _("Profile updated."))
169             return HttpResponseRedirect(user.get_profile_url())
170     else:
171         form = EditUserForm(user)
172     return render_to_response('users/edit.html', {
173     'user': user,
174     'form' : form,
175     'gravatar_faq_url' : reverse('faq') + '#gravatar',
176     }, context_instance=RequestContext(request))
177
178
179 @decorate.withfn(decorators.command)
180 def user_powers(request, id, action, status):
181     if not request.user.is_superuser:
182         raise decorators.CommandException(_("Only superusers are allowed to alter other users permissions."))
183
184     if (action == 'remove' and 'status' == 'super') and not request.user.is_siteowner():
185         raise decorators.CommandException(_("Only the site owner can remove the super user status from other user."))
186
187     user = get_object_or_404(User, id=id)
188     new_state = action == 'grant'
189
190     if status == 'super':
191         user.is_superuser = new_state
192     elif status == 'staff':
193         user.is_staff = new_state
194     else:
195         raise Http404()
196
197     user.save()
198     return decorators.RefreshPageCommand()
199
200
201 @decorate.withfn(decorators.command)
202 def award_points(request, id):
203     if not request.POST:
204         return render_to_response('users/karma_bonus.html')
205
206     if not request.user.is_superuser:
207         raise decorators.CommandException(_("Only superusers are allowed to award reputation points"))
208
209     try:
210         points = int(request.POST['points'])
211     except:
212         raise decorators.CommandException(_("Invalid number of points to award."))
213
214     awarding_user = get_object_or_404(User, id=request.user.pk)
215
216     if points > awarding_user.reputation:
217         raise decorators.CommandException(_("Invalid number of points to award."))
218
219     user = get_object_or_404(User, id=id)
220
221     extra = dict(message=request.POST.get('message', ''), awarding_user=request.user.id, value=points)
222
223     BonusRepAction(user=user, extra=extra).save(data=dict(value=points, affected=user))
224
225     return {'commands': {
226             'update_profile_karma': [user.reputation]
227         }}
228     
229
230 @decorate.withfn(decorators.command)
231 def suspend(request, id):
232     user = get_object_or_404(User, id=id)
233
234     if not request.user.is_superuser:
235         raise decorators.CommandException(_("Only superusers can suspend other users"))
236
237     if not request.POST.get('bantype', None):
238         if user.is_suspended():
239             suspension = user.suspension
240             suspension.cancel(user=request.user, ip=request.META['REMOTE_ADDR'])
241             return decorators.RefreshPageCommand()
242         else:
243             return render_to_response('users/suspend_user.html')
244
245     data = {
246         'bantype': request.POST.get('bantype', 'Indefinitely').strip(),
247         'publicmsg': request.POST.get('publicmsg', _('Bad behaviour')),
248         'privatemsg': request.POST.get('privatemsg', None) or request.POST.get('publicmsg', ''),
249         'suspended': user
250     }
251
252     if data['bantype'] == 'forxdays':
253         try:
254             data['forxdays'] = int(request.POST['forxdays'])
255         except:
256             raise decorators.CommandException(_('Invalid numeric argument for the number of days.'))
257
258     SuspendAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=data)
259
260     return decorators.RefreshPageCommand()
261
262 @decorate.withfn(decorators.command)
263 def report_user(request, id):
264     user = get_object_or_404(User, id=id)
265
266     if not request.POST.get('publicmsg', None):
267         return render_to_response('users/report_user.html')
268
269     data = {
270         'publicmsg': request.POST.get('publicmsg', _('N/A')),
271         'reported': user
272     }
273
274     ReportAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=data)
275
276
277     return decorators.RefreshPageCommand()
278
279
280
281 def user_view(template, tab_name, tab_title, tab_description, private=False, tabbed=True, render_to=None, weight=500):
282     def decorator(fn):
283         def params(request, id=None, slug=None):
284             # Get the user object by id if the id parameter has been passed
285             if id is not None:
286                 user = get_object_or_404(User, id=id)
287             # ...or by slug if the slug has been given
288             elif slug is not None:
289                 try:
290                     user = User.objects.get(username__iexact=slug)
291                 except User.DoesNotExist:
292                     raise Http404
293
294             if private and not (user == request.user or request.user.is_superuser):
295                 raise ReturnImediatelyException(HttpResponseUnauthorized(request))
296
297             if render_to and (not render_to(user)):
298                 raise ReturnImediatelyException(HttpResponseRedirect(user.get_profile_url()))
299
300             return [request, user], { 'slug' : slug, }
301
302         decorated = decorate.params.withfn(params)(fn)
303
304         def result(context_or_response, request, user, **kwargs):
305             rev_page_title = smart_unicode(user.username) + " - " + tab_description
306
307             # Check whether the return type of the decorated function is a context or Http Response
308             if isinstance(context_or_response, HttpResponse):
309                 response = context_or_response
310
311                 # If it is a response -- show it
312                 return response
313             else:
314                 # ...if it is a context move forward, update it and render it to response
315                 context = context_or_response
316
317             context.update({
318                 "tab": "users",
319                 "active_tab" : tab_name,
320                 "tab_description" : tab_description,
321                 "page_title" : rev_page_title,
322                 "can_view_private": (user == request.user) or request.user.is_superuser
323             })
324             return render_to_response(template, context, context_instance=RequestContext(request))
325
326         decorated = decorate.result.withfn(result, needs_params=True)(decorated)
327
328         if tabbed:
329             def url_getter(vu):
330                 try:
331                     return reverse(fn.__name__, kwargs={'id': vu.id, 'slug': slugify(smart_unicode(vu.username))})
332                 except NoReverseMatch:
333                     try:
334                         return reverse(fn.__name__, kwargs={'id': vu.id})
335                     except NoReverseMatch:
336                         return reverse(fn.__name__, kwargs={'slug': slugify(smart_unicode(vu.username))})
337
338             ui.register(ui.PROFILE_TABS, ui.ProfileTab(
339                 tab_name, tab_title, tab_description,url_getter, private, render_to, weight
340             ))
341
342         return decorated
343     return decorator
344
345
346 @user_view('users/stats.html', 'stats', _('overview'), _('user overview'))
347 def user_profile(request, user, **kwargs):
348     questions = Question.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')
349     answers = Answer.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')
350
351     # Check whether the passed slug matches the one for the user object
352     slug = kwargs['slug']
353     if slug != slugify(smart_unicode(user.username)):
354         return HttpResponseRedirect(user.get_absolute_url())
355
356     up_votes = user.vote_up_count
357     down_votes = user.vote_down_count
358     votes_today = user.get_vote_count_today()
359     votes_total = user.can_vote_count_today()
360
361     user_tags = Tag.objects.filter(Q(nodes__author=user) | Q(nodes__children__author=user)) \
362         .annotate(user_tag_usage_count=Count('name')).order_by('-user_tag_usage_count')
363
364     awards = [(Badge.objects.get(id=b['id']), b['count']) for b in
365               Badge.objects.filter(awards__user=user).values('id').annotate(count=Count('cls')).order_by('-count')]
366
367     return pagination.paginated(request, (
368     ('questions', QuestionListPaginatorContext('USER_QUESTION_LIST', _('questions'), default_pagesize=15)),
369     ('answers', UserAnswersPaginatorContext())), {
370     "view_user" : user,
371     "questions" : questions,
372     "answers" : answers,
373     "up_votes" : up_votes,
374     "down_votes" : down_votes,
375     "total_votes": up_votes + down_votes,
376     "votes_today_left": votes_total-votes_today,
377     "votes_total_per_day": votes_total,
378     "user_tags" : user_tags[:50],
379     "awards": awards,
380     "total_awards" : len(awards),
381     })
382     
383 @user_view('users/recent.html', 'recent', _('recent activity'), _('recent user activity'))
384 def user_recent(request, user, **kwargs):
385     activities = user.actions.exclude(
386             action_type__in=("voteup", "votedown", "voteupcomment", "flag", "newpage", "editpage")).order_by(
387             '-action_date')[:USERS_PAGE_SIZE]
388
389     return {"view_user" : user, "activities" : activities}
390
391
392 @user_view('users/reputation.html', 'reputation', _('reputation history'), _('graph of user karma'))
393 def user_reputation(request, user, **kwargs):
394     rep = list(user.reputes.order_by('date'))
395     values = [r.value for r in rep]
396     redux = lambda x, y: x+y
397
398     graph_data = json.dumps([
399     (time.mktime(rep[i].date.timetuple()) * 1000, reduce(redux, values[:i+1], 0))
400     for i in range(len(values))
401     ])
402
403     rep = user.reputes.filter(action__canceled=False).order_by('-date')[0:20]
404
405     return {"view_user": user, "reputation": rep, "graph_data": graph_data}
406
407 @user_view('users/votes.html', 'votes', _('votes'), _('user vote record'), True)
408 def user_votes(request, user, **kwargs):
409     votes = user.votes.exclude(node__state_string__contains="(deleted").filter(
410             node__node_type__in=("question", "answer")).order_by('-voted_at')[:USERS_PAGE_SIZE]
411
412     return {"view_user" : user, "votes" : votes}
413
414 @user_view('users/questions.html', 'favorites', _('favorites'), _('questions that user selected as his/her favorite'))
415 def user_favorites(request, user, **kwargs):
416     favorites = FavoriteAction.objects.filter(canceled=False, user=user)
417
418     return {"favorites" : favorites, "view_user" : user}
419
420 @user_view('users/subscriptions.html', 'subscriptions', _('subscription'), _('subscriptions'), True, tabbed=False)
421 def user_subscriptions(request, user, **kwargs):
422     return _user_subscriptions(request, user, **kwargs)
423
424 def _user_subscriptions(request, user, **kwargs):
425     enabled = True
426
427     tab = request.GET.get('tab', "settings")
428
429     # Manage tab
430     if tab == 'manage':
431         manage_open = True
432
433         auto = request.GET.get('auto', 'True')
434         if auto == 'True':
435             show_auto = True
436             subscriptions = QuestionSubscription.objects.filter(user=user).order_by('-last_view')
437         else:
438             show_auto = False
439             subscriptions = QuestionSubscription.objects.filter(user=user, auto_subscription=False).order_by('-last_view')
440
441         return pagination.paginated(request, ('subscriptions', SubscriptionListPaginatorContext()), {
442             'subscriptions':subscriptions,
443             'view_user':user,
444             "auto":show_auto,
445             'manage_open':manage_open,
446         })
447     # Settings Tab and everything else
448     else:
449         manage_open = False
450         if request.method == 'POST':
451             manage_open = False
452             form = SubscriptionSettingsForm(data=request.POST, instance=user.subscription_settings)
453
454             if form.is_valid():
455                 form.save()
456                 message = _('New subscription settings are now saved')
457
458                 user.subscription_settings.enable_notifications = enabled
459                 user.subscription_settings.save()
460
461                 messages.info(request, message)
462         else:
463             form = SubscriptionSettingsForm(instance=user.subscription_settings)
464
465         return {
466             'view_user':user,
467             'notificatons_on': enabled,
468             'form':form,
469             'manage_open':manage_open,
470         }
471
472 @user_view('users/preferences.html', 'preferences', _('preferences'), _('preferences'), True, tabbed=False)
473 def user_preferences(request, user, **kwargs):
474     if request.POST:
475         form = UserPreferencesForm(request.POST)
476
477         if form.is_valid():
478             user.prop.preferences = form.cleaned_data
479             messages.info(request, _('New preferences saved'))
480
481     else:
482         preferences = user.prop.preferences
483
484         if preferences:
485             form = UserPreferencesForm(initial=preferences)
486         else:
487             form = UserPreferencesForm()
488             
489     return {'view_user': user, 'form': form}
490
491