]> git.openstreetmap.org Git - osqa.git/blobdiff - forum/views/readers.py
Prevents questions on which the slug comes out empty to enter a redirect loop.
[osqa.git] / forum / views / readers.py
index 31efdcef52fb9be453de1f32592e612f3cb8ce64..6a490876506c59acaf03e944444d7a6acc3a7511 100644 (file)
@@ -2,9 +2,9 @@
 import datetime
 import logging
 from urllib import unquote
 import datetime
 import logging
 from urllib import unquote
-from django.conf import settings as django_settings
+from forum import settings as django_settings
 from django.shortcuts import render_to_response, get_object_or_404
 from django.shortcuts import render_to_response, get_object_or_404
-from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404, HttpResponsePermanentRedirect
+from django.http import HttpResponseRedirect, HttpResponse, Http404, HttpResponsePermanentRedirect
 from django.core.paginator import Paginator, EmptyPage, InvalidPage
 from django.template import RequestContext
 from django import template
 from django.core.paginator import Paginator, EmptyPage, InvalidPage
 from django.template import RequestContext
 from django import template
@@ -20,99 +20,172 @@ from django.utils.http import urlquote  as django_urlquote
 from django.template.defaultfilters import slugify
 from django.utils.safestring import mark_safe
 
 from django.template.defaultfilters import slugify
 from django.utils.safestring import mark_safe
 
-from forum.utils.html import sanitize_html
+from forum.utils.html import sanitize_html, hyperlink
 from forum.utils.diff import textDiff as htmldiff
 from forum.utils.diff import textDiff as htmldiff
+from forum.utils import pagination
 from forum.forms import *
 from forum.models import *
 from forum.forms import *
 from forum.models import *
-from forum.const import *
-from forum.utils.forms import get_next_url
-from forum.models.question import question_view
+from forum.forms import get_next_url
+from forum.actions import QuestionViewAction
+from forum.http_responses import HttpResponseUnauthorized
+from forum.feed import RssQuestionFeed, RssAnswerFeed
 import decorators
 
 import decorators
 
-# used in index page
-#refactor - move these numbers somewhere?
-INDEX_PAGE_SIZE = 30
-INDEX_AWARD_SIZE = 15
-INDEX_TAGS_SIZE = 25
-# used in tags list
-DEFAULT_PAGE_SIZE = 60
-# used in questions
-QUESTIONS_PAGE_SIZE = 30
-# used in answers
-ANSWERS_PAGE_SIZE = 10
-
-#system to display main content
-def _get_tags_cache_json():#service routine used by views requiring tag list in the javascript space
-    """returns list of all tags in json format
-    no caching yet, actually
-    """
-    tags = Tag.objects.filter(deleted=False).all()
-    tags_list = []
-    for tag in tags:
-        dic = {'n': tag.name, 'c': tag.used_count}
-        tags_list.append(dic)
-    tags = simplejson.dumps(tags_list)
-    return tags
+class QuestionListPaginatorContext(pagination.PaginatorContext):
+    def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=30):
+        super (QuestionListPaginatorContext, self).__init__(id, sort_methods=(
+            (_('active'), pagination.SimpleSort(_('active'), '-last_activity_at', _("most recently updated questions"))),
+            (_('newest'), pagination.SimpleSort(_('newest'), '-added_at', _("most recently asked questions"))),
+            (_('hottest'), pagination.SimpleSort(_('hottest'), '-extra_count', _("hottest questions"))),
+            (_('mostvoted'), pagination.SimpleSort(_('most voted'), '-score', _("most voted questions"))),
+        ), pagesizes=(15, 30, 50), default_pagesize=default_pagesize, prefix=prefix)
+
+class AnswerPaginatorContext(pagination.PaginatorContext):
+    def __init__(self, id='ANSWER_LIST', prefix='', default_pagesize=10):
+        super (AnswerPaginatorContext, self).__init__(id, sort_methods=(
+            (_('oldest'), pagination.SimpleSort(_('oldest answers'), ('-marked', 'added_at'), _("oldest answers will be shown first"))),
+            (_('newest'), pagination.SimpleSort(_('newest answers'), ('-marked', '-added_at'), _("newest answers will be shown first"))),
+            (_('votes'), pagination.SimpleSort(_('popular answers'), ('-marked', '-score', 'added_at'), _("most voted answers will be shown first"))),
+        ), default_sort=_('votes'), pagesizes=(5, 10, 20), default_pagesize=default_pagesize, prefix=prefix)
+
+class TagPaginatorContext(pagination.PaginatorContext):
+    def __init__(self):
+        super (TagPaginatorContext, self).__init__('TAG_LIST', sort_methods=(
+            (_('name'), pagination.SimpleSort(_('by name'), 'name', _("sorted alphabetically"))),
+            (_('used'), pagination.SimpleSort(_('by popularity'), '-used_count', _("sorted by frequency of tag use"))),
+        ), default_sort=_('used'), pagesizes=(30, 60, 120))
+    
+
+def feed(request):
+    return RssQuestionFeed(
+                request,
+                Question.objects.filter_state(deleted=False).order_by('-last_activity_at'),
+                settings.APP_TITLE + _(' - ')+ _('latest questions'),
+                settings.APP_DESCRIPTION)(request)
+
 
 @decorators.render('index.html')
 def index(request):
 
 @decorators.render('index.html')
 def index(request):
