]> git.openstreetmap.org Git - osqa.git/blob - forum/views/readers.py
4fbc6bcbb13bd56b4003e27890606e605e2894a7
[osqa.git] / forum / views / readers.py
1 # encoding:utf-8
2 import datetime
3 import logging
4 from urllib import unquote
5 from forum import settings as django_settings
6 from django.shortcuts import render_to_response, get_object_or_404
7 from django.http import HttpResponseRedirect, HttpResponse, Http404, HttpResponsePermanentRedirect
8 from django.core.paginator import Paginator, EmptyPage, InvalidPage
9 from django.template import RequestContext
10 from django import template
11 from django.utils.html import *
12 from django.utils import simplejson
13 from django.db.models import Q, Count
14 from django.utils.translation import ugettext as _
15 from django.template.defaultfilters import slugify
16 from django.core.urlresolvers import reverse
17 from django.utils.datastructures import SortedDict
18 from django.views.decorators.cache import cache_page
19 from django.utils.http import urlquote  as django_urlquote
20 from django.template.defaultfilters import slugify
21 from django.utils.safestring import mark_safe
22
23 from forum.utils.html import sanitize_html, hyperlink
24 from forum.utils.diff import textDiff as htmldiff
25 from forum.utils import pagination
26 from forum.forms import *
27 from forum.models import *
28 from forum.forms import get_next_url
29 from forum.actions import QuestionViewAction
30 from forum.http_responses import HttpResponseUnauthorized
31 from forum.feed import RssQuestionFeed, RssAnswerFeed
32 import decorators
33
34 class HottestQuestionsSort(pagination.SortBase):
35     def apply(self, questions):
36         return questions.annotate(new_child_count=Count('all_children')).filter(
37                 all_children__added_at__gt=datetime.datetime.now() - datetime.timedelta(days=1)).order_by('-new_child_count')
38
39
40 class QuestionListPaginatorContext(pagination.PaginatorContext):
41     def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=30):
42         super (QuestionListPaginatorContext, self).__init__(id, sort_methods=(
43             (_('active'), pagination.SimpleSort(_('active'), '-last_activity_at', _("Most <strong>recently updated</strong> questions"))),
44             (_('newest'), pagination.SimpleSort(_('newest'), '-added_at', _("most <strong>recently asked</strong> questions"))),
45             (_('hottest'), HottestQuestionsSort(_('hottest'), _("most <strong>active</strong> questions in the last 24 hours</strong>"))),
46             (_('mostvoted'), pagination.SimpleSort(_('most voted'), '-score', _("most <strong>voted</strong> questions"))),
47         ), pagesizes=(15, 30, 50), default_pagesize=default_pagesize, prefix=prefix)
48
49 class AnswerPaginatorContext(pagination.PaginatorContext):
50     def __init__(self, id='ANSWER_LIST', prefix='', default_pagesize=10):
51         super (AnswerPaginatorContext, self).__init__(id, sort_methods=(
52             (_('oldest'), pagination.SimpleSort(_('oldest answers'), ('-marked', 'added_at'), _("oldest answers will be shown first"))),
53             (_('newest'), pagination.SimpleSort(_('newest answers'), ('-marked', '-added_at'), _("newest answers will be shown first"))),
54             (_('votes'), pagination.SimpleSort(_('popular answers'), ('-marked', '-score', 'added_at'), _("most voted answers will be shown first"))),
55         ), default_sort=_('votes'), pagesizes=(5, 10, 20), default_pagesize=default_pagesize, prefix=prefix)
56
57 class TagPaginatorContext(pagination.PaginatorContext):
58     def __init__(self):
59         super (TagPaginatorContext, self).__init__('TAG_LIST', sort_methods=(
60             (_('name'), pagination.SimpleSort(_('by name'), 'name', _("sorted alphabetically"))),
61             (_('used'), pagination.SimpleSort(_('by popularity'), '-used_count', _("sorted by frequency of tag use"))),
62         ), default_sort=_('used'), pagesizes=(30, 60, 120))
63     
64
65 def feed(request):
66     return RssQuestionFeed(
67                 request,
68                 Question.objects.filter_state(deleted=False).order_by('-last_activity_at'),
69                 settings.APP_TITLE + _(' - ')+ _('latest questions'),
70                 settings.APP_DESCRIPTION)(request)
71
72
73 @decorators.render('index.html')
74 def index(request):
75     paginator_context = QuestionListPaginatorContext()
76     paginator_context.base_path = reverse('questions')
77     return question_list(request,
78                          Question.objects.all(),
79                          base_path=reverse('questions'),
80                          feed_url=reverse('latest_questions_feed'),
81                          paginator_context=paginator_context)
82
83 @decorators.render('questions.html', 'unanswered', _('unanswered'), weight=400)
84 def unanswered(request):
85     return question_list(request,
86                          Question.objects.filter(extra_ref=None),
87                          _('open questions without an accepted answer'),
88                          None,
89                          _("Unanswered Questions"))
90
91 @decorators.render('questions.html', 'questions', _('questions'), weight=0)
92 def questions(request):
93     return question_list(request, Question.objects.all(), _('questions'))
94
95 @decorators.render('questions.html')
96 def tag(request, tag):
97     return question_list(request,
98                          Question.objects.filter(tags__name=unquote(tag)),
99                          mark_safe(_('questions tagged <span class="tag">%(tag)s</span>') % {'tag': tag}),
100                          None,
101                          mark_safe(_('Questions Tagged With %(tag)s') % {'tag': tag}),
102                          False)
103
104 @decorators.render('questions.html', 'questions', tabbed=False)
105 def user_questions(request, mode, user, slug):
106     user = get_object_or_404(User, id=user)
107
108     if mode == _('asked-by'):
109         questions = Question.objects.filter(author=user)
110         description = _("Questions asked by %s")
111     elif mode == _('answered-by'):
112         questions = Question.objects.filter(children__author=user, children__node_type='answer').distinct()
113         description = _("Questions answered by %s")
114     elif mode == _('subscribed-by'):
115         if not (request.user.is_superuser or request.user == user):
116             return HttpResponseUnauthorized(request)
117         questions = user.subscriptions
118
119         if request.user == user:
120             description = _("Questions you subscribed %s")
121         else:
122             description = _("Questions subscribed by %s")
123     else:
124         raise Http404
125
126
127     return question_list(request, questions,
128                          mark_safe(description % hyperlink(user.get_profile_url(), user.username)),
129                          page_title=description % user.username)
130
131 def question_list(request, initial,
132                   list_description=_('questions'),
133                   base_path=None,
134                   page_title=_("All Questions"),
135                   allowIgnoreTags=True,
136                   feed_url=None,
137                   paginator_context=None):
138
139     questions = initial.filter_state(deleted=False)
140
141     if request.user.is_authenticated() and allowIgnoreTags:
142         questions = questions.filter(~Q(tags__id__in = request.user.marked_tags.filter(user_selections__reason = 'bad')))
143
144     if page_title is None:
145         page_title = _("Questions")
146
147     if request.GET.get('type', None) == 'rss':
148         questions = questions.order_by('-added_at')
149         return RssQuestionFeed(request, questions, page_title, list_description)(request)
150
151     keywords =  ""
152     if request.GET.get("q"):
153         keywords = request.GET.get("q").strip()
154
155     answer_count = Answer.objects.filter_state(deleted=False).filter(parent__in=questions).count()
156     answer_description = _("answers")
157
158     if not feed_url:
159         req_params = "&".join(["%s=%s" % (k, v) for k, v in request.GET.items() if not k in (_('page'), _('pagesize'), _('sort'))])
160         if req_params:
161             req_params = '&' + req_params
162
163         feed_url = mark_safe(request.path + "?type=rss" + req_params)
164
165     return pagination.paginated(request, ('questions', paginator_context or QuestionListPaginatorContext()), {
166     "questions" : questions,
167     "questions_count" : questions.count(),
168     "keywords" : keywords,
169     "list_description": list_description,
170     "base_path" : base_path,
171     "page_title" : page_title,
172     "tab" : "questions",
173     'feed_url': feed_url,
174     })
175
176
177 def search(request):
178     if request.method == "GET" and "q" in request.GET:
179         keywords = request.GET.get("q")
180         search_type = request.GET.get("t")
181
182         if not keywords:
183             return HttpResponseRedirect(reverse(index))
184         if search_type == 'tag':
185             return HttpResponseRedirect(reverse('tags') + '?q=%s' % urlquote(keywords.strip()))
186         elif search_type == "user":
187             return HttpResponseRedirect(reverse('users') + '?q=%s' % urlquote(keywords.strip()))
188         elif search_type == "question":
189             return question_search(request, keywords)
190     else:
191         return render_to_response("search.html", context_instance=RequestContext(request))
192
193 @decorators.render('questions.html')
194 def question_search(request, keywords):
195     can_rank, initial = Question.objects.search(keywords)
196
197     if can_rank:
198         paginator_context = QuestionListPaginatorContext()
199         paginator_context.sort_methods[_('ranking')] = pagination.SimpleSort(_('relevance'), '-ranking', _("most relevant questions"))
200         paginator_context.force_sort = _('ranking')
201     else:
202         paginator_context = None
203
204     return question_list(request, initial,
205                          _("questions matching '%(keywords)s'") % {'keywords': keywords},
206                          None,
207                          _("questions matching '%(keywords)s'") % {'keywords': keywords},
208                          paginator_context=paginator_context)
209
210
211 @decorators.render('tags.html', 'tags', _('tags'), weight=100)
212 def tags(request):
213     stag = ""
214     tags = Tag.active.all()
215
216     if request.method == "GET":
217         stag = request.GET.get("q", "").strip()
218         if stag:
219             tags = tags.filter(name__contains=stag)
220
221     return pagination.paginated(request, ('tags', TagPaginatorContext()), {
222         "tags" : tags,
223         "stag" : stag,
224         "keywords" : stag
225     })
226
227 def update_question_view_times(request, question):
228     if not 'last_seen_in_question' in request.session:
229         request.session['last_seen_in_question'] = {}
230
231     last_seen = request.session['last_seen_in_question'].get(question.id, None)
232
233     if (not last_seen) or last_seen < question.last_activity_at:
234         QuestionViewAction(question, request.user, ip=request.META['REMOTE_ADDR']).save()
235         request.session['last_seen_in_question'][question.id] = datetime.datetime.now()
236
237     request.session['last_seen_in_question'][question.id] = datetime.datetime.now()
238
239 def match_question_slug(slug):
240     slug_words = slug.split('-')
241     qs = Question.objects.filter(title__istartswith=slug_words[0])
242
243     for q in qs:
244         if slug == urlquote(slugify(q.title)):
245             return q
246
247     return None
248
249 def answer_redirect(request, answer):
250     pc = AnswerPaginatorContext()
251
252     sort = pc.sort(request)
253
254     if sort == _('oldest'):
255         filter = Q(added_at__lt=answer.added_at)
256     elif sort == _('newest'):
257         filter = Q(added_at__gt=answer.added_at)
258     elif sort == _('votes'):
259         filter = Q(score__gt=answer.score) | Q(score=answer.score, added_at__lt=answer.added_at)
260     else:
261         raise Http404()
262         
263     count = answer.question.answers.filter(Q(marked=True) | filter).count()
264     pagesize = pc.pagesize(request)
265
266     page = count / pagesize
267     
268     if count % pagesize:
269         page += 1
270         
271     if page == 0:
272         page = 1
273
274     return HttpResponsePermanentRedirect("%s?%s=%s#%s" % (
275         answer.question.get_absolute_url(), _('page'), page, answer.id))
276
277 @decorators.render("question.html", 'questions')
278 def question(request, id, slug='', answer=None):
279     try:
280         question = Question.objects.get(id=id)
281     except:
282         if slug:
283             question = match_question_slug(slug)
284             if question is not None:
285                 return HttpResponseRedirect(question.get_absolute_url())
286
287         raise Http404()
288
289     if question.nis.deleted and not request.user.can_view_deleted_post(question):
290         raise Http404
291
292     if request.GET.get('type', None) == 'rss':
293         return RssAnswerFeed(request, question, include_comments=request.GET.get('comments', None) == 'yes')(request)
294
295     if answer:
296         answer = get_object_or_404(Answer, id=answer)
297
298         if (question.nis.deleted and not request.user.can_view_deleted_post(question)) or answer.question != question:
299             raise Http404
300
301         if answer.marked:
302             return HttpResponsePermanentRedirect(question.get_absolute_url())
303
304         return answer_redirect(request, answer)
305
306     if settings.FORCE_SINGLE_URL and (slug != slugify(question.title)):
307         return HttpResponsePermanentRedirect(question.get_absolute_url())
308
309     if request.POST:
310         answer_form = AnswerForm(question, request.POST)
311     else:
312         answer_form = AnswerForm(question)
313
314     answers = request.user.get_visible_answers(question)
315
316     update_question_view_times(request, question)
317
318     if request.user.is_authenticated():
319         try:
320             subscription = QuestionSubscription.objects.get(question=question, user=request.user)
321         except:
322             subscription = False
323     else:
324         subscription = False
325
326     return pagination.paginated(request, ('answers', AnswerPaginatorContext()), {
327     "question" : question,
328     "answer" : answer_form,
329     "answers" : answers,
330     "similar_questions" : question.get_related_questions(),
331     "subscription": subscription,
332     })
333
334
335 REVISION_TEMPLATE = template.loader.get_template('node/revision.html')
336
337 def revisions(request, id):
338     post = get_object_or_404(Node, id=id).leaf
339     revisions = list(post.revisions.order_by('revised_at'))
340     rev_ctx = []
341
342     for i, revision in enumerate(revisions):
343         rev_ctx.append(dict(inst=revision, html=template.loader.get_template('node/revision.html').render(template.Context({
344         'title': revision.title,
345         'html': revision.html,
346         'tags': revision.tagname_list(),
347         }))))
348
349         if i > 0:
350             rev_ctx[i]['diff'] = mark_safe(htmldiff(rev_ctx[i-1]['html'], rev_ctx[i]['html']))
351         else:
352             rev_ctx[i]['diff'] = mark_safe(rev_ctx[i]['html'])
353
354         if not (revision.summary):
355             rev_ctx[i]['summary'] = _('Revision n. %(rev_number)d') % {'rev_number': revision.revision}
356         else:
357             rev_ctx[i]['summary'] = revision.summary
358
359     rev_ctx.reverse()
360
361     return render_to_response('revisions.html', {
362     'post': post,
363     'revisions': rev_ctx,
364     }, context_instance=RequestContext(request))
365
366
367