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