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