]> git.openstreetmap.org Git - osqa.git/blob - forum/views/readers.py
7bfd75af2a34f054e7a9810c81128d4fd15bc332
[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     return HttpResponsePermanentRedirect("%s?%s=%s#%s" % (
308         answer.question.get_absolute_url(), _('page'), page, answer.id))
309
310 @decorators.render("question.html", 'questions', tabbed=False)
311 def question(request, id, slug, answer=None):
312     try:
313         question = Question.objects.get(id=id)
314     except:
315         if slug:
316             question = match_question_slug(slug)
317             if question is not None:
318                 return HttpResponseRedirect(question.get_absolute_url())
319
320         raise Http404()
321
322     if question.nis.deleted and not request.user.can_view_deleted_post(question):
323         raise Http404
324
325     if answer:
326         answer = get_object_or_404(Answer, id=answer)
327
328         if (question.nis.deleted and not request.user.can_view_deleted_post(question)) or answer.question != question:
329             raise Http404
330
331         if answer.marked:
332             return HttpResponsePermanentRedirect(question.get_absolute_url())
333
334         return answer_redirect(request, answer)
335
336     if request.POST:
337         answer_form = AnswerForm(question, request.POST)
338     else:
339         answer_form = AnswerForm(question)
340
341     answers = request.user.get_visible_answers(question)
342
343     update_question_view_times(request, question)
344
345     if request.user.is_authenticated():
346         try:
347             subscription = QuestionSubscription.objects.get(question=question, user=request.user)
348         except:
349             subscription = False
350     else:
351         subscription = False
352
353     return pagination.paginated(request, 'answers', AnswerPaginatorContext(), {
354     "question" : question,
355     "answer" : answer_form,
356     "answers" : answers,
357     "similar_questions" : question.get_related_questions(),
358     "subscription": subscription,
359     })
360
361
362 REVISION_TEMPLATE = template.loader.get_template('node/revision.html')
363
364 def revisions(request, id):
365     post = get_object_or_404(Node, id=id).leaf
366     revisions = list(post.revisions.order_by('revised_at'))
367     rev_ctx = []
368
369     for i, revision in enumerate(revisions):
370         rev_ctx.append(dict(inst=revision, html=template.loader.get_template('node/revision.html').render(template.Context({
371         'title': revision.title,
372         'html': revision.html,
373         'tags': revision.tagname_list(),
374         }))))
375
376         if i > 0:
377             rev_ctx[i]['diff'] = mark_safe(htmldiff(rev_ctx[i-1]['html'], rev_ctx[i]['html']))
378         else:
379             rev_ctx[i]['diff'] = mark_safe(rev_ctx[i]['html'])
380
381         if not (revision.summary):
382             rev_ctx[i]['summary'] = _('Revision n. %(rev_number)d') % {'rev_number': revision.revision}
383         else:
384             rev_ctx[i]['summary'] = revision.summary
385
386     rev_ctx.reverse()
387
388     return render_to_response('revisions.html', {
389     'post': post,
390     'revisions': rev_ctx,
391     }, context_instance=RequestContext(request))
392
393
394