# encoding:utf-8 import datetime import logging from urllib import unquote from django.conf import settings from django.shortcuts import render_to_response, get_object_or_404 from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404 from django.core.paginator import Paginator, EmptyPage, InvalidPage from django.template import RequestContext from django.utils.html import * from django.utils import simplejson from django.db.models import Q from django.utils.translation import ugettext as _ from django.template.defaultfilters import slugify from django.core.urlresolvers import reverse from django.utils.datastructures import SortedDict from django.views.decorators.cache import cache_page from forum.utils.html import sanitize_html from markdown2 import Markdown #from lxml.html.diff import htmldiff from forum.utils.diff import textDiff as htmldiff from forum.forms import * from forum.models import * from forum.auth import * from forum.const import * from forum import const from forum import auth from forum.utils.forms import get_next_url # 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 # used in answers ANSWERS_PAGE_SIZE = 10 markdowner = Markdown(html4tags=True) #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 #refactor? - we have these #views that generate a listing of questions in one way or another: #index, unanswered, questions, search, tag #should we dry them up? #related topics - information drill-down, search refinement def index(request):#generates front page - shows listing of questions sorted in various ways """index view mapped to the root url of the Q&A site """ return HttpResponseRedirect(reverse('questions')) #todo: eliminate this from urls def unanswered(request):#generates listing of unanswered questions return questions(request, unanswered=True) def _get_and_stick(key, storage, defaults, data=None): """storage is request session in this case if a new value is coming from data, then storage will be updated this function assumes that data is clean """ if key in storage: value = storage[key] else: value = defaults[key] if data and key in data: new_value = data[key] if new_value != value: storage[key] = new_value return new_value else: return value def questions(request, tagname=None, unanswered=False):#a view generating listing of questions, used by 'unanswered' too """ List of Questions, Tagged questions, and Unanswered questions. """ #don't allow to post to this view if request.method == 'POST': raise Http404 #default values DV = { 'scope_selector': const.DEFAULT_POST_SCOPE, 'search_query': None, 'tag_selector': None, 'author_selector': None, 'question_sort_method': const.DEFAULT_POST_SORT_METHOD, 'page_size': const.QUESTIONS_PAGE_SIZE, 'page_number': 1, } form = AdvancedSearchForm(request) if form.is_valid(): d = form.cleaned_data s = request.session scope_selector = _get_and_stick('scope_selector', s, DV, d) search_query = _get_and_stick('search_query', s, DV, d) tag_selector = _get_and_stick('tag_selector', s, DV, d) author_selector = _get_and_stick('author_selector', s, DV, d) sort_method = _get_and_stick('question_sort_method', s, DV, d) page_size = _get_and_stick('page_size', s, DV, d) else: s = request.session scope_selector = _get_and_stick('scope_selector', s, DV) search_query = _get_and_stick('search_query', s, DV) tag_selector = _get_and_stick('tag_selector', s, DV) author_selector = _get_and_stick('author_selector', s, DV) sort_method = _get_and_stick('question_sort_method', s, DV) page_size = _get_and_stick('page_size', s, DV) #have this call implemented for sphinx, mysql and pgsql (questions, meta_data) = Question.objects.run_advanced_search( request_user = request.user, scope_selector = scope_selector,#unanswered/all/favorite (for logged in) search_query = search_query, tag_selector = tags, author_selector = author_selector,#???question or answer author or just contributor sort_method = sort_method# ) #maybe have this inside? questions = questions.select_related(depth=1).order_by(orderby) page = Paginator(questions).get_page(page_number) related_tags = Tag.objects.get_tags_by_questions(questions) objects_list = Paginator(qs, pagesize) questions = objects_list.page(page)#todo: is this right? tags_autocomplete = _get_tags_cache_json() #contributors = #User.objects.get_related_to_questions #todo make above form compatible with the template data #take whatever is missing from meta_data returned above return render_to_response('questions.html', { "questions" : questions, "author_name" : author_name, "tab_id" : view_id, "questions_count" : objects_list.count, "tags" : related_tags, "tags_autocomplete" : tags_autocomplete, "searchtag" : tagname, "is_unanswered" : unanswered, "interesting_tag_names": interesting_tag_names, 'ignored_tag_names': ignored_tag_names, "context" : { 'is_paginated' : True, 'pages': objects_list.num_pages, 'page': page, 'has_previous': questions.has_previous(), 'has_next': questions.has_next(), 'previous': questions.previous_page_number(), 'next': questions.next_page_number(), 'base_url' : request.path + '?sort=%s&' % view_id, 'pagesize' : pagesize }}, context_instance=RequestContext(request)) def fulltext(request): if request.method == "GET": keywords = request.GET.get("q") view_id = request.GET.get('sort', None) view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" } try: orderby = view_dic[view_id] except KeyError: view_id = "latest" orderby = "-added_at" def question_search(keywords, orderby): objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby) # RISK - inner join queries return objects.select_related(); from forum.modules import get_handler question_search = get_handler('question_search', question_search) objects = question_search(keywords, orderby) objects_list = Paginator(objects, pagesize) questions = objects_list.page(page) # Get related tags from this page objects related_tags = [] for question in questions.object_list: tags = list(question.tags.all()) for tag in tags: if tag not in related_tags: related_tags.append(tag) #if is_search is true in the context, prepend this string to soting tabs urls search_uri = "?q=%s&page=%d&t=question" % ("+".join(keywords.split()), page) return render_to_response(template_file, { "questions" : questions, "tab_id" : view_id, "questions_count" : objects_list.count, "tags" : related_tags, "searchtag" : None, "searchtitle" : keywords, "keywords" : keywords, "is_unanswered" : False, "is_search": True, "search_uri": search_uri, "context" : { 'is_paginated' : True, 'pages': objects_list.num_pages, 'page': page, 'has_previous': questions.has_previous(), 'has_next': questions.has_next(), 'previous': questions.previous_page_number(), 'next': questions.next_page_number(), 'base_url' : request.path + '?t=question&q=%s&sort=%s&' % (keywords, view_id), 'pagesize' : pagesize }}, context_instance=RequestContext(request)) else: raise Http404 def search(request): #generates listing of questions matching a search query - including tags and just words """redirects to people and tag search pages todo: eliminate this altogether and instead make search "tab" sensitive automatically - the radio-buttons are useless under the search bar """ if request.method == "GET": search_type == request.GET.get('t') try: page = int(request.GET.get('page', '1')) except ValueError: page = 1 if search_type == 'tag': return HttpResponseRedirect(reverse('tags') + '?q=%s&page=%s' % (keywords.strip(), page)) elif search_type == "user": return HttpResponseRedirect(reverse('users') + '?q=%s&page=%s' % (keywords.strip(), page)) else: raise Http404 else: raise Http404 def tag(request, tag):#stub generates listing of questions tagged with a single tag return questions(request, tagname=tag) def tags(request):#view showing a listing of available tags - plain list stag = "" is_paginated = True sortby = request.GET.get('sort', 'used') try: page = int(request.GET.get('page', '1')) except ValueError: page = 1 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) 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 question(request, id):#refactor - long subroutine. display question body, answers and comments """view that displays body of the question and all answers to it """ try: page = int(request.GET.get('page', '1')) except ValueError: page = 1 view_id = request.GET.get('sort', None) view_dic = {"latest":"-added_at", "oldest":"added_at", "votes":"-score" } try: orderby = view_dic[view_id] except KeyError: qsm = request.session.get('questions_sort_method',None) if qsm in ('mostvoted','latest'): logging.debug('loaded from session ' + qsm) if qsm == 'mostvoted': view_id = 'votes' orderby = '-score' else: view_id = 'latest' orderby = '-added_at' else: view_id = "votes" orderby = "-score" logging.debug('view_id=' + str(view_id)) question = get_object_or_404(Question, id=id) try: pattern = r'/%s%s%d/([\w-]+)' % (settings.FORUM_SCRIPT_ALIAS,_('question/'), question.id) path_re = re.compile(pattern) logging.debug(pattern) logging.debug(request.path) m = path_re.match(request.path) if m: slug = m.group(1) logging.debug('have slug %s' % slug) assert(slug == slugify(question.title)) else: logging.debug('no match!') except: return HttpResponseRedirect(question.get_absolute_url()) if question.deleted and not auth.can_view_deleted_post(request.user, question): raise Http404 answer_form = AnswerForm(question,request.user) answers = Answer.objects.get_answers_from_question(question, request.user) answers = answers.select_related(depth=1) favorited = question.has_favorite_by_user(request.user) if request.user.is_authenticated(): question_vote = question.votes.select_related().filter(user=request.user) else: question_vote = None #is this correct? if question_vote is not None and question_vote.count() > 0: question_vote = question_vote[0] user_answer_votes = {} for answer in answers: vote = answer.get_user_vote(request.user) if vote is not None and not user_answer_votes.has_key(answer.id): vote_value = -1 if vote.is_upvote(): vote_value = 1 user_answer_votes[answer.id] = vote_value if answers is not None: answers = answers.order_by("-accepted", orderby) filtered_answers = [] for answer in answers: if answer.deleted == True: if answer.author_id == request.user.id: filtered_answers.append(answer) else: filtered_answers.append(answer) objects_list = Paginator(filtered_answers, ANSWERS_PAGE_SIZE) page_objects = objects_list.page(page) #todo: merge view counts per user and per session #1) view count per session update_view_count = False if 'question_view_times' not in request.session: request.session['question_view_times'] = {} last_seen = request.session['question_view_times'].get(question.id,None) updated_when, updated_who = question.get_last_update_info() if updated_who != request.user: if last_seen: if last_seen < updated_when: update_view_count = True else: update_view_count = True request.session['question_view_times'][question.id] = datetime.datetime.now() if update_view_count: question.view_count += 1 question.save() #2) question view count per user if request.user.is_authenticated(): try: question_view = QuestionView.objects.get(who=request.user, question=question) except QuestionView.DoesNotExist: question_view = QuestionView(who=request.user, question=question) question_view.when = datetime.datetime.now() question_view.save() return render_to_response('question.html', { "question" : question, "question_vote" : question_vote, "question_comment_count":question.comments.count(), "answer" : answer_form, "answers" : page_objects.object_list, "user_answer_votes": user_answer_votes, "tags" : question.tags.all(), "tab_id" : view_id, "favorited" : favorited, "similar_questions" : Question.objects.get_similar_questions(question), "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)) QUESTION_REVISION_TEMPLATE = ('