-    return question_list(request, Question.objects.all(), sort='active', base_path=reverse('questions'))
-
-@decorators.render('questions.html', 'unanswered')
+    paginator_context = QuestionListPaginatorContext()
+    paginator_context.base_path = reverse('questions')
+    return question_list(request,
+                         Question.objects.all(),
+                         sort=request.utils.set_sort_method('active'),
+                         base_path=reverse('questions'),
+                         feed_url=reverse('latest_questions_feed'),
+                         paginator_context=paginator_context)
+
+@decorators.render('questions.html', 'unanswered', _('unanswered'), weight=400)
 def unanswered(request):
 def unanswered(request):
-    return question_list(request, Question.objects.filter(accepted_answer=None),
-                         _('Open questions without an accepted answer'))
-
-@decorators.render('questions.html', 'questions')
+    return question_list(request,
+                         Question.objects.filter(extra_ref=None),
+                         _('open questions without an accepted answer'),
+                         request.utils.set_sort_method('active'),
+                         None,
+                         _("Unanswered Questions"))
+
+@decorators.render('questions.html', 'questions', _('questions'), weight=0)
 def questions(request):
 def questions(request):
-    return question_list(request, Question.objects.all())
+    return question_list(request, Question.objects.all(), _('questions'), request.utils.set_sort_method('active'))
 
 @decorators.render('questions.html')
 def tag(request, tag):
 
 @decorators.render('questions.html')
 def tag(request, tag):
