]> git.openstreetmap.org Git - osqa.git/blob - forum/views/commands.py
OSQA - 19
[osqa.git] / forum / views / commands.py
1 import datetime
2 from django.conf import settings
3 from django.core.exceptions import ObjectDoesNotExist
4 from django.utils import simplejson
5 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
6 from django.shortcuts import get_object_or_404, render_to_response
7 from django.utils.translation import ungettext, ugettext as _
8 from django.template import RequestContext
9 from forum.models import *
10 from forum.forms import CloseForm
11 from django.core.urlresolvers import reverse
12 from django.contrib.auth.decorators import login_required
13 from forum.utils.decorators import ajax_method, ajax_login_required
14 from decorators import command
15 import logging
16
17 class NotEnoughRepPointsException(Exception):
18     def __init__(self, action):
19         super(NotEnoughRepPointsException, self).__init__(
20             _("""
21             Sorry, but you don't have enough reputation points to %(action)s.<br />
22             Please check the <a href'%(faq_url)s'>faq</a>
23             """ % {'action': action, 'faq_url': reverse('faq')})
24         )
25
26 class CannotDoOnOwnException(Exception):
27     def __init__(self, action):
28         super(CannotDoOnOwnException, self).__init__(
29             _("""
30             Sorry but you cannot %(action)s your own post.<br />
31             Please check the <a href'%(faq_url)s'>faq</a>
32             """ % {'action': action, 'faq_url': reverse('faq')})
33         )
34
35 class AnonymousNotAllowedException(Exception):
36     def __init__(self, action):
37         super(AnonymousNotAllowedException, self).__init__(
38             _("""
39             Sorry but anonymous users cannot %(action)s.<br />
40             Please login or create an account <a href'%(signin_url)s'>here</a>.
41             """ % {'action': action, 'signin_url': reverse('auth_signin')})
42         )
43
44 class SpamNotAllowedException(Exception):
45     def __init__(self, action = "comment"):
46         super(SpamNotAllowedException, self).__init__(
47             _("""Your %s has been marked as spam.""" % action)
48         )
49
50 class NotEnoughLeftException(Exception):
51     def __init__(self, action, limit):
52         super(NotEnoughLeftException, self).__init__(
53             _("""
54             Sorry, but you don't have enough %(action)s left for today..<br />
55             The limit is %(limit)s per day..<br />
56             Please check the <a href'%(faq_url)s'>faq</a>
57             """ % {'action': action, 'limit': limit, 'faq_url': reverse('faq')})
58         )
59
60 class CannotDoubleActionException(Exception):
61     def __init__(self, action):
62         super(CannotDoubleActionException, self).__init__(
63             _("""
64             Sorry, but you cannot %(action)s twice the same post.<br />
65             Please check the <a href'%(faq_url)s'>faq</a>
66             """ % {'action': action, 'faq_url': reverse('faq')})
67         )
68
69
70 @command
71 def vote_post(request, id, vote_type):
72     post = get_object_or_404(Node, id=id).leaf
73     vote_score = vote_type == 'up' and 1 or -1
74     user = request.user
75
76     if not user.is_authenticated():
77         raise AnonymousNotAllowedException(_('vote'))
78
79     if user == post.author:
80         raise CannotDoOnOwnException(_('vote'))
81
82     if not (vote_type == 'up' and user.can_vote_up() or user.can_vote_down()):
83         raise NotEnoughRepPointsException(vote_type == 'up' and _('upvote') or _('downvote'))
84
85     user_vote_count_today = user.get_vote_count_today()
86
87     if user_vote_count_today >= int(settings.MAX_VOTES_PER_DAY):
88         raise NotEnoughLeftException(_('votes'), str(settings.MAX_VOTES_PER_DAY))
89
90     try:
91         vote = post.votes.get(canceled=False, user=user)
92
93         if vote.voted_at < datetime.datetime.now() - datetime.timedelta(days=int(settings.DENY_UNVOTE_DAYS)):
94             raise Exception(
95                     _("Sorry but you cannot cancel a vote after %(ndays)d %(tdays)s from the original vote") %
96                     {'ndays': int(settings.DENY_UNVOTE_DAYS), 'tdays': ungettext('day', 'days', int(settings.DENY_UNVOTE_DAYS))}
97             )
98
99         vote.cancel()
100         vote_type = 'none'
101     except ObjectDoesNotExist:
102         #there is no vote yet
103         vote = Vote(user=user, node=post, vote=vote_score)
104         vote.save()
105
106     response = {
107         'commands': {
108             'update_post_score': [id, vote.vote * (vote_type == 'none' and -1 or 1)],
109             'update_user_post_vote': [id, vote_type]
110         }
111     }
112
113     votes_left = int(settings.MAX_VOTES_PER_DAY) - user_vote_count_today + (vote_type == 'none' and -1 or 1)
114
115     if int(settings.START_WARN_VOTES_LEFT) >= votes_left:
116         response['message'] = _("You have %(nvotes)s %(tvotes)s left today.") % \
117                     {'nvotes': votes_left, 'tvotes': ungettext('vote', 'votes', votes_left)}
118
119     return response
120
121 @command
122 def flag_post(request, id):
123     post = get_object_or_404(Node, id=id)
124     user = request.user
125
126     if not user.is_authenticated():
127         raise AnonymousNotAllowedException(_('flag posts'))
128
129     if user == post.author:
130         raise CannotDoOnOwnException(_('flag'))
131
132     if not (user.can_flag_offensive(post)):
133         raise NotEnoughRepPointsException(_('flag posts'))
134
135     user_flag_count_today = user.get_flagged_items_count_today()
136
137     if user_flag_count_today >= int(settings.MAX_FLAGS_PER_DAY):
138         raise NotEnoughLeftException(_('flags'), str(settings.MAX_FLAGS_PER_DAY))
139
140     try:
141         post.flaggeditems.get(user=user)
142         raise CannotDoubleActionException(_('flag'))
143     except ObjectDoesNotExist:
144         flag = FlaggedItem(user=user, content_object=post)
145         flag.save()
146
147     return {}
148         
149 @command
150 def like_comment(request, id):
151     comment = get_object_or_404(Comment, id=id)
152     user = request.user
153
154     if not user.is_authenticated():
155         raise AnonymousNotAllowedException(_('like comments'))
156
157     if user == comment.user:
158         raise CannotDoOnOwnException(_('like'))
159
160     if not user.can_like_comment(comment):
161         raise NotEnoughRepPointsException( _('like comments'))    
162
163     try:
164         like = LikedComment.active.get(comment=comment, user=user)
165         like.cancel()
166         likes = False
167     except ObjectDoesNotExist:
168         like = LikedComment(comment=comment, user=user)
169         like.save()
170         likes = True
171
172     return {
173         'commands': {
174             'update_comment_score': [comment.id, likes and 1 or -1],
175             'update_likes_comment_mark': [comment.id, likes and 'on' or 'off']
176         }
177     }
178
179 @command
180 def delete_comment(request, id):
181     comment = get_object_or_404(Comment, id=id)
182     user = request.user
183
184     if not user.is_authenticated():
185         raise AnonymousNotAllowedException(_('delete comments'))
186
187     if not user.can_delete_comment(comment):
188         raise NotEnoughRepPointsException( _('delete comments'))
189
190     comment.mark_deleted(user)
191
192     return {
193         'commands': {
194             'remove_comment': [comment.id],
195         }
196     }
197
198 @command
199 def mark_favorite(request, id):
200     question = get_object_or_404(Question, id=id)
201
202     if not request.user.is_authenticated():
203         raise AnonymousNotAllowedException(_('mark a question as favorite'))
204
205     try:
206         favorite = FavoriteQuestion.objects.get(question=question, user=request.user)
207         favorite.delete()
208         added = False
209     except ObjectDoesNotExist:
210         favorite = FavoriteQuestion(question=question, user=request.user)
211         favorite.save()
212         added = True
213
214     return {
215         'commands': {
216             'update_favorite_count': [added and 1 or -1],
217             'update_favorite_mark': [added and 'on' or 'off']
218         }
219     }
220
221 @command
222 def comment(request, id):
223     post = get_object_or_404(Node, id=id)
224     user = request.user
225
226     if not user.is_authenticated():
227         raise AnonymousNotAllowedException(_('comment'))
228
229     if not request.method == 'POST':
230         raise Exception(_("Invalid request"))
231
232     if 'id' in request.POST:
233         comment = get_object_or_404(Comment, id=request.POST['id'])
234
235         if not user.can_edit_comment(comment):
236             raise NotEnoughRepPointsException( _('edit comments'))
237     else:
238         if not user.can_comment(post):
239             raise NotEnoughRepPointsException( _('comment'))
240
241         comment = Comment(parent=post)
242
243     comment_text = request.POST.get('comment', '').strip()
244
245     if not len(comment_text):
246         raise Exception(_("Comment is empty"))
247
248     if not len(comment_text) > settings.FORM_MIN_COMMENT_BODY:
249         raise Exception(_("Comment must be at least %s characters" % settings.FORM_MIN_COMMENT_BODY))
250
251     comment.create_revision(user, body=comment_text)
252
253     data = {
254         "user_ip":request.META["REMOTE_ADDR"],
255         "user_agent":request.environ['HTTP_USER_AGENT'],
256         "comment_author":request.user.real_name,
257         "comment_author_email":request.user.email,
258         "comment_author_url":request.user.website,
259         "comment":comment_text
260     }
261     if Node.isSpam(comment_text, data):
262         raise SpamNotAllowedException()
263
264     if comment.active_revision.revision == 1:
265         return {
266             'commands': {
267                 'insert_comment': [
268                     id, comment.id, comment_text, user.username, user.get_profile_url(), reverse('delete_comment', kwargs={'id': comment.id})
269                 ]
270             }
271         }
272     else:
273         return {
274             'commands': {
275                 'update_comment': [comment.id, comment.comment]
276             }
277         }
278
279
280 @command
281 def accept_answer(request, id):
282     user = request.user
283
284     if not user.is_authenticated():
285         raise AnonymousNotAllowedException(_('accept answers'))
286
287     answer = get_object_or_404(Answer, id=id)
288     question = answer.question
289
290     if not user.can_accept_answer(answer):
291         raise Exception(_("Sorry but only the question author can accept an answer"))
292
293     commands = {}
294
295     if answer.accepted:
296         answer.unmark_accepted(user)
297         commands['unmark_accepted'] = [answer.id]
298     else:
299         if question.accepted_answer is not None:
300             accepted = question.accepted_answer
301             accepted.unmark_accepted(user)
302             commands['unmark_accepted'] = [accepted.id]
303
304         answer.mark_accepted(user)
305         commands['mark_accepted'] = [answer.id]
306
307     return {'commands': commands}
308
309 @command    
310 def delete_post(request, id):
311     post = get_object_or_404(Node, id=id)
312     user = request.user
313
314     if not user.is_authenticated():
315         raise AnonymousNotAllowedException(_('delete posts'))
316
317     if not (user.can_delete_post(post)):
318         raise NotEnoughRepPointsException(_('delete posts'))
319
320     post.mark_deleted(user)
321
322     return {
323         'commands': {
324                 'mark_deleted': [post.node_type, id]
325             }
326     }
327
328 @command
329 def subscribe(request, id):
330     question = get_object_or_404(Question, id=id)
331
332     try:
333         subscription = QuestionSubscription.objects.get(question=question, user=request.user)
334         subscription.delete()
335         subscribed = False
336     except:
337         subscription = QuestionSubscription(question=question, user=request.user, auto_subscription=False)
338         subscription.save()
339         subscribed = True
340
341     return {
342         'commands': {
343                 'set_subscription_button': [subscribed and _('unsubscribe me') or _('subscribe me')],
344                 'set_subscription_status': ['']
345             }
346     }
347
348 #internally grouped views - used by the tagging system
349 @ajax_login_required
350 def mark_tag(request, tag=None, **kwargs):#tagging system
351     action = kwargs['action']
352     ts = MarkedTag.objects.filter(user=request.user, tag__name=tag)
353     if action == 'remove':
354         logging.debug('deleting tag %s' % tag)
355         ts.delete()
356     else:
357         reason = kwargs['reason']
358         if len(ts) == 0:
359             try:
360                 t = Tag.objects.get(name=tag)
361                 mt = MarkedTag(user=request.user, reason=reason, tag=t)
362                 mt.save()
363             except:
364                 pass
365         else:
366             ts.update(reason=reason)
367     return HttpResponse(simplejson.dumps(''), mimetype="application/json")
368
369 def matching_tags(request):
370     if len(request.GET['q']) == 0:
371        raise Exception(_("Invalid request"))
372
373     possible_tags = Tag.objects.filter(name__istartswith = request.GET['q'])
374     tag_output = ''
375     for tag in possible_tags:
376         tag_output += (tag.name + "|" + tag.name + "." + tag.used_count.__str__() + "\n")
377         
378     return HttpResponse(tag_output, mimetype="text/plain")
379
380 @ajax_login_required
381 def ajax_toggle_ignored_questions(request):#ajax tagging and tag-filtering system
382     if request.user.hide_ignored_questions:
383         new_hide_setting = False
384     else:
385         new_hide_setting = True
386     request.user.hide_ignored_questions = new_hide_setting
387     request.user.save()
388
389 @ajax_method
390 def ajax_command(request):#refactor? view processing ajax commands - note "vote" and view others do it too
391     if 'command' not in request.POST:
392         return HttpResponseForbidden(mimetype="application/json")
393     if request.POST['command'] == 'toggle-ignored-questions':
394         return ajax_toggle_ignored_questions(request)
395
396 @login_required
397 def close(request, id):#close question
398     """view to initiate and process 
399     question close
400     """
401     question = get_object_or_404(Question, id=id)
402     if not request.user.can_close_question(question):
403         return HttpResponseForbidden()
404     if request.method == 'POST':
405         form = CloseForm(request.POST)
406         if form.is_valid():
407             reason = form.cleaned_data['reason']
408             question.closed = True
409             question.closed_by = request.user
410             question.closed_at = datetime.datetime.now()
411             question.close_reason = reason
412             question.save()
413         return HttpResponseRedirect(question.get_absolute_url())
414     else:
415         form = CloseForm()
416         return render_to_response('close.html', {
417             'form' : form,
418             'question' : question,
419             }, context_instance=RequestContext(request))
420
421 @login_required
422 def reopen(request, id):#re-open question
423     """view to initiate and process 
424     question close
425     """
426     question = get_object_or_404(Question, id=id)
427     # open question
428     if not request.user.can_reopen_question(question):
429         return HttpResponseForbidden()
430     if request.method == 'POST' :
431         Question.objects.filter(id=question.id).update(closed=False,
432             closed_by=None, closed_at=None, close_reason=None)
433         return HttpResponseRedirect(question.get_absolute_url())
434     else:
435         return render_to_response('reopen.html', {
436             'question' : question,
437             }, context_instance=RequestContext(request))
438
439 #osqa-user communication system
440 def read_message(request):#marks message a read
441     if request.method == "POST":
442         if request.POST['formdata'] == 'required':
443             request.session['message_silent'] = 1
444             if request.user.is_authenticated():
445                 request.user.delete_messages()
446     return HttpResponse('')
447
448