%(title)s

\n' '
%(html)s
\n' '
%(tags)s
') def question_revisions(request, id): post = get_object_or_404(Question, id=id) revisions = list(post.revisions.all()) revisions.reverse() for i, revision in enumerate(revisions): revision.html = QUESTION_REVISION_TEMPLATE % { 'title': revision.title, 'html': sanitize_html(markdowner.convert(revision.text)), 'tags': ' '.join(['%s' % tag for tag in revision.tagnames.split(' ')]), } if i > 0: revisions[i].diff = htmldiff(revisions[i-1].html, revision.html) else: revisions[i].diff = QUESTION_REVISION_TEMPLATE % { 'title': revisions[0].title, 'html': sanitize_html(markdowner.convert(revisions[0].text)), 'tags': ' '.join(['%s' % tag for tag in revisions[0].tagnames.split(' ')]), } revisions[i].summary = _('initial version') return render_to_response('revisions_question.html', { 'post': post, 'revisions': revisions, }, context_instance=RequestContext(request)) ANSWER_REVISION_TEMPLATE = ('
%(html)s
') def answer_revisions(request, id): post = get_object_or_404(Answer, id=id) revisions = list(post.revisions.all()) revisions.reverse() for i, revision in enumerate(revisions): revision.html = ANSWER_REVISION_TEMPLATE % { 'html': sanitize_html(markdowner.convert(revision.text)) } if i > 0: revisions[i].diff = htmldiff(revisions[i-1].html, revision.html) else: revisions[i].diff = revisions[i].text revisions[i].summary = _('initial version') return render_to_response('revisions_answer.html', { 'post': post, 'revisions': revisions, }, context_instance=RequestContext(request))