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