-    return question_list(request, Question.objects.filter(tags__name=unquote(tag)),
-                        mark_safe(_('Questions tagged <span class="tag">%(tag)s</span>') % {'tag': tag}))
+    return question_list(request,
+                         Question.objects.filter(tags__name=unquote(tag)),
+                         mark_safe(_('questions tagged <span class="tag">%(tag)s</span>') % {'tag': tag}),
+                         request.utils.set_sort_method('active'),
+                         None,
+                         mark_safe(_('Questions Tagged With %(tag)s') % {'tag': tag}),
+                         False)
+
+@decorators.render('questions.html', 'questions', tabbed=False)
+def user_questions(request, mode, user, slug):
+    user = get_object_or_404(User, id=user)
+
+    if mode == _('asked-by'):
+        questions = Question.objects.filter(author=user)
+        description = _("Questions asked by %s")
+    elif mode == _('answered-by'):
+        questions = Question.objects.filter(children__author=user, children__node_type='answer').distinct()
+        description = _("Questions answered by %s")
+    elif mode == _('subscribed-by'):
+        if not (request.user.is_superuser or request.user == user):
+            return HttpResponseUnauthorized(request)
+        questions = user.subscriptions
+
+        if request.user == user:
+            description = _("Questions you subscribed %s")
+        else:
+            description = _("Questions subscribed by %s")
+    else:
+        raise Http404
 
 
-@decorators.list('questions', QUESTIONS_PAGE_SIZE)
-def question_list(request, initial, list_description=_('questions'), sort=None, base_path=None):
-    pagesize = request.utils.page_size(QUESTIONS_PAGE_SIZE)
-    page = int(request.GET.get('page', 1))
 
 
-    questions = initial.filter(deleted=False)
+    return question_list(request, questions,
+                         mark_safe(description % hyperlink(user.get_profile_url(), user.username)),
+                         request.utils.set_sort_method('active'),
+                         page_title=description % user.username)
 
 
-    if request.user.is_authenticated():
-        questions = questions.filter(
-                ~Q(tags__id__in=request.user.marked_tags.filter(user_selections__reason='bad')))
+def question_list(request, initial,
+                  list_description=_('questions'),
+                  sort=None,
+                  base_path=None,
+                  page_title=_("All Questions"),
+                  allowIgnoreTags=True,
+                  feed_url=None,
+                  paginator_context=None):
 
 
-    if sort is not False:
-        if sort is None:
-            sort = request.utils.sort_method('latest')
-        else:
-            request.utils.set_sort_method(sort)
+    questions = initial.filter_state(deleted=False)
+
+    if request.user.is_authenticated() and allowIgnoreTags:
+        questions = questions.filter(~Q(tags__id__in = request.user.marked_tags.filter(user_selections__reason = 'bad')))
+
+    if page_title is None:
+        page_title = _("Questions")
+
+    if request.GET.get('type', None) == 'rss':
+        questions = questions.order_by('-added_at')
+        return RssQuestionFeed(request, questions, page_title, list_description)(request)
 
 
-        view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+    keywords =  ""
+    if request.GET.get("q"):
+        keywords = request.GET.get("q").strip()
 
 
-        questions=questions.order_by(view_dic.get(sort, '-added_at'))
+    answer_count = Answer.objects.filter_state(deleted=False).filter(parent__in=questions).count()
+    answer_description = _("answers")
 
 
-    return {
-        "questions" : questions,
-        "questions_count" : questions.count(),
-        "tags_autocomplete" : _get_tags_cache_json(),
-        "list_description": list_description,
-        "base_path" : base_path,
-        }
+    if not feed_url:
+        req_params = "&".join(["%s=%s" % (k, v) for k, v in request.GET.items() if not k in (_('page'), _('pagesize'), _('sort'))])
+        if req_params:
+            req_params = '&' + req_params
+
+        feed_url = mark_safe(request.path + "?type=rss" + req_params)
+
+    return pagination.paginated(request, ('questions', paginator_context or QuestionListPaginatorContext()), {
+    "questions" : questions,
+    "questions_count" : questions.count(),
+    "answer_count" : answer_count,
+    "keywords" : keywords,
+    "list_description": list_description,
+    "answer_description": answer_description,
+    "base_path" : base_path,
+    "page_title" : page_title,
+    "tab" : "questions",
+    'feed_url': feed_url,
+    })
 
 
 def search(request):
     if request.method == "GET" and "q" in request.GET:
         keywords = request.GET.get("q")
         search_type = request.GET.get("t")
 
 
 def search(request):
     if request.method == "GET" and "q" in request.GET:
         keywords = request.GET.get("q")
         search_type = request.GET.get("t")
-        
+
         if not keywords:
             return HttpResponseRedirect(reverse(index))
         if search_type == 'tag':
         if not keywords:
             return HttpResponseRedirect(reverse(index))
         if search_type == 'tag':
-            return HttpResponseRedirect(reverse('tags') + '?q=%s' % (keywords.strip()))
+            return HttpResponseRedirect(reverse('tags') + '?q=%s' % urlquote(keywords.strip()))
         elif search_type == "user":
         elif search_type == "user":
-            return HttpResponseRedirect(reverse('users') + '?q=%s' % (keywords.strip()))
+            return HttpResponseRedirect(reverse('users') + '?q=%s' % urlquote(keywords.strip()))
         elif search_type == "question":
             return question_search(request, keywords)
     else:
         elif search_type == "question":
             return question_search(request, keywords)
     else:
@@ -120,87 +193,54 @@ def search(request):
 
 @decorators.render('questions.html')
 def question_search(request, keywords):
 
 @decorators.render('questions.html')
 def question_search(request, keywords):
-    def question_search(keywords):
-        return Question.objects.filter(Q(title__icontains=keywords) | Q(body__icontains=keywords))
+    can_rank, initial = Question.objects.search(keywords)
 
 
-    from forum.modules import get_handler
+    if can_rank:
+        paginator_context = QuestionListPaginatorContext()
+        paginator_context.sort_methods[_('ranking')] = pagination.SimpleSort(_('relevance'), '-ranking', _("most relevant questions"))
+        paginator_context.force_sort = _('ranking')
+    else:
+        paginator_context = None
 
 
-    question_search = get_handler('question_search', question_search)
-    initial = question_search(keywords)
+    return question_list(request, initial,
+                         _("questions matching '%(keywords)s'") % {'keywords': keywords},
+                         False,
+                         "%s?t=question&q=%s" % (reverse('search'),django_urlquote(keywords)),
+                         _("questions matching '%(keywords)s'") % {'keywords': keywords},
+                         paginator_context=paginator_context)
 
 
-    return question_list(request, initial, _("questions matching '%(keywords)s'") % {'keywords': keywords},
-            base_path="%s?t=question&q=%s" % (reverse('search'), django_urlquote(keywords)), sort=False)
-    
 
 
-def tags(request):#view showing a listing of available tags - plain list
+@decorators.render('tags.html', 'tags', _('tags'), weight=100)
+def tags(request):
     stag = ""
     stag = ""
-    is_paginated = True
-    sortby = request.GET.get('sort', 'used')
-    try:
-        page = int(request.GET.get('page', '1'))
-    except ValueError:
-        page = 1
+    tags = Tag.active.all()
 
     if request.method == "GET":
         stag = request.GET.get("q", "").strip()
 
     if request.method == "GET":
         stag = request.GET.get("q", "").strip()
-        if stag != '':
-            objects_list = Paginator(Tag.objects.filter(deleted=False).exclude(used_count=0).extra(where=['name like %s'], params=['%' + stag + '%']), DEFAULT_PAGE_SIZE)
-        else:
-            if sortby == "name":
-                objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("name"), DEFAULT_PAGE_SIZE)
-            else:
-                objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-used_count"), DEFAULT_PAGE_SIZE)
+        if stag:
+            tags = tags.filter(name__contains=stag)
 
 
-    try:
-        tags = objects_list.page(page)
-    except (EmptyPage, InvalidPage):
-        tags = objects_list.page(objects_list.num_pages)
-
-    return render_to_response('tags.html', {
-                                            "tags" : tags,
-                                            "stag" : stag,
-                                            "tab_id" : sortby,
-                                            "keywords" : stag,
-                                            "context" : {
-                                                'is_paginated' : is_paginated,
-                                                'pages': objects_list.num_pages,
-                                                'page': page,
-                                                'has_previous': tags.has_previous(),
-                                                'has_next': tags.has_next(),
-                                                'previous': tags.previous_page_number(),
-                                                'next': tags.next_page_number(),
-                                                'base_url' : reverse('tags') + '?sort=%s&' % sortby
-                                            }
-                                }, context_instance=RequestContext(request))
-
-def get_answer_sort_order(request):
-    view_dic = {"latest":"-added_at", "oldest":"added_at", "votes":"-score" }
-
-    view_id = request.GET.get('sort', request.session.get('answer_sort_order', None))
-
-    if view_id is None or not view_id in view_dic:
-        view_id = "votes"
-
-    if view_id != request.session.get('answer_sort_order', None):
-        request.session['answer_sort_order'] = view_id
-
-    return (view_id, view_dic[view_id])
+    return pagination.paginated(request, ('tags', TagPaginatorContext()), {
+        "tags" : tags,
+        "stag" : stag,
+        "keywords" : stag
+    })
 
 def update_question_view_times(request, question):
     if not 'last_seen_in_question' in request.session:
         request.session['last_seen_in_question'] = {}
 
 
 def update_question_view_times(request, question):
     if not 'last_seen_in_question' in request.session:
         request.session['last_seen_in_question'] = {}
 
