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