]> git.openstreetmap.org Git - osqa.git/blob - forum/views/users.py
Fix OSQA-819: Link to profile in message is wrong if OSQA is installed in a subfolder
[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     user = get_object_or_404(User, id=id)
215
216     extra = dict(message=request.POST.get('message', ''), awarding_user=request.user.id, value=points)
217
218     BonusRepAction(user=request.user, extra=extra).save(data=dict(value=points, affected=user))
219
220     return {'commands': {
221             'update_profile_karma': [user.reputation]
222         }}
223     
224
225 @decorate.withfn(decorators.command)
226 def suspend(request, id):
227     user = get_object_or_404(User, id=id)
228
229     if not request.user.is_superuser:
230         raise decorators.CommandException(_("Only superusers can suspend other users"))
231
232     if not request.POST.get('bantype', None):
233         if user.is_suspended():
234             suspension = user.suspension
235             suspension.cancel(user=request.user, ip=request.META['REMOTE_ADDR'])
236             return decorators.RefreshPageCommand()
237         else:
238             return render_to_response('users/suspend_user.html')
239
240     data = {
241         'bantype': request.POST.get('bantype', 'Indefinitely').strip(),
242         'publicmsg': request.POST.get('publicmsg', _('Bad behaviour')),
243         'privatemsg': request.POST.get('privatemsg', None) or request.POST.get('publicmsg', ''),
244         'suspended': user
245     }
246
247     if data['bantype'] == 'forxdays':
248         try:
249             data['forxdays'] = int(request.POST['forxdays'])
250         except:
251             raise decorators.CommandException(_('Invalid numeric argument for the number of days.'))
252
253     SuspendAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=data)
254
255     return decorators.RefreshPageCommand()
256
257 @decorate.withfn(decorators.command)
258 def report_user(request, id):
259     user = get_object_or_404(User, id=id)
260
261     if not request.POST.get('publicmsg', None):
262         return render_to_response('users/report_user.html')
263
264     data = {
265         'publicmsg': request.POST.get('publicmsg', _('N/A')),
266         'reported': user
267     }
268
269     ReportAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=data)
270
271
272     return decorators.RefreshPageCommand()
273
274
275
276 def user_view(template, tab_name, tab_title, tab_description, private=False, tabbed=True, render_to=None, weight=500):
277     def decorator(fn):
278         def params(request, id=None, slug=None):
279             # Get the user object by id if the id parameter has been passed
280             if id is not None:
281                 user = get_object_or_404(User, id=id)
282             # ...or by slug if the slug has been given
283             elif slug is not None:
284                 try:
285                     user = User.objects.get(username__iexact=slug)
286                 except User.DoesNotExist:
287                     raise Http404
288
289             if private and not (user == request.user or request.user.is_superuser):
290                 raise ReturnImediatelyException(HttpResponseUnauthorized(request))
291
292             if render_to and (not render_to(user)):
293                 raise ReturnImediatelyException(HttpResponseRedirect(user.get_profile_url()))
294
295             return [request, user], { 'slug' : slug, }
296
297         decorated = decorate.params.withfn(params)(fn)
298
299         def result(context_or_response, request, user, **kwargs):
300             rev_page_title = smart_unicode(user.username) + " - " + tab_description
301
302             # Check whether the return type of the decorated function is a context or Http Response
303             if isinstance(context_or_response, HttpResponse):
304                 response = context_or_response
305
306                 # If it is a response -- show it
307                 return response
308             else:
309                 # ...if it is a context move forward, update it and render it to response
310                 context = context_or_response
311
312             context.update({
313                 "tab": "users",
314                 "active_tab" : tab_name,
315                 "tab_description" : tab_description,
316                 "page_title" : rev_page_title,
317                 "can_view_private": (user == request.user) or request.user.is_superuser
318             })
319             return render_to_response(template, context, context_instance=RequestContext(request))
320
321         decorated = decorate.result.withfn(result, needs_params=True)(decorated)
322
323         if tabbed:
324             def url_getter(vu):
325                 try:
326                     return reverse(fn.__name__, kwargs={'id': vu.id, 'slug': slugify(smart_unicode(vu.username))})
327                 except NoReverseMatch:
328                     try:
329                         return reverse(fn.__name__, kwargs={'id': vu.id})
330                     except NoReverseMatch:
331                         return reverse(fn.__name__, kwargs={'slug': slugify(smart_unicode(vu.username))})
332
333             ui.register(ui.PROFILE_TABS, ui.ProfileTab(
334                 tab_name, tab_title, tab_description,url_getter, private, render_to, weight
335             ))
336
337         return decorated
338     return decorator
339
340
341 @user_view('users/stats.html', 'stats', _('overview'), _('user overview'))
342 def user_profile(request, user, **kwargs):
343     questions = Question.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')
344     answers = Answer.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')
345
346     # Check whether the passed slug matches the one for the user object
347     slug = kwargs['slug']
348     if slug != slugify(smart_unicode(user.username)):
349         return HttpResponseRedirect(user.get_absolute_url())
350
351     up_votes = user.vote_up_count
352     down_votes = user.vote_down_count
353     votes_today = user.get_vote_count_today()
354     votes_total = user.can_vote_count_today()
355
356     user_tags = Tag.objects.filter(Q(nodes__author=user) | Q(nodes__children__author=user)) \
357         .annotate(user_tag_usage_count=Count('name')).order_by('-user_tag_usage_count')
358
359     awards = [(Badge.objects.get(id=b['id']), b['count']) for b in
360               Badge.objects.filter(awards__user=user).values('id').annotate(count=Count('cls')).order_by('-count')]
361
362     return pagination.paginated(request, (
363     ('questions', QuestionListPaginatorContext('USER_QUESTION_LIST', _('questions'), default_pagesize=15)),
364     ('answers', UserAnswersPaginatorContext())), {
365     "view_user" : user,
366     "questions" : questions,
367     "answers" : answers,
368     "up_votes" : up_votes,
369     "down_votes" : down_votes,
370     "total_votes": up_votes + down_votes,
371     "votes_today_left": votes_total-votes_today,
372     "votes_total_per_day": votes_total,
373     "user_tags" : user_tags[:50],
374     "awards": awards,
375     "total_awards" : len(awards),
376     })
377     
378 @user_view('users/recent.html', 'recent', _('recent activity'), _('recent user activity'))
379 def user_recent(request, user, **kwargs):
380     activities = user.actions.exclude(
381             action_type__in=("voteup", "votedown", "voteupcomment", "flag", "newpage", "editpage")).order_by(
382             '-action_date')[:USERS_PAGE_SIZE]
383
384     return {"view_user" : user, "activities" : activities}
385
386
387 @user_view('users/reputation.html', 'reputation', _('reputation history'), _('graph of user karma'))
388 def user_reputation(request, user, **kwargs):
389     rep = list(user.reputes.order_by('date'))
390     values = [r.value for r in rep]
391     redux = lambda x, y: x+y
392
393     graph_data = json.dumps([
394     (time.mktime(rep[i].date.timetuple()) * 1000, reduce(redux, values[:i+1], 0))
395     for i in range(len(values))
396     ])
397
398     rep = user.reputes.filter(action__canceled=False).order_by('-date')[0:20]
399
400     return {"view_user": user, "reputation": rep, "graph_data": graph_data}
401
402 @user_view('users/votes.html', 'votes', _('votes'), _('user vote record'), True)
403 def user_votes(request, user, **kwargs):
404     votes = user.votes.exclude(node__state_string__contains="(deleted").filter(
405             node__node_type__in=("question", "answer")).order_by('-voted_at')[:USERS_PAGE_SIZE]
406
407     return {"view_user" : user, "votes" : votes}
408
409 @user_view('users/questions.html', 'favorites', _('favorites'), _('questions that user selected as his/her favorite'))
410 def user_favorites(request, user, **kwargs):
411     favorites = FavoriteAction.objects.filter(canceled=False, user=user)
412
413     return {"favorites" : favorites, "view_user" : user}
414
415 @user_view('users/subscriptions.html', 'subscriptions', _('subscription'), _('subscriptions'), True, tabbed=False)
416 def user_subscriptions(request, user, **kwargs):
417     return _user_subscriptions(request, user, **kwargs)
418
419 def _user_subscriptions(request, user, **kwargs):
420     enabled = True
421
422     tab = request.GET.get('tab', "settings")
423
424     # Manage tab
425     if tab == 'manage':
426         manage_open = True
427
428         auto = request.GET.get('auto', 'True')
429         if auto == 'True':
430             show_auto = True
431             subscriptions = QuestionSubscription.objects.filter(user=user).order_by('-last_view')
432         else:
433             show_auto = False
434             subscriptions = QuestionSubscription.objects.filter(user=user, auto_subscription=False).order_by('-last_view')
435
436         return pagination.paginated(request, ('subscriptions', SubscriptionListPaginatorContext()), {
437             'subscriptions':subscriptions,
438             'view_user':user,
439             "auto":show_auto,
440             'manage_open':manage_open,
441         })
442     # Settings Tab and everything else
443     else:
444         manage_open = False
445         if request.method == 'POST':
446             manage_open = False
447             form = SubscriptionSettingsForm(data=request.POST, instance=user.subscription_settings)
448
449             if form.is_valid():
450                 form.save()
451                 message = _('New subscription settings are now saved')
452
453                 user.subscription_settings.enable_notifications = enabled
454                 user.subscription_settings.save()
455
456                 messages.info(request, message)
457         else:
458             form = SubscriptionSettingsForm(instance=user.subscription_settings)
459
460         return {
461             'view_user':user,
462             'notificatons_on': enabled,
463             'form':form,
464             'manage_open':manage_open,
465         }
466
467 @user_view('users/preferences.html', 'preferences', _('preferences'), _('preferences'), True, tabbed=False)
468 def user_preferences(request, user, **kwargs):
469     if request.POST:
470         form = UserPreferencesForm(request.POST)
471
472         if form.is_valid():
473             user.prop.preferences = form.cleaned_data
474             messages.info(request, _('New preferences saved'))
475
476     else:
477         preferences = user.prop.preferences
478
479         if preferences:
480             form = UserPreferencesForm(initial=preferences)
481         else:
482             form = UserPreferencesForm()
483             
484     return {'view_user': user, 'form': form}
485
486