-    last_seen = request.session['last_seen_in_question'].get(question.id,None)
+    last_seen = request.session['last_seen_in_question'].get(question.id, None)
 
     if (not last_seen) or last_seen < question.last_activity_at:
 
     if (not last_seen) or last_seen < question.last_activity_at:
-        question_view.send(sender=update_question_view_times, instance=question, user=request.user)
+        QuestionViewAction(question, request.user, ip=request.META['REMOTE_ADDR']).save()
         request.session['last_seen_in_question'][question.id] = datetime.datetime.now()
 
     request.session['last_seen_in_question'][question.id] = datetime.datetime.now()
 
 def match_question_slug(slug):
     slug_words = slug.split('-')
         request.session['last_seen_in_question'][question.id] = datetime.datetime.now()
 
     request.session['last_seen_in_question'][question.id] = datetime.datetime.now()
 
 def match_question_slug(slug):
     slug_words = slug.split('-')
-    qs = Question.objects.filter(node_type="question", title__istartswith=slug_words[0])
+    qs = Question.objects.filter(title__istartswith=slug_words[0])
 
     for q in qs:
         if slug == urlquote(slugify(q.title)):
 
     for q in qs:
         if slug == urlquote(slugify(q.title)):
@@ -208,34 +248,72 @@ def match_question_slug(slug):
 
     return None
 
 
     return None
 
-def question(request, id, slug):
+def answer_redirect(request, answer):
+    pc = AnswerPaginatorContext()
+
+    sort = pc.sort(request)
+
+    if sort == _('oldest'):
+        filter = Q(added_at__lt=answer.added_at)
+    elif sort == _('newest'):
+        filter = Q(added_at__gt=answer.added_at)
+    elif sort == _('votes'):
+        filter = Q(score__gt=answer.score) | Q(score=answer.score, added_at__lt=answer.added_at)
+    else:
+        raise Http404()
+        
+    count = answer.question.answers.filter(Q(marked=True) | filter).count()
+    pagesize = pc.pagesize(request)
+
+    page = count / pagesize
+    
+    if count % pagesize:
+        page += 1
+        
+    if page == 0:
+        page = 1
+
+    return HttpResponsePermanentRedirect("%s?%s=%s#%s" % (
+        answer.question.get_absolute_url(), _('page'), page, answer.id))
+
+@decorators.render("question.html", 'questions')
+def question(request, id, slug='', answer=None):
     try:
     try:
-        question = Question.objects.get(node_type="question", id=id)
+        question = Question.objects.get(id=id)
     except:
     except:
-        question = match_question_slug(slug)
-        if question is not None:
-            return HttpResponsePermanentRedirect(question.get_absolute_url())
-        else:
-            raise Http404()
+        if slug:
+            question = match_question_slug(slug)
+            if question is not None:
+                return HttpResponseRedirect(question.get_absolute_url())
 
 
-    if slug != urlquote(slugify(question.title)):
-        return HttpResponsePermanentRedirect(question.get_absolute_url())
-
-    page = int(request.GET.get('page', 1))
-    view_id, order_by = get_answer_sort_order(request)
+        raise Http404()
 
 
-    if question.deleted and not request.user.can_view_deleted_post(question):
+    if question.nis.deleted and not request.user.can_view_deleted_post(question):
         raise Http404
 
         raise Http404
 
-    answer_form = AnswerForm(question)
-    answers = request.user.get_visible_answers(question)
+    if request.GET.get('type', None) == 'rss':
+        return RssAnswerFeed(request, question, include_comments=request.GET.get('comments', None) == 'yes')(request)
 
 
-    if answers is not None:
-        answers = [a for a in answers.order_by("-accepted", order_by)
-                   if not a.deleted or a.author == request.user]
+    if answer:
+        answer = get_object_or_404(Answer, id=answer)
 
 
-    objects_list = Paginator(answers, ANSWERS_PAGE_SIZE)
-    page_objects = objects_list.page(page)
+        if (question.nis.deleted and not request.user.can_view_deleted_post(question)) or answer.question != question:
+            raise Http404
+
+        if answer.marked:
+            return HttpResponsePermanentRedirect(question.get_absolute_url())
+
+        return answer_redirect(request, answer)
+
+    if settings.FORCE_SINGLE_URL and (slug != slugify(question.title)):
+        return HttpResponsePermanentRedirect(question.get_absolute_url())
+
+    if request.POST:
+        answer_form = AnswerForm(question, request.POST)
+    else:
+        answer_form = AnswerForm(question)
+
+    answers = request.user.get_visible_answers(question)
 
     update_question_view_times(request, question)
 
 
     update_question_view_times(request, question)
 
@@ -247,26 +325,13 @@ def question(request, id, slug):
     else:
         subscription = False
 
     else:
         subscription = False
 
-    return render_to_response('question.html', {
-        "question" : question,
-        "answer" : answer_form,
-        "answers" : page_objects.object_list,
-        "tags" : question.tags.all(),
-        "tab_id" : view_id,
-        "similar_questions" : question.get_related_questions(),
-        "subscription": subscription,
-        "context" : {
-            'is_paginated' : True,
-            'pages': objects_list.num_pages,
-            'page': page,
-            'has_previous': page_objects.has_previous(),
-            'has_next': page_objects.has_next(),
-            'previous': page_objects.previous_page_number(),
-            'next': page_objects.next_page_number(),
-            'base_url' : request.path + '?sort=%s&' % view_id,
-            'extend_url' : "#sort-top"
-        }
-        }, context_instance=RequestContext(request))
+    return pagination.paginated(request, ('answers', AnswerPaginatorContext()), {
+    "question" : question,
+    "answer" : answer_form,
+    "answers" : answers,
+    "similar_questions" : question.get_related_questions(),
+    "subscription": subscription,
+    })
 
 
 REVISION_TEMPLATE = template.loader.get_template('node/revision.html')
 
 
 REVISION_TEMPLATE = template.loader.get_template('node/revision.html')
@@ -274,14 +339,13 @@ REVISION_TEMPLATE = template.loader.get_template('node/revision.html')
 def revisions(request, id):
     post = get_object_or_404(Node, id=id).leaf
     revisions = list(post.revisions.order_by('revised_at'))
 def revisions(request, id):
     post = get_object_or_404(Node, id=id).leaf
     revisions = list(post.revisions.order_by('revised_at'))
-
     rev_ctx = []
 
     for i, revision in enumerate(revisions):
     rev_ctx = []
 
     for i, revision in enumerate(revisions):
-        rev_ctx.append(dict(inst=revision, html=REVISION_TEMPLATE.render(template.Context({
-                'title': revision.title,
-                'html': revision.html,
-                'tags': revision.tagname_list(),
+        rev_ctx.append(dict(inst=revision, html=template.loader.get_template('node/revision.html').render(template.Context({
+        'title': revision.title,
+        'html': revision.html,
+        'tags': revision.tagname_list(),
         }))))
 
         if i > 0:
         }))))
 
         if i > 0:
@@ -293,11 +357,13 @@ def revisions(request, id):
             rev_ctx[i]['summary'] = _('Revision n. %(rev_number)d') % {'rev_number': revision.revision}
         else:
             rev_ctx[i]['summary'] = revision.summary
             rev_ctx[i]['summary'] = _('Revision n. %(rev_number)d') % {'rev_number': revision.revision}
         else:
             rev_ctx[i]['summary'] = revision.summary
-            
-    return render_to_response('revisions_question.html', {
-                              'post': post,
-                              'revisions': rev_ctx,
-                              }, context_instance=RequestContext(request))
+
+    rev_ctx.reverse()
+
+    return render_to_response('revisions.html', {
+    'post': post,
+    'revisions': rev_ctx,
+    }, context_instance=RequestContext(request))