diff options
-rw-r--r-- | PENDING | 31 | ||||
-rw-r--r-- | TODO | 3 | ||||
-rw-r--r-- | WISH_LIST | 10 | ||||
-rw-r--r-- | forum/skins/default/media/js/com.cnprog.utils.js | 2 | ||||
-rw-r--r-- | forum/skins/default/media/js/wmd/wmd.js | 2 | ||||
-rwxr-xr-x | forum/skins/default/templates/base.html | 4 | ||||
-rw-r--r-- | forum/urls.py | 70 | ||||
-rw-r--r-- | forum/views/README | 12 | ||||
-rw-r--r-- | forum/views/__init__.py | 4 | ||||
-rw-r--r-- | forum/views/commands.py | 334 | ||||
-rw-r--r-- | forum/views/meta.py | 8 | ||||
-rw-r--r-- | forum/views/readers.py | 577 | ||||
-rw-r--r-- | forum/views/users.py | 163 | ||||
-rw-r--r-- | forum/views/views.py | 2412 | ||||
-rw-r--r-- | forum/views/writers.py | 441 |
15 files changed, 1531 insertions, 2542 deletions
diff --git a/PENDING b/PENDING new file mode 100644 index 00000000..9d2f00ee --- /dev/null +++ b/PENDING @@ -0,0 +1,31 @@ +There are two kinds of things that can be done: +refactorings (think of jogging in the morning, going to a spa, well make the code better :) +new features (go to law school, get a job, do something real) +Just a joke - pick yourself a task and work on it. + +==Refactoring== +* analyze and split models.py --> models/ +* create forum/modules directory +* make modules load into the forum app like they + are the integral part of the forum +* set up loading of default settings from inside the /forum dir +* automatic dependency checking for modules +* propose how to rename directory forum --> osqa + without breaking things and keeping name of the project root + named the same way - osqa + +==New features== +Whoever wants - pick a feature from the WISH_LIST +add it here and start working on it +If you are not starting immediately - leave it on the wishlist :) + +==Notes== +1)after this is done most new suggested features + may be worked on easily since most of them + only require editing view functions and templates + + However, anyone can work on new features anyway - you'll + just have to probably copy-paste your code into + the branch undergoing refactoring which involves + splitting the files. Auto merging across split points + is harder or impossible. @@ -1,3 +0,0 @@ -* per-tag email subscriptions
-* make sorting tabs work in question search - done
-* allow multiple logins to the same account
diff --git a/WISH_LIST b/WISH_LIST new file mode 100644 index 00000000..6b106875 --- /dev/null +++ b/WISH_LIST @@ -0,0 +1,10 @@ +* The wonder bar (integrated the search / ask functionality)
+* The authentication system ???
+* allow multiple logins to the same account
+* more advanced templating/skinning system
+* per-tag email subscriptions
+* view for personalized news on the site
+* a little flag popping when there are news
+* drill-down mode for navigation by tags
+* improved admin console
+* sort out mess with profile - currently we patch django User
diff --git a/forum/skins/default/media/js/com.cnprog.utils.js b/forum/skins/default/media/js/com.cnprog.utils.js index 93083288..3f7720c1 100644 --- a/forum/skins/default/media/js/com.cnprog.utils.js +++ b/forum/skins/default/media/js/com.cnprog.utils.js @@ -41,7 +41,7 @@ var notify = function() { function appendLoader(containerSelector) { $(containerSelector).append('<img class="ajax-loader" ' + - 'src="mediaUrl("media/images/indicator.gif")" title="' + + 'src="' + mediaUrl("media/images/indicator.gif") + '" title="' + $.i18n._('loading...') + '" alt="' + $.i18n._('loading...') + diff --git a/forum/skins/default/media/js/wmd/wmd.js b/forum/skins/default/media/js/wmd/wmd.js index 9888cda0..b5f1c9e0 100644 --- a/forum/skins/default/media/js/wmd/wmd.js +++ b/forum/skins/default/media/js/wmd/wmd.js @@ -54,7 +54,7 @@ Attacklab.wmdBase = function(){ var uploadImageHTML ="<div>" + $.i18n._('upload image') + "</div>" + '<input type="file" name="file-upload" id="file-upload" size="26" '+ 'onchange="return ajaxFileUpload($("#image-url"));"/><br>' + - '<img id="loading" src="mediaUrl("media/images/indicator.gif")" style="display:none;"/>'; + '<img id="loading" src="' + mediaUrl("media/images/indicator.gif") + '" style="display:none;"/>'; // The default text that appears in the dialog input box when entering // links. diff --git a/forum/skins/default/templates/base.html b/forum/skins/default/templates/base.html index 9033b0c9..3a1848ef 100755 --- a/forum/skins/default/templates/base.html +++ b/forum/skins/default/templates/base.html @@ -18,9 +18,11 @@ <script src="http://www.google.com/jsapi" type="text/javascript"></script> <script type="text/javascript">google.load("jquery", "1.2.6");</script> <script type="text/javascript"> + /* <![CDATA[ */ var i18nLang = '{{settings.LANGUAGE_CODE}}'; var scriptUrl = '/{{settings.FORUM_SCRIPT_ALIAS}}' var osqaSkin = '{{settings.OSQA_SKIN}}'; + /* ]] */ </script> <script type='text/javascript' src='{% media "/media/js/com.cnprog.i18n.js" %}'></script> <script type='text/javascript' src='{% media "/media/js/jquery.i18n.js" %}'></script> @@ -30,10 +32,12 @@ body { margin-top:2.4em; } </style> <script type="text/javascript"> + /* <![CDATA[ */ $(document).ready(function() { $('#validate_email_alert').click(function(){notify.close(true)}) notify.show(); }); + /* ]] */ </script> {% endif %} diff --git a/forum/urls.py b/forum/urls.py index 7fa03706..219364cb 100644 --- a/forum/urls.py +++ b/forum/urls.py @@ -17,57 +17,59 @@ sitemaps = { APP_PATH = os.path.dirname(__file__) urlpatterns = patterns('', - url(r'^$', app.content.index, name='index'), - url(r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}), + url(r'^$', app.readers.index, name='index'), + url(r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}, name='sitemap'), #(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/media/images/favicon.ico'}), #(r'^favicon\.gif$', 'django.views.generic.simple.redirect_to', {'url': '/media/images/favicon.gif'}), - (r'^m/(?P<path>.*)$', 'django.views.static.serve', - {'document_root': os.path.join(APP_PATH,'skins').replace('\\','/')} + url(r'^m/(?P<path>.*)$', 'django.views.static.serve', + {'document_root': os.path.join(APP_PATH,'skins').replace('\\','/')}, + name='osqa_media', ), - (r'^%s(?P<path>.*)$' % _('upfiles/'), 'django.views.static.serve', - {'document_root': os.path.join(APP_PATH,'upfiles').replace('\\','/')} + url(r'^%s(?P<path>.*)$' % _('upfiles/'), 'django.views.static.serve', + {'document_root': os.path.join(APP_PATH,'upfiles').replace('\\','/')}, + name='uploaded_file', ), - (r'^%s/$' % _('signin/'), 'django_authopenid.views.signin'), + url(r'^%s/$' % _('signin/'), 'django_authopenid.views.signin', name='signin'), url(r'^%s$' % _('about/'), app.meta.about, name='about'), url(r'^%s$' % _('faq/'), app.meta.faq, name='faq'), url(r'^%s$' % _('privacy/'), app.meta.privacy, name='privacy'), url(r'^%s$' % _('logout/'), app.meta.logout, name='logout'), - url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('comments/')), app.content.answer_comments, name='answer_comments'), - url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('edit/')), app.content.edit_answer, name='edit_answer'), - url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('revisions/')), app.content.answer_revisions, name='answer_revisions'), - url(r'^%s$' % _('questions/'), app.content.questions, name='questions'), - url(r'^%s%s$' % (_('questions/'), _('ask/')), app.content.ask, name='ask'), - url(r'^%s%s$' % (_('questions/'), _('unanswered/')), app.content.unanswered, name='unanswered'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')), app.content.edit_question, name='edit_question'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')), app.content.close, name='close'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')), app.content.reopen, name='reopen'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')), app.content.answer, name='answer'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('vote/')), app.content.vote, name='vote'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')), app.content.question_revisions, name='question_revisions'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('comments/')), app.content.question_comments, name='question_comments'), - url(r'^%s$' % _('command/'), app.content.ajax_command, name='call_ajax'), + url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('comments/')), app.writers.answer_comments, name='answer_comments'), + url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('edit/')), app.writers.edit_answer, name='edit_answer'), + url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('revisions/')), app.readers.answer_revisions, name='answer_revisions'), + url(r'^%s$' % _('questions/'), app.readers.questions, name='questions'), + url(r'^%s%s$' % (_('questions/'), _('ask/')), app.writers.ask, name='ask'), + url(r'^%s%s$' % (_('questions/'), _('unanswered/')), app.readers.unanswered, name='unanswered'), + url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')), app.writers.edit_question, name='edit_question'), + url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')), app.commands.close, name='close'), + url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')), app.commands.reopen, name='reopen'), + url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')), app.writers.answer, name='answer'), + url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('vote/')), app.commands.vote, name='vote'), + url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')), app.readers.question_revisions, name='question_revisions'), + url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('comments/')), app.writers.question_comments, name='question_comments'), + url(r'^%s$' % _('command/'), app.commands.ajax_command, name='call_ajax'), url(r'^%s(?P<object_id>\d+)/%s(?P<comment_id>\d+)/%s$' % (_('questions/'), _('comments/'),_('delete/')), \ - app.content.delete_comment, kwargs={'commented_object_type':'question'},\ + app.writers.delete_comment, kwargs={'commented_object_type':'question'},\ name='delete_question_comment'), url(r'^%s(?P<object_id>\d+)/%s(?P<comment_id>\d+)/%s$' % (_('answers/'), _('comments/'),_('delete/')), \ - app.content.delete_comment, kwargs={'commented_object_type':'answer'}, \ + app.writers.delete_comment, kwargs={'commented_object_type':'answer'}, \ name='delete_answer_comment'), \ #place general question item in the end of other operations - url(r'^%s(?P<id>\d+)/' % _('question/'), app.content.question, name='question'), - url(r'^%s$' % _('tags/'), app.content.tags, name='tags'), - url(r'^%s(?P<tag>[^/]+)/$' % _('tags/'), app.content.tag, name='tag_questions'), + url(r'^%s(?P<id>\d+)/' % _('question/'), app.readers.question, name='question'), + url(r'^%s$' % _('tags/'), app.readers.tags, name='tags'), + url(r'^%s(?P<tag>[^/]+)/$' % _('tags/'), app.readers.tag, name='tag_questions'), - url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('interesting/')), app.content.mark_tag, \ + url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('interesting/')), app.commands.mark_tag, \ kwargs={'reason':'good','action':'add'}, \ name='mark_interesting_tag'), - url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('ignored/')), app.content.mark_tag, \ + url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('ignored/')), app.commands.mark_tag, \ kwargs={'reason':'bad','action':'add'}, \ name='mark_ignored_tag'), - url(r'^%s(?P<tag>[^/]+)/$' % _('unmark-tag/'), app.content.mark_tag, \ + url(r'^%s(?P<tag>[^/]+)/$' % _('unmark-tag/'), app.commands.mark_tag, \ kwargs={'action':'remove'}, \ name='mark_ignored_tag'), @@ -77,15 +79,15 @@ urlpatterns = patterns('', url(r'^%s(?P<id>\d+)//*' % _('users/'), app.users.user, name='user'), url(r'^%s$' % _('badges/'),app.meta.badges, name='badges'), url(r'^%s(?P<id>\d+)//*' % _('badges/'), app.meta.badge, name='badge'), - url(r'^%s%s$' % (_('messages/'), _('markread/')),app.meta.read_message, name='read_message'), + url(r'^%s%s$' % (_('messages/'), _('markread/')),app.commands.read_message, name='read_message'), # (r'^admin/doc/' % _('admin/doc'), include('django.contrib.admindocs.urls')), - (r'^%s(.*)' % _('nimda/'), admin.site.root), - url(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}), - (r'^%s$' % _('upload/'), app.content.upload), + url(r'^%s(.*)' % _('nimda/'), admin.site.root, name='osqa_admin'), + url(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}, name='feeds'), + url(r'^%s$' % _('upload/'), app.writers.upload, name='upload'), #url(r'^%s$' % _('books/'), app.books.books, name='books'), #url(r'^%s%s(?P<short_name>[^/]+)/$' % (_('books/'), _('ask/')), app.books.ask_book, name='ask_book'), #url(r'^%s(?P<short_name>[^/]+)/$' % _('books/'), app.books.book, name='book'), - url(r'^%s$' % _('search/'), app.content.search, name='search'), + url(r'^%s$' % _('search/'), app.readers.search, name='search'), url(r'^%s$' % _('feedback/'), app.meta.feedback, name='feedback'), (r'^%sfb/' % _('account/'), include('fbconnect.urls')), (r'^%s' % _('account/'), include('django_authopenid.urls')), diff --git a/forum/views/README b/forum/views/README new file mode 100644 index 00000000..7b6201cd --- /dev/null +++ b/forum/views/README @@ -0,0 +1,12 @@ +readers.py - views strictly reading main content: questions, answers, tags and comments + +writers.py - views that write main content, with possible reading + note: deletion counts as writing in this case + +commands.py - data status changing commands, votes, question close/reopen + +users.py - user views - user listing and profiles + +meta.py - privacy, about, faq, feedback, logout, badges + +books.py - book views - to be moved to a books extension diff --git a/forum/views/__init__.py b/forum/views/__init__.py index 9c112c9b..faea1179 100644 --- a/forum/views/__init__.py +++ b/forum/views/__init__.py @@ -1,4 +1,6 @@ -import content +import readers +import writers +import commands import users import meta #import books diff --git a/forum/views/commands.py b/forum/views/commands.py new file mode 100644 index 00000000..38915728 --- /dev/null +++ b/forum/views/commands.py @@ -0,0 +1,334 @@ +import datetime +from django.conf import settings +from django.utils import simplejson +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.utils.translation import ugettext as _ +from django.template import RequestContext +from forum.models import * +from forum.forms import CloseForm +from forum import auth +from django.contrib.auth.decorators import login_required +from utils.decorators import ajax_method, ajax_login_required +import logging + +def vote(request, id):#refactor - pretty incomprehensible view used by various ajax calls +#issues: this subroutine is too long, contains many magic numbers and other issues +#it's called "vote" but many actions processed here have nothing to do with voting + """ + vote_type: + acceptAnswer : 0, + questionUpVote : 1, + questionDownVote : 2, + favorite : 4, + answerUpVote: 5, + answerDownVote:6, + offensiveQuestion : 7, + offensiveAnswer:8, + removeQuestion: 9, + removeAnswer:10 + questionSubscribeUpdates:11 + questionUnSubscribeUpdates:12 + + accept answer code: + response_data['allowed'] = -1, Accept his own answer 0, no allowed - Anonymous 1, Allowed - by default + response_data['success'] = 0, failed 1, Success - by default + response_data['status'] = 0, By default 1, Answer has been accepted already(Cancel) + + vote code: + allowed = -3, Don't have enough votes left + -2, Don't have enough reputation score + -1, Vote his own post + 0, no allowed - Anonymous + 1, Allowed - by default + status = 0, By default + 1, Cancel + 2, Vote is too old to be canceled + + offensive code: + allowed = -3, Don't have enough flags left + -2, Don't have enough reputation score to do this + 0, not allowed + 1, allowed + status = 0, by default + 1, can't do it again + """ + response_data = { + "allowed": 1, + "success": 1, + "status" : 0, + "count" : 0, + "message" : '' + } + + def __can_vote(vote_score, user):#refactor - belongs to auth.py + if vote_score == 1:#refactor magic number + return auth.can_vote_up(request.user) + else: + return auth.can_vote_down(request.user) + + try: + if not request.user.is_authenticated(): + response_data['allowed'] = 0 + response_data['success'] = 0 + + elif request.is_ajax() and request.method == 'POST': + question = get_object_or_404(Question, id=id) + vote_type = request.POST.get('type') + + #accept answer + if vote_type == '0': + answer_id = request.POST.get('postId') + answer = get_object_or_404(Answer, id=answer_id) + # make sure question author is current user + if question.author == request.user: + # answer user who is also question author is not allow to accept answer + if answer.author == question.author: + response_data['success'] = 0 + response_data['allowed'] = -1 + # check if answer has been accepted already + elif answer.accepted: + auth.onAnswerAcceptCanceled(answer, request.user) + response_data['status'] = 1 + else: + # set other answers in this question not accepted first + for answer_of_question in Answer.objects.get_answers_from_question(question, request.user): + if answer_of_question != answer and answer_of_question.accepted: + auth.onAnswerAcceptCanceled(answer_of_question, request.user) + + #make sure retrieve data again after above author changes, they may have related data + answer = get_object_or_404(Answer, id=answer_id) + auth.onAnswerAccept(answer, request.user) + else: + response_data['allowed'] = 0 + response_data['success'] = 0 + # favorite + elif vote_type == '4': + has_favorited = False + fav_questions = FavoriteQuestion.objects.filter(question=question) + # if the same question has been favorited before, then delete it + if fav_questions is not None: + for item in fav_questions: + if item.user == request.user: + item.delete() + response_data['status'] = 1 + response_data['count'] = len(fav_questions) - 1 + if response_data['count'] < 0: + response_data['count'] = 0 + has_favorited = True + # if above deletion has not been executed, just insert a new favorite question + if not has_favorited: + new_item = FavoriteQuestion(question=question, user=request.user) + new_item.save() + response_data['count'] = FavoriteQuestion.objects.filter(question=question).count() + Question.objects.update_favorite_count(question) + + elif vote_type in ['1', '2', '5', '6']: + post_id = id + post = question + vote_score = 1 + if vote_type in ['5', '6']: + answer_id = request.POST.get('postId') + answer = get_object_or_404(Answer, id=answer_id) + post_id = answer_id + post = answer + if vote_type in ['2', '6']: + vote_score = -1 + + if post.author == request.user: + response_data['allowed'] = -1 + elif not __can_vote(vote_score, request.user): + response_data['allowed'] = -2 + elif post.votes.filter(user=request.user).count() > 0: + vote = post.votes.filter(user=request.user)[0] + # unvote should be less than certain time + if (datetime.datetime.now().day - vote.voted_at.day) >= auth.VOTE_RULES['scope_deny_unvote_days']: + response_data['status'] = 2 + else: + voted = vote.vote + if voted > 0: + # cancel upvote + auth.onUpVotedCanceled(vote, post, request.user) + + else: + # cancel downvote + auth.onDownVotedCanceled(vote, post, request.user) + + response_data['status'] = 1 + response_data['count'] = post.score + elif Vote.objects.get_votes_count_today_from_user(request.user) >= auth.VOTE_RULES['scope_votes_per_user_per_day']: + response_data['allowed'] = -3 + else: + vote = Vote(user=request.user, content_object=post, vote=vote_score, voted_at=datetime.datetime.now()) + if vote_score > 0: + # upvote + auth.onUpVoted(vote, post, request.user) + else: + # downvote + auth.onDownVoted(vote, post, request.user) + + votes_left = auth.VOTE_RULES['scope_votes_per_user_per_day'] - Vote.objects.get_votes_count_today_from_user(request.user) + if votes_left <= auth.VOTE_RULES['scope_warn_votes_left']: + response_data['message'] = u'%s votes left' % votes_left + response_data['count'] = post.score + elif vote_type in ['7', '8']: + post = question + post_id = id + if vote_type == '8': + post_id = request.POST.get('postId') + post = get_object_or_404(Answer, id=post_id) + + if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= auth.VOTE_RULES['scope_flags_per_user_per_day']: + response_data['allowed'] = -3 + elif not auth.can_flag_offensive(request.user): + response_data['allowed'] = -2 + elif post.flagged_items.filter(user=request.user).count() > 0: + response_data['status'] = 1 + else: + item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now()) + auth.onFlaggedItem(item, post, request.user) + response_data['count'] = post.offensive_flag_count + # send signal when question or answer be marked offensive + mark_offensive.send(sender=post.__class__, instance=post, mark_by=request.user) + elif vote_type in ['9', '10']: + post = question + post_id = id + if vote_type == '10': + post_id = request.POST.get('postId') + post = get_object_or_404(Answer, id=post_id) + + if not auth.can_delete_post(request.user, post): + response_data['allowed'] = -2 + elif post.deleted == True: + logging.debug('debug restoring post in view') + auth.onDeleteCanceled(post, request.user) + response_data['status'] = 1 + else: + auth.onDeleted(post, request.user) + delete_post_or_answer.send(sender=post.__class__, instance=post, delete_by=request.user) + elif vote_type == '11':#subscribe q updates + user = request.user + if user.is_authenticated(): + if user not in question.followed_by.all(): + question.followed_by.add(user) + if settings.EMAIL_VALIDATION == 'on' and user.email_isvalid == False: + response_data['message'] = \ + _('subscription saved, %(email)s needs validation, see %(details_url)s') \ + % {'email':user.email,'details_url':reverse('faq') + '#validate'} + feed_setting = EmailFeedSetting.objects.get(subscriber=user,feed_type='q_sel') + if feed_setting.frequency == 'n': + feed_setting.frequency = 'd' + feed_setting.save() + if 'message' in response_data: + response_data['message'] += '<br/>' + response_data['message'] = _('email update frequency has been set to daily') + #response_data['status'] = 1 + #responst_data['allowed'] = 1 + else: + pass + #response_data['status'] = 0 + #response_data['allowed'] = 0 + elif vote_type == '12':#unsubscribe q updates + user = request.user + if user.is_authenticated(): + if user in question.followed_by.all(): + question.followed_by.remove(user) + else: + response_data['success'] = 0 + response_data['message'] = u'Request mode is not supported. Please try again.' + + data = simplejson.dumps(response_data) + + except Exception, e: + response_data['message'] = str(e) + data = simplejson.dumps(response_data) + return HttpResponse(data, mimetype="application/json") + +#internally grouped views - used by the tagging system +@ajax_login_required +def mark_tag(request, tag=None, **kwargs):#tagging system + action = kwargs['action'] + ts = MarkedTag.objects.filter(user=request.user, tag__name=tag) + if action == 'remove': + logging.debug('deleting tag %s' % tag) + ts.delete() + else: + reason = kwargs['reason'] + if len(ts) == 0: + try: + t = Tag.objects.get(name=tag) + mt = MarkedTag(user=request.user, reason=reason, tag=t) + mt.save() + except: + pass + else: + ts.update(reason=reason) + return HttpResponse(simplejson.dumps(''), mimetype="application/json") + +@ajax_login_required +def ajax_toggle_ignored_questions(request):#ajax tagging and tag-filtering system + if request.user.hide_ignored_questions: + new_hide_setting = False + else: + new_hide_setting = True + request.user.hide_ignored_questions = new_hide_setting + request.user.save() + +@ajax_method +def ajax_command(request):#refactor? view processing ajax commands - note "vote" and view others do it too + if 'command' not in request.POST: + return HttpResponseForbidden(mimetype="application/json") + if request.POST['command'] == 'toggle-ignored-questions': + return ajax_toggle_ignored_questions(request) + +@login_required +def close(request, id):#close question + """view to initiate and process + question close + """ + question = get_object_or_404(Question, id=id) + if not auth.can_close_question(request.user, question): + return HttpResponse('Permission denied.') + if request.method == 'POST': + form = CloseForm(request.POST) + if form.is_valid(): + reason = form.cleaned_data['reason'] + question.closed = True + question.closed_by = request.user + question.closed_at = datetime.datetime.now() + question.close_reason = reason + question.save() + return HttpResponseRedirect(question.get_absolute_url()) + else: + form = CloseForm() + return render_to_response('close.html', { + 'form' : form, + 'question' : question, + }, context_instance=RequestContext(request)) + +@login_required +def reopen(request, id):#re-open question + """view to initiate and process + question close + """ + question = get_object_or_404(Question, id=id) + # open question + if not auth.can_reopen_question(request.user, question): + return HttpResponse('Permission denied.') + if request.method == 'POST' : + Question.objects.filter(id=question.id).update(closed=False, + closed_by=None, closed_at=None, close_reason=None) + return HttpResponseRedirect(question.get_absolute_url()) + else: + return render_to_response('reopen.html', { + 'question' : question, + }, context_instance=RequestContext(request)) + +#osqa-user communication system +def read_message(request):#marks message a read + if request.method == "POST": + if request.POST['formdata'] == 'required': + request.session['message_silent'] = 1 + if request.user.is_authenticated(): + request.user.delete_messages() + return HttpResponse('') diff --git a/forum/views/meta.py b/forum/views/meta.py index b7a1183c..07e222b0 100644 --- a/forum/views/meta.py +++ b/forum/views/meta.py @@ -88,11 +88,3 @@ def badge(request, id): 'badge' : badge, }, context_instance=RequestContext(request)) -#osqa-user communication system -def read_message(request):#marks message a read - if request.method == "POST": - if request.POST['formdata'] == 'required': - request.session['message_silent'] = 1 - if request.user.is_authenticated(): - request.user.delete_messages() - return HttpResponse('') diff --git a/forum/views/readers.py b/forum/views/readers.py new file mode 100644 index 00000000..88b786aa --- /dev/null +++ b/forum/views/readers.py @@ -0,0 +1,577 @@ +# 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.utils.datastructures import SortedDict + +from utils.html import sanitize_html +from markdown2 import Markdown +#from lxml.html.diff import htmldiff +from forum.diff import textDiff as htmldiff +from forum.forms import * +from forum.models import * +from forum.auth import * +from forum.const import * +from forum import auth +from utils.forms import get_next_url + +# used in index page +#refactor - move these numbers somewhere? +INDEX_PAGE_SIZE = 20 +INDEX_AWARD_SIZE = 15 +INDEX_TAGS_SIZE = 100 +# used in tags list +DEFAULT_PAGE_SIZE = 60 +# used in questions +QUESTIONS_PAGE_SIZE = 10 +# 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 + +def _get_and_remember_questions_sort_method(request, view_dic, default):#service routine used by q listing views and question view + """manages persistence of post sort order + it is assumed that when user wants newest question - + then he/she wants newest answers as well, etc. + how far should this assumption actually go - may be a good question + """ + if default not in view_dic: + raise Exception('default value must be in view_dic') + + q_sort_method = request.REQUEST.get('sort', None) + if q_sort_method == None: + q_sort_method = request.session.get('questions_sort_method', default) + + if q_sort_method not in view_dic: + q_sort_method = default + request.session['questions_sort_method'] = q_sort_method + return q_sort_method, view_dic[q_sort_method] + +#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 + """ + view_dic = { + "latest":"-last_activity_at", + "hottest":"-answer_count", + "mostvoted":"-score", + } + view_id, orderby = _get_and_remember_questions_sort_method(request, view_dic, 'latest') + + page_size = request.session.get('pagesize', QUESTIONS_PAGE_SIZE) + questions = Question.objects.exclude(deleted=True).order_by(orderby)[:page_size] + # RISK - inner join queries + questions = questions.select_related() + tags = Tag.objects.get_valid_tags(INDEX_TAGS_SIZE) + + awards = Award.objects.get_recent_awards() + + (interesting_tag_names, ignored_tag_names) = (None, None) + if request.user.is_authenticated(): + pt = MarkedTag.objects.filter(user=request.user) + interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True) + ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True) + + tags_autocomplete = _get_tags_cache_json() + + return render_to_response('index.html', { + 'interesting_tag_names': interesting_tag_names, + 'tags_autocomplete': tags_autocomplete, + 'ignored_tag_names': ignored_tag_names, + "questions" : questions, + "tab_id" : view_id, + "tags" : tags, + "awards" : awards[:INDEX_AWARD_SIZE], + }, context_instance=RequestContext(request)) + +def unanswered(request):#generates listing of unanswered questions + return questions(request, unanswered=True) + +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. + """ + # template file + # "questions.html" or maybe index.html in the future + template_file = "questions.html" + # Set flag to False by default. If it is equal to True, then need to be saved. + pagesize_changed = False + # get pagesize from session, if failed then get default value + pagesize = request.session.get("pagesize",10) + try: + page = int(request.GET.get('page', '1')) + except ValueError: + page = 1 + + view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" } + view_id, orderby = _get_and_remember_questions_sort_method(request,view_dic,'latest') + + # check if request is from tagged questions + qs = Question.objects.exclude(deleted=True) + + if tagname is not None: + qs = qs.filter(tags__name = unquote(tagname)) + + if unanswered: + qs = qs.exclude(answer_accepted=True) + + author_name = None + #user contributed questions & answers + if 'user' in request.GET: + try: + author_name = request.GET['user'] + u = User.objects.get(username=author_name) + qs = qs.filter(Q(author=u) | Q(answers__author=u)) + except User.DoesNotExist: + author_name = None + + if request.user.is_authenticated(): + uid_str = str(request.user.id) + qs = qs.extra( + select = SortedDict([ + ( + 'interesting_score', + 'SELECT COUNT(1) FROM forum_markedtag, question_tags ' + + 'WHERE forum_markedtag.user_id = %s ' + + 'AND forum_markedtag.tag_id = question_tags.tag_id ' + + 'AND forum_markedtag.reason = \'good\' ' + + 'AND question_tags.question_id = question.id' + ), + ]), + select_params = (uid_str,), + ) + if request.user.hide_ignored_questions: + ignored_tags = Tag.objects.filter(user_selections__reason='bad', + user_selections__user = request.user) + qs = qs.exclude(tags__in=ignored_tags) + else: + qs = qs.extra( + select = SortedDict([ + ( + 'ignored_score', + 'SELECT COUNT(1) FROM forum_markedtag, question_tags ' + + 'WHERE forum_markedtag.user_id = %s ' + + 'AND forum_markedtag.tag_id = question_tags.tag_id ' + + 'AND forum_markedtag.reason = \'bad\' ' + + 'AND question_tags.question_id = question.id' + ) + ]), + select_params = (uid_str, ) + ) + + qs = qs.select_related(depth=1).order_by(orderby) + + objects_list = Paginator(qs, pagesize) + questions = objects_list.page(page) + + # Get related tags from this page objects + if questions.object_list.count() > 0: + related_tags = Tag.objects.get_tags_by_questions(questions.object_list) + else: + related_tags = None + tags_autocomplete = _get_tags_cache_json() + + # get the list of interesting and ignored tags + (interesting_tag_names, ignored_tag_names) = (None, None) + if request.user.is_authenticated(): + pt = MarkedTag.objects.filter(user=request.user) + interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True) + ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True) + + return render_to_response(template_file, { + "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 search(request): #generates listing of questions matching a search query - including tags and just words + """generates listing of questions matching a search query + supports full text search in mysql db using sphinx and internally in postgresql + falls back on simple partial string matching approach if + full text search function is not available + """ + if request.method == "GET": + keywords = request.GET.get("q") + search_type = request.GET.get("t") + try: + page = int(request.GET.get('page', '1')) + except ValueError: + page = 1 + if keywords is None: + return HttpResponseRedirect(reverse(index)) + 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)) + elif search_type == "question": + + template_file = "questions.html" + # Set flag to False by default. If it is equal to True, then need to be saved. + pagesize_changed = False + # get pagesize from session, if failed then get default value + user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE) + # set pagesize equal to logon user specified value in database + if request.user.is_authenticated() and request.user.questions_per_page > 0: + user_page_size = request.user.questions_per_page + + try: + page = int(request.GET.get('page', '1')) + # get new pagesize from UI selection + pagesize = int(request.GET.get('pagesize', user_page_size)) + if pagesize <> user_page_size: + pagesize_changed = True + + except ValueError: + page = 1 + pagesize = user_page_size + + # save this pagesize to user database + if pagesize_changed: + request.session["pagesize"] = pagesize + if request.user.is_authenticated(): + user = request.user + user.questions_per_page = pagesize + user.save() + + 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" + + if settings.USE_PG_FTS: + objects = Question.objects.filter(deleted=False).extra( + select={ + 'ranking': "ts_rank_cd(tsv, plainto_tsquery(%s), 32)", + }, + where=["tsv @@ plainto_tsquery(%s)"], + params=[keywords], + select_params=[keywords] + ).order_by('-ranking') + + elif settings.USE_SPHINX_SEARCH == True: + #search index is now free of delete questions and answers + #so there is not "antideleted" filtering here + objects = Question.search.query(keywords) + #no related selection either because we're relying on full text search here + else: + objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby) + # RISK - inner join queries + objects = objects.select_related(); + + 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 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 = ('<h1>%(title)s</h1>\n' + '<div class="text">%(html)s</div>\n' + '<div class="tags">%(tags)s</div>') +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(['<a class="post-tag">%s</a>' % 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(['<a class="post-tag">%s</a>' % 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 = ('<div class="text">%(html)s</div>') +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)) + diff --git a/forum/views/users.py b/forum/views/users.py index 965d3a68..d988a195 100644 --- a/forum/views/users.py +++ b/forum/views/users.py @@ -1,6 +1,7 @@ from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User from django.core.paginator import Paginator, EmptyPage, InvalidPage +from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext @@ -23,88 +24,6 @@ question_revision_type_id = question_revision_type.id answer_revision_type_id = answer_revision_type.id repute_type_id = repute_type.id -# used in users -class UserView: - def __init__(self, id, tab_title, tab_description, page_title, view_name, template_file, data_size=0): - self.id = id - self.tab_title = tab_title - self.tab_description = tab_description - self.page_title = page_title - self.view_name = view_name - self.template_file = template_file - self.data_size = data_size - -USER_TEMPLATE_VIEWS = ( - UserView( - id = 'stats', - tab_title = _('overview'), - tab_description = _('user profile'), - page_title = _('user profile overview'), - view_name = 'user_stats', - template_file = 'user_stats.html' - ), - UserView( - id = 'recent', - tab_title = _('recent activity'), - tab_description = _('recent user activity'), - page_title = _('profile - recent activity'), - view_name = 'user_recent', - template_file = 'user_recent.html', - data_size = 50 - ), - UserView( - id = 'responses', - tab_title = _('responses'), - tab_description = _('comments and answers to others questions'), - page_title = _('profile - responses'), - view_name = 'user_responses', - template_file = 'user_responses.html', - data_size = 50 - ), - UserView( - id = 'reputation', - tab_title = _('reputation'), - tab_description = _('user reputation in the community'), - page_title = _('profile - user reputation'), - view_name = 'user_reputation', - template_file = 'user_reputation.html' - ), - UserView( - id = 'favorites', - tab_title = _('favorite questions'), - tab_description = _('users favorite questions'), - page_title = _('profile - favorite questions'), - view_name = 'user_favorites', - template_file = 'user_favorites.html', - data_size = 50 - ), - UserView( - id = 'votes', - tab_title = _('casted votes'), - tab_description = _('user vote record'), - page_title = _('profile - votes'), - view_name = 'user_votes', - template_file = 'user_votes.html', - data_size = 50 - ), - UserView( - id = 'email_subscriptions', - tab_title = _('email subscriptions'), - tab_description = _('email subscription settings'), - page_title = _('profile - email subscriptions'), - view_name = 'user_email_subscriptions', - template_file = 'user_email_subscriptions.html' - ) -) - -def user(request, id): - sort = request.GET.get('sort', 'stats') - user_view = dict((v.id, v) for v in USER_TEMPLATE_VIEWS).get(sort, USER_TEMPLATE_VIEWS[0]) - from forum.views import users - func = getattr(users, user_view.view_name) - return func(request, id, user_view) - - USERS_PAGE_SIZE = 35# refactor - move to some constants file def users(request): @@ -942,3 +861,83 @@ def user_email_subscriptions(request, user_id, user_view): 'action_status':action_status, }, context_instance=RequestContext(request)) +class UserView: + def __init__(self, id, tab_title, tab_description, page_title, view_func, template_file, data_size=0): + self.id = id + self.tab_title = tab_title + self.tab_description = tab_description + self.page_title = page_title + self.view_func = view_func + self.template_file = template_file + self.data_size = data_size + +USER_TEMPLATE_VIEWS = ( + UserView( + id = 'stats', + tab_title = _('overview'), + tab_description = _('user profile'), + page_title = _('user profile overview'), + view_func = user_stats, + template_file = 'user_stats.html' + ), + UserView( + id = 'recent', + tab_title = _('recent activity'), + tab_description = _('recent user activity'), + page_title = _('profile - recent activity'), + view_func = user_recent, + template_file = 'user_recent.html', + data_size = 50 + ), + UserView( + id = 'responses', + tab_title = _('responses'), + tab_description = _('comments and answers to others questions'), + page_title = _('profile - responses'), + view_func = user_responses, + template_file = 'user_responses.html', + data_size = 50 + ), + UserView( + id = 'reputation', + tab_title = _('reputation'), + tab_description = _('user reputation in the community'), + page_title = _('profile - user reputation'), + view_func = user_reputation, + template_file = 'user_reputation.html' + ), + UserView( + id = 'favorites', + tab_title = _('favorite questions'), + tab_description = _('users favorite questions'), + page_title = _('profile - favorite questions'), + view_func = user_favorites, + template_file = 'user_favorites.html', + data_size = 50 + ), + UserView( + id = 'votes', + tab_title = _('casted votes'), + tab_description = _('user vote record'), + page_title = _('profile - votes'), + view_func = user_votes, + template_file = 'user_votes.html', + data_size = 50 + ), + UserView( + id = 'email_subscriptions', + tab_title = _('email subscriptions'), + tab_description = _('email subscription settings'), + page_title = _('profile - email subscriptions'), + view_func = user_email_subscriptions, + template_file = 'user_email_subscriptions.html' + ) +) + +def user(request, id): + sort = request.GET.get('sort', 'stats') + user_view = dict((v.id, v) for v in USER_TEMPLATE_VIEWS).get(sort, USER_TEMPLATE_VIEWS[0]) + from forum.views import users + func = user_view.view_func + return func(request, id, user_view) + diff --git a/forum/views/views.py b/forum/views/views.py deleted file mode 100644 index 4d214bad..00000000 --- a/forum/views/views.py +++ /dev/null @@ -1,2412 +0,0 @@ -# encoding:utf-8 -import os.path -import time, datetime, calendar, random -import logging -from urllib import quote, unquote -from django.conf import settings -from django.core.files.storage import default_storage -from django.shortcuts import render_to_response, get_object_or_404 -from django.contrib.auth.decorators import login_required -from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404 -from django.core.paginator import Paginator, EmptyPage, InvalidPage -from django.template import RequestContext, loader -from django.utils.html import * -from django.utils import simplejson -from django.core import serializers -from django.core.mail import mail_admins -from django.db import transaction -from django.db.models import Count, Q -from django.contrib.contenttypes.models import ContentType -from django.utils.translation import ugettext as _ -from django.utils.datastructures import SortedDict -from django.template.defaultfilters import slugify -from django.core.exceptions import PermissionDenied - -from utils.html import sanitize_html -from utils.decorators import ajax_method, ajax_login_required -from markdown2 import Markdown -#from lxml.html.diff import htmldiff -from forum.diff import textDiff as htmldiff -from forum.forms import * -from forum.models import * -from forum.auth import * -from forum.const import * -from forum.user import * -from forum import auth -from utils.forms import get_next_url - -# used in index page -INDEX_PAGE_SIZE = 20 -INDEX_AWARD_SIZE = 15 -INDEX_TAGS_SIZE = 100 -# used in tags list -DEFAULT_PAGE_SIZE = 60 -# used in questions -QUESTIONS_PAGE_SIZE = 10 -# used in users -USERS_PAGE_SIZE = 35 -# used in answers -ANSWERS_PAGE_SIZE = 10 -markdowner = Markdown(html4tags=True) -question_type = ContentType.objects.get_for_model(Question) -answer_type = ContentType.objects.get_for_model(Answer) -comment_type = ContentType.objects.get_for_model(Comment) -question_revision_type = ContentType.objects.get_for_model(QuestionRevision) -answer_revision_type = ContentType.objects.get_for_model(AnswerRevision) -repute_type = ContentType.objects.get_for_model(Repute) -question_type_id = question_type.id -answer_type_id = answer_type.id -comment_type_id = comment_type.id -question_revision_type_id = question_revision_type.id -answer_revision_type_id = answer_revision_type.id -repute_type_id = repute_type.id -def _get_tags_cache_json(): - 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 - -def _get_and_remember_questions_sort_method(request, view_dic, default): - if default not in view_dic: - raise Exception('default value must be in view_dic') - - q_sort_method = request.REQUEST.get('sort', None) - if q_sort_method == None: - q_sort_method = request.session.get('questions_sort_method', default) - - if q_sort_method not in view_dic: - q_sort_method = default - request.session['questions_sort_method'] = q_sort_method - return q_sort_method, view_dic[q_sort_method] - -def index(request): - view_dic = { - "latest":"-last_activity_at", - "hottest":"-answer_count", - "mostvoted":"-score", - } - view_id, orderby = _get_and_remember_questions_sort_method(request, view_dic, 'latest') - - page_size = request.session.get('pagesize', QUESTIONS_PAGE_SIZE) - questions = Question.objects.exclude(deleted=True).order_by(orderby)[:page_size] - # RISK - inner join queries - questions = questions.select_related() - tags = Tag.objects.get_valid_tags(INDEX_TAGS_SIZE) - - awards = Award.objects.get_recent_awards() - - (interesting_tag_names, ignored_tag_names) = (None, None) - if request.user.is_authenticated(): - pt = MarkedTag.objects.filter(user=request.user) - interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True) - ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True) - - tags_autocomplete = _get_tags_cache_json() - - return render_to_response('index.html', { - 'interesting_tag_names': interesting_tag_names, - 'tags_autocomplete': tags_autocomplete, - 'ignored_tag_names': ignored_tag_names, - "questions" : questions, - "tab_id" : view_id, - "tags" : tags, - "awards" : awards[:INDEX_AWARD_SIZE], - }, context_instance=RequestContext(request)) - -def about(request): - return render_to_response('about.html', context_instance=RequestContext(request)) - -def faq(request): - data = { - 'gravatar_faq_url': reverse('faq') + '#gravatar', - 'send_email_key_url': reverse('send_email_key'), - 'ask_question_url': reverse('ask'), - } - return render_to_response('faq.html', data, context_instance=RequestContext(request)) - -def feedback(request): - data = {} - form = None - if request.method == "POST": - form = FeedbackForm(request.POST) - if form.is_valid(): - if not request.user.is_authenticated: - data['email'] = form.cleaned_data.get('email',None) - data['message'] = form.cleaned_data['message'] - data['name'] = form.cleaned_data.get('name',None) - message = render_to_response('feedback_email.txt',data,context_instance=RequestContext(request)) - mail_admins(_('Q&A forum feedback'), message) - msg = _('Thanks for the feedback!') - request.user.message_set.create(message=msg) - return HttpResponseRedirect(get_next_url(request)) - else: - form = FeedbackForm(initial={'next':get_next_url(request)}) - - data['form'] = form - return render_to_response('feedback.html', data, context_instance=RequestContext(request)) -feedback.CANCEL_MESSAGE=_('We look forward to hearing your feedback! Please, give it next time :)') - -def privacy(request): - return render_to_response('privacy.html', context_instance=RequestContext(request)) - -def unanswered(request): - return questions(request, unanswered=True) - -def questions(request, tagname=None, unanswered=False): - """ - List of Questions, Tagged questions, and Unanswered questions. - """ - # template file - # "questions.html" or maybe index.html in the future - template_file = "questions.html" - # Set flag to False by default. If it is equal to True, then need to be saved. - pagesize_changed = False - # get pagesize from session, if failed then get default value - pagesize = request.session.get("pagesize",10) - try: - page = int(request.GET.get('page', '1')) - except ValueError: - page = 1 - - view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" } - view_id, orderby = _get_and_remember_questions_sort_method(request,view_dic,'latest') - - # check if request is from tagged questions - qs = Question.objects.exclude(deleted=True) - - if tagname is not None: - qs = qs.filter(tags__name = unquote(tagname)) - - if unanswered: - qs = qs.exclude(answer_accepted=True) - - author_name = None - #user contributed questions & answers - if 'user' in request.GET: - try: - author_name = request.GET['user'] - u = User.objects.get(username=author_name) - qs = qs.filter(Q(author=u) | Q(answers__author=u)) - except User.DoesNotExist: - author_name = None - - if request.user.is_authenticated(): - uid_str = str(request.user.id) - qs = qs.extra( - select = SortedDict([ - ( - 'interesting_score', - 'SELECT COUNT(1) FROM forum_markedtag, question_tags ' - + 'WHERE forum_markedtag.user_id = %s ' - + 'AND forum_markedtag.tag_id = question_tags.tag_id ' - + 'AND forum_markedtag.reason = \'good\' ' - + 'AND question_tags.question_id = question.id' - ), - ]), - select_params = (uid_str,), - ) - if request.user.hide_ignored_questions: - ignored_tags = Tag.objects.filter(user_selections__reason='bad', - user_selections__user = request.user) - qs = qs.exclude(tags__in=ignored_tags) - else: - qs = qs.extra( - select = SortedDict([ - ( - 'ignored_score', - 'SELECT COUNT(1) FROM forum_markedtag, question_tags ' - + 'WHERE forum_markedtag.user_id = %s ' - + 'AND forum_markedtag.tag_id = question_tags.tag_id ' - + 'AND forum_markedtag.reason = \'bad\' ' - + 'AND question_tags.question_id = question.id' - ) - ]), - select_params = (uid_str, ) - ) - - qs = qs.select_related(depth=1).order_by(orderby) - - objects_list = Paginator(qs, pagesize) - questions = objects_list.page(page) - - # Get related tags from this page objects - if questions.object_list.count() > 0: - related_tags = Tag.objects.get_tags_by_questions(questions.object_list) - else: - related_tags = None - tags_autocomplete = _get_tags_cache_json() - - # get the list of interesting and ignored tags - (interesting_tag_names, ignored_tag_names) = (None, None) - if request.user.is_authenticated(): - pt = MarkedTag.objects.filter(user=request.user) - interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True) - ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True) - - return render_to_response(template_file, { - "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 create_new_answer( question=None, author=None,\ - added_at=None, wiki=False,\ - text='', email_notify=False): - - html = sanitize_html(markdowner.convert(text)) - - #create answer - answer = Answer( - question = question, - author = author, - added_at = added_at, - wiki = wiki, - html = html - ) - if answer.wiki: - answer.last_edited_by = answer.author - answer.last_edited_at = added_at - answer.wikified_at = added_at - - answer.save() - - #update question data - question.last_activity_at = added_at - question.last_activity_by = author - question.save() - Question.objects.update_answer_count(question) - - #update revision - AnswerRevision.objects.create( - answer = answer, - revision = 1, - author = author, - revised_at = added_at, - summary = CONST['default_version'], - text = text - ) - - #set notification/delete - if email_notify: - if author not in question.followed_by.all(): - question.followed_by.add(author) - else: - #not sure if this is necessary. ajax should take care of this... - try: - question.followed_by.remove(author) - except: - pass - -def create_new_question(title=None,author=None,added_at=None, - wiki=False,tagnames=None,summary=None, - text=None): - """this is not a view - and maybe should become one of the methods on Question object? - """ - html = sanitize_html(markdowner.convert(text)) - question = Question( - title = title, - author = author, - added_at = added_at, - last_activity_at = added_at, - last_activity_by = author, - wiki = wiki, - tagnames = tagnames, - html = html, - summary = summary - ) - if question.wiki: - question.last_edited_by = question.author - question.last_edited_at = added_at - question.wikified_at = added_at - - question.save() - - # create the first revision - QuestionRevision.objects.create( - question = question, - revision = 1, - title = question.title, - author = author, - revised_at = added_at, - tagnames = question.tagnames, - summary = CONST['default_version'], - text = text - ) - return question - -#TODO: allow anynomus user to ask question by providing email and username. -#@login_required -def ask(request): - if request.method == "POST": - form = AskForm(request.POST) - if form.is_valid(): - - added_at = datetime.datetime.now() - title = strip_tags(form.cleaned_data['title'].strip()) - wiki = form.cleaned_data['wiki'] - tagnames = form.cleaned_data['tags'].strip() - text = form.cleaned_data['text'] - html = sanitize_html(markdowner.convert(text)) - summary = strip_tags(html)[:120] - - if request.user.is_authenticated(): - author = request.user - - question = create_new_question( - title = title, - author = author, - added_at = added_at, - wiki = wiki, - tagnames = tagnames, - summary = summary, - text = text - ) - - return HttpResponseRedirect(question.get_absolute_url()) - else: - request.session.flush() - session_key = request.session.session_key - question = AnonymousQuestion( - session_key = session_key, - title = title, - tagnames = tagnames, - wiki = wiki, - text = text, - summary = summary, - added_at = added_at, - ip_addr = request.META['REMOTE_ADDR'], - ) - question.save() - return HttpResponseRedirect(reverse('user_signin_new_question')) - else: - form = AskForm() - - tags = _get_tags_cache_json() - return render_to_response('ask.html', { - 'form' : form, - 'tags' : tags, - 'email_validation_faq_url':reverse('faq') + '#validate', - }, context_instance=RequestContext(request)) - -def question(request, id): - 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 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)) - -@login_required -def close(request, id): - question = get_object_or_404(Question, id=id) - if not can_close_question(request.user, question): - return HttpResponse('Permission denied.') - if request.method == 'POST': - form = CloseForm(request.POST) - if form.is_valid(): - reason = form.cleaned_data['reason'] - question.closed = True - question.closed_by = request.user - question.closed_at = datetime.datetime.now() - question.close_reason = reason - question.save() - return HttpResponseRedirect(question.get_absolute_url()) - else: - form = CloseForm() - return render_to_response('close.html', { - 'form' : form, - 'question' : question, - }, context_instance=RequestContext(request)) - -@login_required -def reopen(request, id): - question = get_object_or_404(Question, id=id) - # open question - if not can_reopen_question(request.user, question): - return HttpResponse('Permission denied.') - if request.method == 'POST' : - Question.objects.filter(id=question.id).update(closed=False, - closed_by=None, closed_at=None, close_reason=None) - return HttpResponseRedirect(question.get_absolute_url()) - else: - return render_to_response('reopen.html', { - 'question' : question, - }, context_instance=RequestContext(request)) - -@login_required -def edit_question(request, id): - question = get_object_or_404(Question, id=id) - if question.deleted and not can_view_deleted_post(request.user, question): - raise Http404 - if can_edit_post(request.user, question): - return _edit_question(request, question) - elif can_retag_questions(request.user): - return _retag_question(request, question) - else: - raise Http404 - -def _retag_question(request, question): - if request.method == 'POST': - form = RetagQuestionForm(question, request.POST) - if form.is_valid(): - if form.has_changed(): - latest_revision = question.get_latest_revision() - retagged_at = datetime.datetime.now() - # Update the Question itself - Question.objects.filter(id=question.id).update( - tagnames = form.cleaned_data['tags'], - last_edited_at = retagged_at, - last_edited_by = request.user, - last_activity_at = retagged_at, - last_activity_by = request.user - ) - # Update the Question's tag associations - tags_updated = Question.objects.update_tags(question, - form.cleaned_data['tags'], request.user) - # Create a new revision - QuestionRevision.objects.create( - question = question, - title = latest_revision.title, - author = request.user, - revised_at = retagged_at, - tagnames = form.cleaned_data['tags'], - summary = CONST['retagged'], - text = latest_revision.text - ) - # send tags updated singal - tags_updated.send(sender=question.__class__, question=question) - - return HttpResponseRedirect(question.get_absolute_url()) - else: - form = RetagQuestionForm(question) - return render_to_response('question_retag.html', { - 'question': question, - 'form' : form, - 'tags' : _get_tags_cache_json(), - }, context_instance=RequestContext(request)) - -def _edit_question(request, question): - latest_revision = question.get_latest_revision() - revision_form = None - if request.method == 'POST': - if 'select_revision' in request.POST: - # user has changed revistion number - revision_form = RevisionForm(question, latest_revision, request.POST) - if revision_form.is_valid(): - # Replace with those from the selected revision - form = EditQuestionForm(question, - QuestionRevision.objects.get(question=question, - revision=revision_form.cleaned_data['revision'])) - else: - form = EditQuestionForm(question, latest_revision, request.POST) - else: - # Always check modifications against the latest revision - form = EditQuestionForm(question, latest_revision, request.POST) - if form.is_valid(): - html = sanitize_html(markdowner.convert(form.cleaned_data['text'])) - if form.has_changed(): - edited_at = datetime.datetime.now() - tags_changed = (latest_revision.tagnames != - form.cleaned_data['tags']) - tags_updated = False - # Update the Question itself - updated_fields = { - 'title': form.cleaned_data['title'], - 'last_edited_at': edited_at, - 'last_edited_by': request.user, - 'last_activity_at': edited_at, - 'last_activity_by': request.user, - 'tagnames': form.cleaned_data['tags'], - 'summary': strip_tags(html)[:120], - 'html': html, - } - - # only save when it's checked - # because wiki doesn't allow to be edited if last version has been enabled already - # and we make sure this in forms. - if ('wiki' in form.cleaned_data and - form.cleaned_data['wiki']): - updated_fields['wiki'] = True - updated_fields['wikified_at'] = edited_at - - Question.objects.filter( - id=question.id).update(**updated_fields) - # Update the Question's tag associations - if tags_changed: - tags_updated = Question.objects.update_tags( - question, form.cleaned_data['tags'], request.user) - # Create a new revision - revision = QuestionRevision( - question = question, - title = form.cleaned_data['title'], - author = request.user, - revised_at = edited_at, - tagnames = form.cleaned_data['tags'], - text = form.cleaned_data['text'], - ) - if form.cleaned_data['summary']: - revision.summary = form.cleaned_data['summary'] - else: - revision.summary = 'No.%s Revision' % latest_revision.revision - revision.save() - - return HttpResponseRedirect(question.get_absolute_url()) - else: - - revision_form = RevisionForm(question, latest_revision) - form = EditQuestionForm(question, latest_revision) - return render_to_response('question_edit.html', { - 'question': question, - 'revision_form': revision_form, - 'form' : form, - 'tags' : _get_tags_cache_json() - }, context_instance=RequestContext(request)) - - -@login_required -def edit_answer(request, id): - answer = get_object_or_404(Answer, id=id) - if answer.deleted and not can_view_deleted_post(request.user, answer): - raise Http404 - elif not can_edit_post(request.user, answer): - raise Http404 - else: - latest_revision = answer.get_latest_revision() - if request.method == "POST": - if 'select_revision' in request.POST: - # user has changed revistion number - revision_form = RevisionForm(answer, latest_revision, request.POST) - if revision_form.is_valid(): - # Replace with those from the selected revision - form = EditAnswerForm(answer, - AnswerRevision.objects.get(answer=answer, - revision=revision_form.cleaned_data['revision'])) - else: - form = EditAnswerForm(answer, latest_revision, request.POST) - else: - form = EditAnswerForm(answer, latest_revision, request.POST) - if form.is_valid(): - html = sanitize_html(markdowner.convert(form.cleaned_data['text'])) - if form.has_changed(): - edited_at = datetime.datetime.now() - updated_fields = { - 'last_edited_at': edited_at, - 'last_edited_by': request.user, - 'html': html, - } - Answer.objects.filter(id=answer.id).update(**updated_fields) - - revision = AnswerRevision( - answer=answer, - author=request.user, - revised_at=edited_at, - text=form.cleaned_data['text'] - ) - - if form.cleaned_data['summary']: - revision.summary = form.cleaned_data['summary'] - else: - revision.summary = 'No.%s Revision' % latest_revision.revision - revision.save() - - answer.question.last_activity_at = edited_at - answer.question.last_activity_by = request.user - answer.question.save() - - return HttpResponseRedirect(answer.get_absolute_url()) - else: - revision_form = RevisionForm(answer, latest_revision) - form = EditAnswerForm(answer, latest_revision) - return render_to_response('answer_edit.html', { - 'answer': answer, - 'revision_form': revision_form, - 'form': form, - }, context_instance=RequestContext(request)) - -QUESTION_REVISION_TEMPLATE = ('<h1>%(title)s</h1>\n' - '<div class="text">%(html)s</div>\n' - '<div class="tags">%(tags)s</div>') -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(['<a class="post-tag">%s</a>' % 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(['<a class="post-tag">%s</a>' % 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 = ('<div class="text">%(html)s</div>') -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)) - -def answer(request, id): - question = get_object_or_404(Question, id=id) - if request.method == "POST": - form = AnswerForm(question, request.user, request.POST) - if form.is_valid(): - wiki = form.cleaned_data['wiki'] - text = form.cleaned_data['text'] - update_time = datetime.datetime.now() - - if request.user.is_authenticated(): - create_new_answer( - question=question, - author=request.user, - added_at=update_time, - wiki=wiki, - text=text, - email_notify=form.cleaned_data['email_notify'] - ) - else: - request.session.flush() - html = sanitize_html(markdowner.convert(text)) - summary = strip_tags(html)[:120] - anon = AnonymousAnswer( - question=question, - wiki=wiki, - text=text, - summary=summary, - session_key=request.session.session_key, - ip_addr=request.META['REMOTE_ADDR'], - ) - anon.save() - return HttpResponseRedirect(reverse('user_signin_new_answer')) - - return HttpResponseRedirect(question.get_absolute_url()) - -def tags(request): - 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 tag(request, tag): - return questions(request, tagname=tag) - -def vote(request, id): - """ - vote_type: - acceptAnswer : 0, - questionUpVote : 1, - questionDownVote : 2, - favorite : 4, - answerUpVote: 5, - answerDownVote:6, - offensiveQuestion : 7, - offensiveAnswer:8, - removeQuestion: 9, - removeAnswer:10 - questionSubscribeUpdates:11 - - accept answer code: - response_data['allowed'] = -1, Accept his own answer 0, no allowed - Anonymous 1, Allowed - by default - response_data['success'] = 0, failed 1, Success - by default - response_data['status'] = 0, By default 1, Answer has been accepted already(Cancel) - - vote code: - allowed = -3, Don't have enough votes left - -2, Don't have enough reputation score - -1, Vote his own post - 0, no allowed - Anonymous - 1, Allowed - by default - status = 0, By default - 1, Cancel - 2, Vote is too old to be canceled - - offensive code: - allowed = -3, Don't have enough flags left - -2, Don't have enough reputation score to do this - 0, not allowed - 1, allowed - status = 0, by default - 1, can't do it again - """ - response_data = { - "allowed": 1, - "success": 1, - "status" : 0, - "count" : 0, - "message" : '' - } - - def can_vote(vote_score, user): - if vote_score == 1: - return can_vote_up(request.user) - else: - return can_vote_down(request.user) - - try: - if not request.user.is_authenticated(): - response_data['allowed'] = 0 - response_data['success'] = 0 - - elif request.is_ajax(): - question = get_object_or_404(Question, id=id) - vote_type = request.POST.get('type') - - #accept answer - if vote_type == '0': - answer_id = request.POST.get('postId') - answer = get_object_or_404(Answer, id=answer_id) - # make sure question author is current user - if question.author == request.user: - # answer user who is also question author is not allow to accept answer - if answer.author == question.author: - response_data['success'] = 0 - response_data['allowed'] = -1 - # check if answer has been accepted already - elif answer.accepted: - onAnswerAcceptCanceled(answer, request.user) - response_data['status'] = 1 - else: - # set other answers in this question not accepted first - for answer_of_question in Answer.objects.get_answers_from_question(question, request.user): - if answer_of_question != answer and answer_of_question.accepted: - onAnswerAcceptCanceled(answer_of_question, request.user) - - #make sure retrieve data again after above author changes, they may have related data - answer = get_object_or_404(Answer, id=answer_id) - onAnswerAccept(answer, request.user) - else: - response_data['allowed'] = 0 - response_data['success'] = 0 - # favorite - elif vote_type == '4': - has_favorited = False - fav_questions = FavoriteQuestion.objects.filter(question=question) - # if the same question has been favorited before, then delete it - if fav_questions is not None: - for item in fav_questions: - if item.user == request.user: - item.delete() - response_data['status'] = 1 - response_data['count'] = len(fav_questions) - 1 - if response_data['count'] < 0: - response_data['count'] = 0 - has_favorited = True - # if above deletion has not been executed, just insert a new favorite question - if not has_favorited: - new_item = FavoriteQuestion(question=question, user=request.user) - new_item.save() - response_data['count'] = FavoriteQuestion.objects.filter(question=question).count() - Question.objects.update_favorite_count(question) - - elif vote_type in ['1', '2', '5', '6']: - post_id = id - post = question - vote_score = 1 - if vote_type in ['5', '6']: - answer_id = request.POST.get('postId') - answer = get_object_or_404(Answer, id=answer_id) - post_id = answer_id - post = answer - if vote_type in ['2', '6']: - vote_score = -1 - - if post.author == request.user: - response_data['allowed'] = -1 - elif not can_vote(vote_score, request.user): - response_data['allowed'] = -2 - elif post.votes.filter(user=request.user).count() > 0: - vote = post.votes.filter(user=request.user)[0] - # unvote should be less than certain time - if (datetime.datetime.now().day - vote.voted_at.day) >= VOTE_RULES['scope_deny_unvote_days']: - response_data['status'] = 2 - else: - voted = vote.vote - if voted > 0: - # cancel upvote - onUpVotedCanceled(vote, post, request.user) - - else: - # cancel downvote - onDownVotedCanceled(vote, post, request.user) - - response_data['status'] = 1 - response_data['count'] = post.score - elif Vote.objects.get_votes_count_today_from_user(request.user) >= VOTE_RULES['scope_votes_per_user_per_day']: - response_data['allowed'] = -3 - else: - vote = Vote(user=request.user, content_object=post, vote=vote_score, voted_at=datetime.datetime.now()) - if vote_score > 0: - # upvote - onUpVoted(vote, post, request.user) - else: - # downvote - onDownVoted(vote, post, request.user) - - votes_left = VOTE_RULES['scope_votes_per_user_per_day'] - Vote.objects.get_votes_count_today_from_user(request.user) - if votes_left <= VOTE_RULES['scope_warn_votes_left']: - response_data['message'] = u'%s votes left' % votes_left - response_data['count'] = post.score - elif vote_type in ['7', '8']: - post = question - post_id = id - if vote_type == '8': - post_id = request.POST.get('postId') - post = get_object_or_404(Answer, id=post_id) - - if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= VOTE_RULES['scope_flags_per_user_per_day']: - response_data['allowed'] = -3 - elif not can_flag_offensive(request.user): - response_data['allowed'] = -2 - elif post.flagged_items.filter(user=request.user).count() > 0: - response_data['status'] = 1 - else: - item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now()) - onFlaggedItem(item, post, request.user) - response_data['count'] = post.offensive_flag_count - # send signal when question or answer be marked offensive - mark_offensive.send(sender=post.__class__, instance=post, mark_by=request.user) - elif vote_type in ['9', '10']: - post = question - post_id = id - if vote_type == '10': - post_id = request.POST.get('postId') - post = get_object_or_404(Answer, id=post_id) - - if not can_delete_post(request.user, post): - response_data['allowed'] = -2 - elif post.deleted == True: - logging.debug('debug restoring post in view') - onDeleteCanceled(post, request.user) - response_data['status'] = 1 - else: - onDeleted(post, request.user) - delete_post_or_answer.send(sender=post.__class__, instance=post, delete_by=request.user) - elif vote_type == '11':#subscribe q updates - user = request.user - if user.is_authenticated(): - if user not in question.followed_by.all(): - question.followed_by.add(user) - if settings.EMAIL_VALIDATION == 'on' and user.email_isvalid == False: - response_data['message'] = \ - _('subscription saved, %(email)s needs validation, see %(details_url)s') \ - % {'email':user.email,'details_url':reverse('faq') + '#validate'} - feed_setting = EmailFeedSetting.objects.get(subscriber=user,feed_type='q_sel') - if feed_setting.frequency == 'n': - feed_setting.frequency = 'd' - feed_setting.save() - if 'message' in response_data: - response_data['message'] += '<br/>' - response_data['message'] = _('email update frequency has been set to daily') - #response_data['status'] = 1 - #responst_data['allowed'] = 1 - else: - pass - #response_data['status'] = 0 - #response_data['allowed'] = 0 - elif vote_type == '12':#unsubscribe q updates - user = request.user - if user.is_authenticated(): - if user in question.followed_by.all(): - question.followed_by.remove(user) - else: - response_data['success'] = 0 - response_data['message'] = u'Request mode is not supported. Please try again.' - - data = simplejson.dumps(response_data) - - except Exception, e: - response_data['message'] = str(e) - data = simplejson.dumps(response_data) - return HttpResponse(data, mimetype="application/json") - -@ajax_login_required -def mark_tag(request, tag=None, **kwargs): - action = kwargs['action'] - ts = MarkedTag.objects.filter(user=request.user, tag__name=tag) - if action == 'remove': - logging.debug('deleting tag %s' % tag) - ts.delete() - else: - reason = kwargs['reason'] - if len(ts) == 0: - try: - t = Tag.objects.get(name=tag) - mt = MarkedTag(user=request.user, reason=reason, tag=t) - mt.save() - except: - pass - else: - ts.update(reason=reason) - return HttpResponse(simplejson.dumps(''), mimetype="application/json") - -@ajax_login_required -def ajax_toggle_ignored_questions(request): - if request.user.hide_ignored_questions: - new_hide_setting = False - else: - new_hide_setting = True - request.user.hide_ignored_questions = new_hide_setting - request.user.save() - -@ajax_method -def ajax_command(request): - if 'command' not in request.POST: - return HttpResponseForbidden(mimetype="application/json") - if request.POST['command'] == 'toggle-ignored-questions': - return ajax_toggle_ignored_questions(request) - -def users(request): - is_paginated = True - sortby = request.GET.get('sort', 'reputation') - suser = request.REQUEST.get('q', "") - try: - page = int(request.GET.get('page', '1')) - except ValueError: - page = 1 - - if suser == "": - if sortby == "newest": - objects_list = Paginator(User.objects.all().order_by('-date_joined'), USERS_PAGE_SIZE) - elif sortby == "last": - objects_list = Paginator(User.objects.all().order_by('date_joined'), USERS_PAGE_SIZE) - elif sortby == "user": - objects_list = Paginator(User.objects.all().order_by('username'), USERS_PAGE_SIZE) - # default - else: - objects_list = Paginator(User.objects.all().order_by('-reputation'), USERS_PAGE_SIZE) - base_url = reverse('users') + '?sort=%s&' % sortby - else: - sortby = "reputation" - objects_list = Paginator(User.objects.extra(where=['username like %s'], params=['%' + suser + '%']).order_by('-reputation'), USERS_PAGE_SIZE) - base_url = reverse('users') + '?name=%s&sort=%s&' % (suser, sortby) - - try: - users = objects_list.page(page) - except (EmptyPage, InvalidPage): - users = objects_list.page(objects_list.num_pages) - - return render_to_response('users.html', { - "users" : users, - "suser" : suser, - "keywords" : suser, - "tab_id" : sortby, - "context" : { - 'is_paginated' : is_paginated, - 'pages': objects_list.num_pages, - 'page': page, - 'has_previous': users.has_previous(), - 'has_next': users.has_next(), - 'previous': users.previous_page_number(), - 'next': users.next_page_number(), - 'base_url' : base_url - } - - }, context_instance=RequestContext(request)) - -def user(request, id): - sort = request.GET.get('sort', 'stats') - user_view = dict((v.id, v) for v in USER_TEMPLATE_VIEWS).get(sort, USER_TEMPLATE_VIEWS[0]) - from forum import views - func = getattr(views, user_view.view_name) - return func(request, id, user_view) - -@login_required -def moderate_user(request, id): - """ajax handler of user moderation - """ - if not auth.can_moderate_users(request.user) or request.method != 'POST': - raise Http404 - if not request.is_ajax(): - return HttpResponseForbidden(mimetype="application/json") - - user = get_object_or_404(User, id=id) - form = ModerateUserForm(request.POST, instance=user) - - if form.is_valid(): - form.save() - logging.debug('data saved') - response = HttpResponse(simplejson.dumps(''), mimetype="application/json") - else: - response = HttpResponseForbidden(mimetype="application/json") - return response - -@login_required -def edit_user(request, id): - user = get_object_or_404(User, id=id) - if request.user != user: - raise Http404 - if request.method == "POST": - form = EditUserForm(user, request.POST) - if form.is_valid(): - new_email = sanitize_html(form.cleaned_data['email']) - - from django_authopenid.views import set_new_email - set_new_email(user, new_email) - - #user.username = sanitize_html(form.cleaned_data['username']) - user.real_name = sanitize_html(form.cleaned_data['realname']) - user.website = sanitize_html(form.cleaned_data['website']) - user.location = sanitize_html(form.cleaned_data['city']) - user.date_of_birth = sanitize_html(form.cleaned_data['birthday']) - if len(user.date_of_birth) == 0: - user.date_of_birth = '1900-01-01' - user.about = sanitize_html(form.cleaned_data['about']) - - user.save() - # send user updated singal if full fields have been updated - if user.email and user.real_name and user.website and user.location and \ - user.date_of_birth and user.about: - user_updated.send(sender=user.__class__, instance=user, updated_by=user) - return HttpResponseRedirect(user.get_profile_url()) - else: - form = EditUserForm(user) - return render_to_response('user_edit.html', { - 'form' : form, - 'gravatar_faq_url' : reverse('faq') + '#gravatar', - }, context_instance=RequestContext(request)) - -def user_stats(request, user_id, user_view): - user = get_object_or_404(User, id=user_id) - questions = Question.objects.extra( - select={ - 'vote_count' : 'question.score', - 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s AND f.question_id = question.id', - 'la_user_id' : 'auth_user.id', - 'la_username' : 'auth_user.username', - 'la_user_gold' : 'auth_user.gold', - 'la_user_silver' : 'auth_user.silver', - 'la_user_bronze' : 'auth_user.bronze', - 'la_user_reputation' : 'auth_user.reputation' - }, - select_params=[user_id], - tables=['question', 'auth_user'], - where=['question.deleted=False AND question.author_id=%s AND question.last_activity_by_id = auth_user.id'], - params=[user_id], - order_by=['-vote_count', '-last_activity_at'] - ).values('vote_count', - 'favorited_myself', - 'id', - 'title', - 'author_id', - 'added_at', - 'answer_accepted', - 'answer_count', - 'comment_count', - 'view_count', - 'favourite_count', - 'summary', - 'tagnames', - 'vote_up_count', - 'vote_down_count', - 'last_activity_at', - 'la_user_id', - 'la_username', - 'la_user_gold', - 'la_user_silver', - 'la_user_bronze', - 'la_user_reputation')[:100] - - answered_questions = Question.objects.extra( - select={ - 'vote_up_count' : 'answer.vote_up_count', - 'vote_down_count' : 'answer.vote_down_count', - 'answer_id' : 'answer.id', - 'accepted' : 'answer.accepted', - 'vote_count' : 'answer.score', - 'comment_count' : 'answer.comment_count' - }, - tables=['question', 'answer'], - where=['answer.deleted=False AND question.deleted=False AND answer.author_id=%s AND answer.question_id=question.id'], - params=[user_id], - order_by=['-vote_count', '-answer_id'], - select_params=[user_id] - ).distinct().values('comment_count', - 'id', - 'answer_id', - 'title', - 'author_id', - 'accepted', - 'vote_count', - 'answer_count', - 'vote_up_count', - 'vote_down_count')[:100] - - up_votes = Vote.objects.get_up_vote_count_from_user(user) - down_votes = Vote.objects.get_down_vote_count_from_user(user) - votes_today = Vote.objects.get_votes_count_today_from_user(user) - votes_total = VOTE_RULES['scope_votes_per_user_per_day'] - - question_id_set = set(map(lambda v: v['id'], list(questions))) \ - | set(map(lambda v: v['id'], list(answered_questions))) - - user_tags = Tag.objects.filter(questions__id__in = question_id_set) - try: - from django.db.models import Count - awards = Award.objects.extra( - select={'id': 'badge.id', - 'name':'badge.name', - 'description': 'badge.description', - 'type': 'badge.type'}, - tables=['award', 'badge'], - order_by=['-awarded_at'], - where=['user_id=%s AND badge_id=badge.id'], - params=[user.id] - ).values('id', 'name', 'description', 'type') - total_awards = awards.count() - awards = awards.annotate(count = Count('badge__id')) - user_tags = user_tags.annotate(user_tag_usage_count=Count('name')) - - except ImportError: - awards = Award.objects.extra( - select={'id': 'badge.id', - 'count': 'count(badge_id)', - 'name':'badge.name', - 'description': 'badge.description', - 'type': 'badge.type'}, - tables=['award', 'badge'], - order_by=['-awarded_at'], - where=['user_id=%s AND badge_id=badge.id'], - params=[user.id] - ).values('id', 'count', 'name', 'description', 'type') - total_awards = awards.count() - awards.query.group_by = ['badge_id'] - - user_tags = user_tags.extra( - select={'user_tag_usage_count': 'COUNT(1)',}, - order_by=['-user_tag_usage_count'], - ) - user_tags.query.group_by = ['name'] - - if auth.can_moderate_users(request.user): - moderate_user_form = ModerateUserForm(instance=user) - else: - moderate_user_form = None - - return render_to_response(user_view.template_file,{ - 'moderate_user_form': moderate_user_form, - "tab_name" : user_view.id, - "tab_description" : user_view.tab_description, - "page_title" : user_view.page_title, - "view_user" : user, - "questions" : questions, - "answered_questions" : answered_questions, - "up_votes" : up_votes, - "down_votes" : down_votes, - "total_votes": up_votes + down_votes, - "votes_today_left": votes_total-votes_today, - "votes_total_per_day": votes_total, - "user_tags" : user_tags[:50], - "tags" : tags, - "awards": awards, - "total_awards" : total_awards, - }, context_instance=RequestContext(request)) - -def user_recent(request, user_id, user_view): - user = get_object_or_404(User, id=user_id) - def get_type_name(type_id): - for item in TYPE_ACTIVITY: - if type_id in item: - return item[1] - - class Event: - def __init__(self, time, type, title, summary, answer_id, question_id): - self.time = time - self.type = get_type_name(type) - self.type_id = type - self.title = title - self.summary = summary - slug_title = slugify(title) - self.title_link = reverse('question', kwargs={'id':question_id}) + u'%s' % slug_title - if int(answer_id) > 0: - self.title_link += '#%s' % answer_id - - class AwardEvent: - def __init__(self, time, type, id): - self.time = time - self.type = get_type_name(type) - self.type_id = type - self.badge = get_object_or_404(Badge, id=id) - - activities = [] - # ask questions - questions = Activity.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'question.id', - 'active_at' : 'activity.active_at', - 'activity_type' : 'activity.activity_type' - }, - tables=['activity', 'question'], - where=['activity.content_type_id = %s AND activity.object_id = ' + - 'question.id AND question.deleted=False AND activity.user_id = %s AND activity.activity_type = %s'], - params=[question_type_id, user_id, TYPE_ACTIVITY_ASK_QUESTION], - order_by=['-activity.active_at'] - ).values( - 'title', - 'question_id', - 'active_at', - 'activity_type' - ) - if len(questions) > 0: - questions = [(Event(q['active_at'], q['activity_type'], q['title'], '', '0', \ - q['question_id'])) for q in questions] - activities.extend(questions) - - # answers - answers = Activity.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'question.id', - 'answer_id' : 'answer.id', - 'active_at' : 'activity.active_at', - 'activity_type' : 'activity.activity_type' - }, - tables=['activity', 'answer', 'question'], - where=['activity.content_type_id = %s AND activity.object_id = answer.id AND ' + - 'answer.question_id=question.id AND answer.deleted=False AND activity.user_id=%s AND '+ - 'activity.activity_type=%s AND question.deleted=False'], - params=[answer_type_id, user_id, TYPE_ACTIVITY_ANSWER], - order_by=['-activity.active_at'] - ).values( - 'title', - 'question_id', - 'answer_id', - 'active_at', - 'activity_type' - ) - if len(answers) > 0: - answers = [(Event(q['active_at'], q['activity_type'], q['title'], '', q['answer_id'], \ - q['question_id'])) for q in answers] - activities.extend(answers) - - # question comments - comments = Activity.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'comment.object_id', - 'added_at' : 'comment.added_at', - 'activity_type' : 'activity.activity_type' - }, - tables=['activity', 'question', 'comment'], - - where=['activity.content_type_id = %s AND activity.object_id = comment.id AND '+ - 'activity.user_id = comment.user_id AND comment.object_id=question.id AND '+ - 'comment.content_type_id=%s AND activity.user_id = %s AND activity.activity_type=%s AND ' + - 'question.deleted=False'], - params=[comment_type_id, question_type_id, user_id, TYPE_ACTIVITY_COMMENT_QUESTION], - order_by=['-comment.added_at'] - ).values( - 'title', - 'question_id', - 'added_at', - 'activity_type' - ) - - if len(comments) > 0: - comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \ - q['question_id'])) for q in comments] - activities.extend(comments) - - # answer comments - comments = Activity.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'question.id', - 'answer_id' : 'answer.id', - 'added_at' : 'comment.added_at', - 'activity_type' : 'activity.activity_type' - }, - tables=['activity', 'question', 'answer', 'comment'], - - where=['activity.content_type_id = %s AND activity.object_id = comment.id AND '+ - 'activity.user_id = comment.user_id AND comment.object_id=answer.id AND '+ - 'comment.content_type_id=%s AND question.id = answer.question_id AND '+ - 'activity.user_id = %s AND activity.activity_type=%s AND '+ - 'answer.deleted=False AND question.deleted=False'], - params=[comment_type_id, answer_type_id, user_id, TYPE_ACTIVITY_COMMENT_ANSWER], - order_by=['-comment.added_at'] - ).values( - 'title', - 'question_id', - 'answer_id', - 'added_at', - 'activity_type' - ) - - if len(comments) > 0: - comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', q['answer_id'], \ - q['question_id'])) for q in comments] - activities.extend(comments) - - # question revisions - revisions = Activity.objects.extra( - select={ - 'title' : 'question_revision.title', - 'question_id' : 'question_revision.question_id', - 'added_at' : 'activity.active_at', - 'activity_type' : 'activity.activity_type', - 'summary' : 'question_revision.summary' - }, - tables=['activity', 'question_revision', 'question'], - where=['activity.content_type_id = %s AND activity.object_id = question_revision.id AND '+ - 'question_revision.id=question.id AND question.deleted=False AND '+ - 'activity.user_id = question_revision.author_id AND activity.user_id = %s AND '+ - 'activity.activity_type=%s'], - params=[question_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_QUESTION], - order_by=['-activity.active_at'] - ).values( - 'title', - 'question_id', - 'added_at', - 'activity_type', - 'summary' - ) - - if len(revisions) > 0: - revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], '0', \ - q['question_id'])) for q in revisions] - activities.extend(revisions) - - # answer revisions - revisions = Activity.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'question.id', - 'answer_id' : 'answer.id', - 'added_at' : 'activity.active_at', - 'activity_type' : 'activity.activity_type', - 'summary' : 'answer_revision.summary' - }, - tables=['activity', 'answer_revision', 'question', 'answer'], - - where=['activity.content_type_id = %s AND activity.object_id = answer_revision.id AND '+ - 'activity.user_id = answer_revision.author_id AND activity.user_id = %s AND '+ - 'answer_revision.answer_id=answer.id AND answer.question_id = question.id AND '+ - 'question.deleted=False AND answer.deleted=False AND '+ - 'activity.activity_type=%s'], - params=[answer_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_ANSWER], - order_by=['-activity.active_at'] - ).values( - 'title', - 'question_id', - 'added_at', - 'answer_id', - 'activity_type', - 'summary' - ) - - if len(revisions) > 0: - revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], \ - q['answer_id'], q['question_id'])) for q in revisions] - activities.extend(revisions) - - # accepted answers - accept_answers = Activity.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'question.id', - 'added_at' : 'activity.active_at', - 'activity_type' : 'activity.activity_type', - }, - tables=['activity', 'answer', 'question'], - where=['activity.content_type_id = %s AND activity.object_id = answer.id AND '+ - 'activity.user_id = question.author_id AND activity.user_id = %s AND '+ - 'answer.deleted=False AND question.deleted=False AND '+ - 'answer.question_id=question.id AND activity.activity_type=%s'], - params=[answer_type_id, user_id, TYPE_ACTIVITY_MARK_ANSWER], - order_by=['-activity.active_at'] - ).values( - 'title', - 'question_id', - 'added_at', - 'activity_type', - ) - if len(accept_answers) > 0: - accept_answers = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \ - q['question_id'])) for q in accept_answers] - activities.extend(accept_answers) - #award history - awards = Activity.objects.extra( - select={ - 'badge_id' : 'badge.id', - 'awarded_at': 'award.awarded_at', - 'activity_type' : 'activity.activity_type' - }, - tables=['activity', 'award', 'badge'], - where=['activity.user_id = award.user_id AND activity.user_id = %s AND '+ - 'award.badge_id=badge.id AND activity.object_id=award.id AND activity.activity_type=%s'], - params=[user_id, TYPE_ACTIVITY_PRIZE], - order_by=['-activity.active_at'] - ).values( - 'badge_id', - 'awarded_at', - 'activity_type' - ) - if len(awards) > 0: - awards = [(AwardEvent(q['awarded_at'], q['activity_type'], q['badge_id'])) for q in awards] - activities.extend(awards) - - activities.sort(lambda x,y: cmp(y.time, x.time)) - - return render_to_response(user_view.template_file,{ - "tab_name" : user_view.id, - "tab_description" : user_view.tab_description, - "page_title" : user_view.page_title, - "view_user" : user, - "activities" : activities[:user_view.data_size] - }, context_instance=RequestContext(request)) - -def user_responses(request, user_id, user_view): - """ - We list answers for question, comments, and answer accepted by others for this user. - """ - class Response: - def __init__(self, type, title, question_id, answer_id, time, username, user_id, content): - self.type = type - self.title = title - self.titlelink = reverse('question', args=[question_id]) + u'%s#%s' % (slugify(title), answer_id) - self.time = time - self.userlink = reverse('users') + u'%s/%s/' % (user_id, username) - self.username = username - self.content = u'%s ...' % strip_tags(content)[:300] - - def __unicode__(self): - return u'%s %s' % (self.type, self.titlelink) - - user = get_object_or_404(User, id=user_id) - responses = [] - answers = Answer.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'question.id', - 'answer_id' : 'answer.id', - 'added_at' : 'answer.added_at', - 'html' : 'answer.html', - 'username' : 'auth_user.username', - 'user_id' : 'auth_user.id' - }, - select_params=[user_id], - tables=['answer', 'question', 'auth_user'], - where=['answer.question_id = question.id AND answer.deleted=False AND question.deleted=False AND '+ - 'question.author_id = %s AND answer.author_id <> %s AND answer.author_id=auth_user.id'], - params=[user_id, user_id], - order_by=['-answer.id'] - ).values( - 'title', - 'question_id', - 'answer_id', - 'added_at', - 'html', - 'username', - 'user_id' - ) - if len(answers) > 0: - answers = [(Response(TYPE_RESPONSE['QUESTION_ANSWERED'], a['title'], a['question_id'], - a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers] - responses.extend(answers) - - - # question comments - comments = Comment.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'comment.object_id', - 'added_at' : 'comment.added_at', - 'comment' : 'comment.comment', - 'username' : 'auth_user.username', - 'user_id' : 'auth_user.id' - }, - tables=['question', 'auth_user', 'comment'], - where=['question.deleted=False AND question.author_id = %s AND comment.object_id=question.id AND '+ - 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id'], - params=[user_id, question_type_id, user_id], - order_by=['-comment.added_at'] - ).values( - 'title', - 'question_id', - 'added_at', - 'comment', - 'username', - 'user_id' - ) - - if len(comments) > 0: - comments = [(Response(TYPE_RESPONSE['QUESTION_COMMENTED'], c['title'], c['question_id'], - '', c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments] - responses.extend(comments) - - # answer comments - comments = Comment.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'question.id', - 'answer_id' : 'answer.id', - 'added_at' : 'comment.added_at', - 'comment' : 'comment.comment', - 'username' : 'auth_user.username', - 'user_id' : 'auth_user.id' - }, - tables=['answer', 'auth_user', 'comment', 'question'], - where=['answer.deleted=False AND answer.author_id = %s AND comment.object_id=answer.id AND '+ - 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id '+ - 'AND question.id = answer.question_id'], - params=[user_id, answer_type_id, user_id], - order_by=['-comment.added_at'] - ).values( - 'title', - 'question_id', - 'answer_id', - 'added_at', - 'comment', - 'username', - 'user_id' - ) - - if len(comments) > 0: - comments = [(Response(TYPE_RESPONSE['ANSWER_COMMENTED'], c['title'], c['question_id'], - c['answer_id'], c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments] - responses.extend(comments) - - # answer has been accepted - answers = Answer.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'question.id', - 'answer_id' : 'answer.id', - 'added_at' : 'answer.accepted_at', - 'html' : 'answer.html', - 'username' : 'auth_user.username', - 'user_id' : 'auth_user.id' - }, - select_params=[user_id], - tables=['answer', 'question', 'auth_user'], - where=['answer.question_id = question.id AND answer.deleted=False AND question.deleted=False AND '+ - 'answer.author_id = %s AND answer.accepted=True AND question.author_id=auth_user.id'], - params=[user_id], - order_by=['-answer.id'] - ).values( - 'title', - 'question_id', - 'answer_id', - 'added_at', - 'html', - 'username', - 'user_id' - ) - if len(answers) > 0: - answers = [(Response(TYPE_RESPONSE['ANSWER_ACCEPTED'], a['title'], a['question_id'], - a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers] - responses.extend(answers) - - # sort posts by time - responses.sort(lambda x,y: cmp(y.time, x.time)) - - return render_to_response(user_view.template_file,{ - "tab_name" : user_view.id, - "tab_description" : user_view.tab_description, - "page_title" : user_view.page_title, - "view_user" : user, - "responses" : responses[:user_view.data_size], - - }, context_instance=RequestContext(request)) - -def user_votes(request, user_id, user_view): - user = get_object_or_404(User, id=user_id) - if not can_view_user_votes(request.user, user): - raise Http404 - votes = [] - question_votes = Vote.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'question.id', - 'answer_id' : 0, - 'voted_at' : 'vote.voted_at', - 'vote' : 'vote', - }, - select_params=[user_id], - tables=['vote', 'question', 'auth_user'], - where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = question.id '+ - 'AND vote.user_id=auth_user.id'], - params=[question_type_id, user_id], - order_by=['-vote.id'] - ).values( - 'title', - 'question_id', - 'answer_id', - 'voted_at', - 'vote', - ) - if(len(question_votes) > 0): - votes.extend(question_votes) - - answer_votes = Vote.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'question.id', - 'answer_id' : 'answer.id', - 'voted_at' : 'vote.voted_at', - 'vote' : 'vote', - }, - select_params=[user_id], - tables=['vote', 'answer', 'question', 'auth_user'], - where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = answer.id '+ - 'AND answer.question_id = question.id AND vote.user_id=auth_user.id'], - params=[answer_type_id, user_id], - order_by=['-vote.id'] - ).values( - 'title', - 'question_id', - 'answer_id', - 'voted_at', - 'vote', - ) - if(len(answer_votes) > 0): - votes.extend(answer_votes) - votes.sort(lambda x,y: cmp(y['voted_at'], x['voted_at'])) - return render_to_response(user_view.template_file,{ - "tab_name" : user_view.id, - "tab_description" : user_view.tab_description, - "page_title" : user_view.page_title, - "view_user" : user, - "votes" : votes[:user_view.data_size] - - }, context_instance=RequestContext(request)) - -def user_reputation(request, user_id, user_view): - user = get_object_or_404(User, id=user_id) - try: - from django.db.models import Sum - reputation = Repute.objects.extra( - select={'question_id':'question_id', - 'title': 'question.title'}, - tables=['repute', 'question'], - order_by=['-reputed_at'], - where=['user_id=%s AND question_id=question.id'], - params=[user.id] - ).values('question_id', 'title', 'reputed_at', 'reputation') - reputation = reputation.annotate(positive=Sum("positive"), negative=Sum("negative")) - except ImportError: - reputation = Repute.objects.extra( - select={'positive':'sum(positive)', 'negative':'sum(negative)', 'question_id':'question_id', - 'title': 'question.title'}, - tables=['repute', 'question'], - order_by=['-reputed_at'], - where=['user_id=%s AND question_id=question.id'], - params=[user.id] - ).values('positive', 'negative', 'question_id', 'title', 'reputed_at', 'reputation') - reputation.query.group_by = ['question_id'] - - rep_list = [] - for rep in Repute.objects.filter(user=user).order_by('reputed_at'): - dic = '[%s,%s]' % (calendar.timegm(rep.reputed_at.timetuple()) * 1000, rep.reputation) - rep_list.append(dic) - reps = ','.join(rep_list) - reps = '[%s]' % reps - - return render_to_response(user_view.template_file, { - "tab_name": user_view.id, - "tab_description": user_view.tab_description, - "page_title": user_view.page_title, - "view_user": user, - "reputation": reputation, - "reps": reps - }, context_instance=RequestContext(request)) - -def user_favorites(request, user_id, user_view): - user = get_object_or_404(User, id=user_id) - questions = Question.objects.extra( - select={ - 'vote_count' : 'question.vote_up_count + question.vote_down_count', - 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s '+ - 'AND f.question_id = question.id', - 'la_user_id' : 'auth_user.id', - 'la_username' : 'auth_user.username', - 'la_user_gold' : 'auth_user.gold', - 'la_user_silver' : 'auth_user.silver', - 'la_user_bronze' : 'auth_user.bronze', - 'la_user_reputation' : 'auth_user.reputation' - }, - select_params=[user_id], - tables=['question', 'auth_user', 'favorite_question'], - where=['question.deleted=True AND question.last_activity_by_id = auth_user.id '+ - 'AND favorite_question.question_id = question.id AND favorite_question.user_id = %s'], - params=[user_id], - order_by=['-vote_count', '-question.id'] - ).values('vote_count', - 'favorited_myself', - 'id', - 'title', - 'author_id', - 'added_at', - 'answer_accepted', - 'answer_count', - 'comment_count', - 'view_count', - 'favourite_count', - 'summary', - 'tagnames', - 'vote_up_count', - 'vote_down_count', - 'last_activity_at', - 'la_user_id', - 'la_username', - 'la_user_gold', - 'la_user_silver', - 'la_user_bronze', - 'la_user_reputation') - return render_to_response(user_view.template_file,{ - "tab_name" : user_view.id, - "tab_description" : user_view.tab_description, - "page_title" : user_view.page_title, - "questions" : questions[:user_view.data_size], - "view_user" : user - }, context_instance=RequestContext(request)) - -def user_email_subscriptions(request, user_id, user_view): - user = get_object_or_404(User, id=user_id) - if request.method == 'POST': - email_feeds_form = EditUserEmailFeedsForm(request.POST) - tag_filter_form = TagFilterSelectionForm(request.POST, instance=user) - if email_feeds_form.is_valid() and tag_filter_form.is_valid(): - - action_status = None - tag_filter_saved = tag_filter_form.save() - if tag_filter_saved: - action_status = _('changes saved') - if 'save' in request.POST: - feeds_saved = email_feeds_form.save(user) - if feeds_saved: - action_status = _('changes saved') - elif 'stop_email' in request.POST: - email_stopped = email_feeds_form.reset().save(user) - initial_values = EditUserEmailFeedsForm.NO_EMAIL_INITIAL - email_feeds_form = EditUserEmailFeedsForm(initial=initial_values) - if email_stopped: - action_status = _('email updates canceled') - else: - email_feeds_form = EditUserEmailFeedsForm() - email_feeds_form.set_initial_values(user) - tag_filter_form = TagFilterSelectionForm(instance=user) - action_status = None - return render_to_response(user_view.template_file,{ - 'tab_name':user_view.id, - 'tab_description':user_view.tab_description, - 'page_title':user_view.page_title, - 'view_user':user, - 'email_feeds_form':email_feeds_form, - 'tag_filter_selection_form':tag_filter_form, - 'action_status':action_status, - }, context_instance=RequestContext(request)) - -def question_comments(request, id): - question = get_object_or_404(Question, id=id) - user = request.user - return __comments(request, question, 'question') - -def answer_comments(request, id): - answer = get_object_or_404(Answer, id=id) - user = request.user - return __comments(request, answer, 'answer') - -def __comments(request, obj, type): - # only support get comments by ajax now - user = request.user - if request.is_ajax(): - if request.method == "GET": - response = __generate_comments_json(obj, type, user) - elif request.method == "POST": - if auth.can_add_comments(user,obj): - comment_data = request.POST.get('comment') - comment = Comment(content_object=obj, comment=comment_data, user=request.user) - comment.save() - obj.comment_count = obj.comment_count + 1 - obj.save() - response = __generate_comments_json(obj, type, user) - else: - response = HttpResponseForbidden(mimetype="application/json") - return response - -def __generate_comments_json(obj, type, user): - comments = obj.comments.all().order_by('id') - # {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null} - json_comments = [] - from forum.templatetags.extra_tags import diff_date - for comment in comments: - comment_user = comment.user - delete_url = "" - if user != None and auth.can_delete_comment(user, comment): - #/posts/392845/comments/219852/delete - #todo translate this url - delete_url = reverse(index) + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id) - json_comments.append({"id" : comment.id, - "object_id" : obj.id, - "comment_age" : diff_date(comment.added_at), - "text" : comment.comment, - "user_display_name" : comment_user.username, - "user_url" : comment_user.get_profile_url(), - "delete_url" : delete_url - }) - - data = simplejson.dumps(json_comments) - return HttpResponse(data, mimetype="application/json") - -def delete_comment(request, object_id='', comment_id='', commented_object_type=None): - response = None - commented_object = None - if commented_object_type == 'question': - commented_object = Question - elif commented_object_type == 'answer': - commented_object = Answer - - if request.is_ajax(): - comment = get_object_or_404(Comment, id=comment_id) - if auth.can_delete_comment(request.user, comment): - obj = get_object_or_404(commented_object, id=object_id) - obj.comments.remove(comment) - obj.comment_count = obj.comment_count - 1 - obj.save() - user = request.user - return __generate_comments_json(obj, commented_object_type, user) - raise PermissionDenied() - -def logout(request): - return render_to_response('logout.html', { - 'next' : get_next_url(request), - }, context_instance=RequestContext(request)) - -def badges(request): - badges = Badge.objects.all().order_by('type') - my_badges = [] - if request.user.is_authenticated(): - my_badges = Award.objects.filter(user=request.user) - my_badges.query.group_by = ['badge_id'] - - return render_to_response('badges.html', { - 'badges' : badges, - 'mybadges' : my_badges, - 'feedback_faq_url' : reverse('feedback'), - }, context_instance=RequestContext(request)) - -def badge(request, id): - badge = get_object_or_404(Badge, id=id) - awards = Award.objects.extra( - select={'id': 'auth_user.id', - 'name': 'auth_user.username', - 'rep':'auth_user.reputation', - 'gold': 'auth_user.gold', - 'silver': 'auth_user.silver', - 'bronze': 'auth_user.bronze'}, - tables=['award', 'auth_user'], - where=['badge_id=%s AND user_id=auth_user.id'], - params=[id] - ).distinct('id') - - return render_to_response('badge.html', { - 'awards' : awards, - 'badge' : badge, - }, context_instance=RequestContext(request)) - -def read_message(request): - if request.method == "POST": - if request.POST['formdata'] == 'required': - request.session['message_silent'] = 1 - if request.user.is_authenticated(): - request.user.delete_messages() - return HttpResponse('') - -def upload(request): - class FileTypeNotAllow(Exception): - pass - class FileSizeNotAllow(Exception): - pass - class UploadPermissionNotAuthorized(Exception): - pass - - #<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result> - xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>" - - try: - f = request.FILES['file-upload'] - # check upload permission - if not can_upload_files(request.user): - raise UploadPermissionNotAuthorized - - # check file type - file_name_suffix = os.path.splitext(f.name)[1].lower() - if not file_name_suffix in settings.ALLOW_FILE_TYPES: - raise FileTypeNotAllow - - # generate new file name - new_file_name = str(time.time()).replace('.', str(random.randint(0,100000))) + file_name_suffix - # use default storage to store file - default_storage.save(new_file_name, f) - # check file size - # byte - size = default_storage.size(new_file_name) - if size > settings.ALLOW_MAX_FILE_SIZE: - default_storage.delete(new_file_name) - raise FileSizeNotAllow - - result = xml_template % ('Good', '', default_storage.url(new_file_name)) - except UploadPermissionNotAuthorized: - result = xml_template % ('', _('uploading images is limited to users with >60 reputation points'), '') - except FileTypeNotAllow: - result = xml_template % ('', _("allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"), '') - except FileSizeNotAllow: - result = xml_template % ('', _("maximum upload file size is %sK") % settings.ALLOW_MAX_FILE_SIZE / 1024, '') - except Exception: - result = xml_template % ('', _('Error uploading file. Please contact the site administrator. Thank you. %s' % Exception), '') - - return HttpResponse(result, mimetype="application/xml") - -def books(request): - return HttpResponseRedirect(reverse('books') + '/mysql-zhaoyang') - -def book(request, short_name, unanswered=False): - """ - 1. questions list - 2. book info - 3. author info and blog rss items - """ - """ - List of Questions, Tagged questions, and Unanswered questions. - """ - books = Book.objects.extra(where=['short_name = %s'], params=[short_name]) - match_count = len(books) - if match_count == 0: - raise Http404 - else: - # the book info - book = books[0] - # get author info - author_info = BookAuthorInfo.objects.get(book=book) - # get author rss info - author_rss = BookAuthorRss.objects.filter(book=book) - - # get pagesize from session, if failed then get default value - user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE) - # set pagesize equal to logon user specified value in database - if request.user.is_authenticated() and request.user.questions_per_page > 0: - user_page_size = request.user.questions_per_page - - try: - page = int(request.GET.get('page', '1')) - except ValueError: - page = 1 - - 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" - - # check if request is from tagged questions - if unanswered: - # check if request is from unanswered questions - # Article.objects.filter(publications__id__exact=1) - objects = Question.objects.filter(book__id__exact=book.id, deleted=False, answer_count=0).order_by(orderby) - else: - objects = Question.objects.filter(book__id__exact=book.id, deleted=False).order_by(orderby) - - # RISK - inner join queries - objects = objects.select_related(); - objects_list = Paginator(objects, user_page_size) - questions = objects_list.page(page) - - return render_to_response('book.html', { - "book" : book, - "author_info" : author_info, - "author_rss" : author_rss, - "questions" : questions, - "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' : user_page_size - } - }, context_instance=RequestContext(request)) - -@login_required -def ask_book(request, short_name): - if request.method == "POST": - form = AskForm(request.POST) - if form.is_valid(): - added_at = datetime.datetime.now() - html = sanitize_html(markdowner.convert(form.cleaned_data['text'])) - question = Question( - title = strip_tags(form.cleaned_data['title']), - author = request.user, - added_at = added_at, - last_activity_at = added_at, - last_activity_by = request.user, - wiki = form.cleaned_data['wiki'], - tagnames = form.cleaned_data['tags'].strip(), - html = html, - summary = strip_tags(html)[:120] - ) - if question.wiki: - question.last_edited_by = question.author - question.last_edited_at = added_at - question.wikified_at = added_at - - question.save() - - # create the first revision - QuestionRevision.objects.create( - question = question, - revision = 1, - title = question.title, - author = request.user, - revised_at = added_at, - tagnames = question.tagnames, - summary = CONST['default_version'], - text = form.cleaned_data['text'] - ) - - books = Book.objects.extra(where=['short_name = %s'], params=[short_name]) - match_count = len(books) - if match_count == 1: - # the book info - book = books[0] - book.questions.add(question) - - return HttpResponseRedirect(question.get_absolute_url()) - else: - form = AskForm() - - tags = _get_tags_cache_json() - return render_to_response('ask.html', { - 'form' : form, - 'tags' : tags, - 'email_validation_faq_url': reverse('faq') + '#validate', - }, context_instance=RequestContext(request)) - -def search(request): - """ - Search by question, user and tag keywords. - For questions now we only search keywords in question title. - """ - if request.method == "GET": - keywords = request.GET.get("q") - search_type = request.GET.get("t") - try: - page = int(request.GET.get('page', '1')) - except ValueError: - page = 1 - if keywords is None: - return HttpResponseRedirect(reverse(index)) - 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)) - elif search_type == "question": - - template_file = "questions.html" - # Set flag to False by default. If it is equal to True, then need to be saved. - pagesize_changed = False - # get pagesize from session, if failed then get default value - user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE) - # set pagesize equal to logon user specified value in database - if request.user.is_authenticated() and request.user.questions_per_page > 0: - user_page_size = request.user.questions_per_page - - try: - page = int(request.GET.get('page', '1')) - # get new pagesize from UI selection - pagesize = int(request.GET.get('pagesize', user_page_size)) - if pagesize <> user_page_size: - pagesize_changed = True - - except ValueError: - page = 1 - pagesize = user_page_size - - # save this pagesize to user database - if pagesize_changed: - request.session["pagesize"] = pagesize - if request.user.is_authenticated(): - user = request.user - user.questions_per_page = pagesize - user.save() - - 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" - - if settings.USE_PG_FTS: - objects = Question.objects.filter(deleted=False).extra( - select={ - 'ranking': "ts_rank_cd(tsv, plainto_tsquery(%s), 32)", - }, - where=["tsv @@ plainto_tsquery(%s)"], - params=[keywords], - select_params=[keywords] - ).order_by('-ranking') - - elif settings.USE_SPHINX_SEARCH == True: - #search index is now free of delete questions and answers - #so there is not "antideleted" filtering here - objects = Question.search.query(keywords) - #no related selection either because we're relying on full text search here - else: - objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby) - # RISK - inner join queries - objects = objects.select_related(); - - 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 diff --git a/forum/views/writers.py b/forum/views/writers.py new file mode 100644 index 00000000..bb966b9f --- /dev/null +++ b/forum/views/writers.py @@ -0,0 +1,441 @@ +# encoding:utf-8 +import os.path +import time, datetime, random +import logging +from django.core.files.storage import default_storage +from django.shortcuts import render_to_response, get_object_or_404 +from django.contrib.auth.decorators import login_required +from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404 +from django.template import RequestContext +from django.utils.html import * +from django.utils import simplejson +from django.utils.translation import ugettext as _ +from django.core.exceptions import PermissionDenied + +from utils.html import sanitize_html +from markdown2 import Markdown +from forum.forms import * +from forum.models import * +from forum.auth import * +from forum.const import * +from forum import auth +from utils.forms import get_next_url +from forum.views.readers import _get_tags_cache_json + +# used in index page +INDEX_PAGE_SIZE = 20 +INDEX_AWARD_SIZE = 15 +INDEX_TAGS_SIZE = 100 +# used in tags list +DEFAULT_PAGE_SIZE = 60 +# used in questions +QUESTIONS_PAGE_SIZE = 10 +# used in answers +ANSWERS_PAGE_SIZE = 10 + +markdowner = Markdown(html4tags=True) + +def upload(request):#ajax upload file to a question or answer + class FileTypeNotAllow(Exception): + pass + class FileSizeNotAllow(Exception): + pass + class UploadPermissionNotAuthorized(Exception): + pass + + #<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result> + xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>" + + try: + f = request.FILES['file-upload'] + # check upload permission + if not auth.can_upload_files(request.user): + raise UploadPermissionNotAuthorized + + # check file type + file_name_suffix = os.path.splitext(f.name)[1].lower() + if not file_name_suffix in settings.ALLOW_FILE_TYPES: + raise FileTypeNotAllow + + # generate new file name + new_file_name = str(time.time()).replace('.', str(random.randint(0,100000))) + file_name_suffix + # use default storage to store file + default_storage.save(new_file_name, f) + # check file size + # byte + size = default_storage.size(new_file_name) + if size > settings.ALLOW_MAX_FILE_SIZE: + default_storage.delete(new_file_name) + raise FileSizeNotAllow + + result = xml_template % ('Good', '', default_storage.url(new_file_name)) + except UploadPermissionNotAuthorized: + result = xml_template % ('', _('uploading images is limited to users with >60 reputation points'), '') + except FileTypeNotAllow: + result = xml_template % ('', _("allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"), '') + except FileSizeNotAllow: + result = xml_template % ('', _("maximum upload file size is %sK") % settings.ALLOW_MAX_FILE_SIZE / 1024, '') + except Exception: + result = xml_template % ('', _('Error uploading file. Please contact the site administrator. Thank you. %s' % Exception), '') + + return HttpResponse(result, mimetype="application/xml") + +#@login_required #actually you can post anonymously, but then must register +def ask(request):#view used to ask a new question + """a view to ask a new question + gives space for q title, body, tags and checkbox for to post as wiki + + user can start posting a question anonymously but then + must login/register in order for the question go be shown + """ + if request.method == "POST": + form = AskForm(request.POST) + if form.is_valid(): + + added_at = datetime.datetime.now() + title = strip_tags(form.cleaned_data['title'].strip()) + wiki = form.cleaned_data['wiki'] + tagnames = form.cleaned_data['tags'].strip() + text = form.cleaned_data['text'] + html = sanitize_html(markdowner.convert(text)) + summary = strip_tags(html)[:120] + + if request.user.is_authenticated(): + author = request.user + + Question.objects.create_new( + title = title, + author = author, + added_at = added_at, + wiki = wiki, + tagnames = tagnames, + summary = summary, + text = text + ) + + return HttpResponseRedirect(question.get_absolute_url()) + else: + request.session.flush() + session_key = request.session.session_key + question = AnonymousQuestion( + session_key = session_key, + title = title, + tagnames = tagnames, + wiki = wiki, + text = text, + summary = summary, + added_at = added_at, + ip_addr = request.META['REMOTE_ADDR'], + ) + question.save() + return HttpResponseRedirect(reverse('user_signin_new_question')) + else: + form = AskForm() + + tags = _get_tags_cache_json() + return render_to_response('ask.html', { + 'form' : form, + 'tags' : tags, + 'email_validation_faq_url':reverse('faq') + '#validate', + }, context_instance=RequestContext(request)) + +@login_required +def edit_question(request, id):#edit or retag a question + """view to edit question + """ + question = get_object_or_404(Question, id=id) + if question.deleted and not auth.can_view_deleted_post(request.user, question): + raise Http404 + if auth.can_edit_post(request.user, question): + return _edit_question(request, question) + elif auth.can_retag_questions(request.user): + return _retag_question(request, question) + else: + raise Http404 + +def _retag_question(request, question):#non-url subview of edit question - just retag + """retag question sub-view used by + view "edit_question" + """ + if request.method == 'POST': + form = RetagQuestionForm(question, request.POST) + if form.is_valid(): + if form.has_changed(): + latest_revision = question.get_latest_revision() + retagged_at = datetime.datetime.now() + # Update the Question itself + Question.objects.filter(id=question.id).update( + tagnames = form.cleaned_data['tags'], + last_edited_at = retagged_at, + last_edited_by = request.user, + last_activity_at = retagged_at, + last_activity_by = request.user + ) + # Update the Question's tag associations + tags_updated = Question.objects.update_tags(question, + form.cleaned_data['tags'], request.user) + # Create a new revision + QuestionRevision.objects.create( + question = question, + title = latest_revision.title, + author = request.user, + revised_at = retagged_at, + tagnames = form.cleaned_data['tags'], + summary = CONST['retagged'], + text = latest_revision.text + ) + # send tags updated singal + tags_updated.send(sender=question.__class__, question=question) + + return HttpResponseRedirect(question.get_absolute_url()) + else: + form = RetagQuestionForm(question) + return render_to_response('question_retag.html', { + 'question': question, + 'form' : form, + 'tags' : _get_tags_cache_json(), + }, context_instance=RequestContext(request)) + +def _edit_question(request, question):#non-url subview of edit_question - just edit the body/title + latest_revision = question.get_latest_revision() + revision_form = None + if request.method == 'POST': + if 'select_revision' in request.POST: + # user has changed revistion number + revision_form = RevisionForm(question, latest_revision, request.POST) + if revision_form.is_valid(): + # Replace with those from the selected revision + form = EditQuestionForm(question, + QuestionRevision.objects.get(question=question, + revision=revision_form.cleaned_data['revision'])) + else: + form = EditQuestionForm(question, latest_revision, request.POST) + else: + # Always check modifications against the latest revision + form = EditQuestionForm(question, latest_revision, request.POST) + if form.is_valid(): + html = sanitize_html(markdowner.convert(form.cleaned_data['text'])) + if form.has_changed(): + edited_at = datetime.datetime.now() + tags_changed = (latest_revision.tagnames != + form.cleaned_data['tags']) + tags_updated = False + # Update the Question itself + updated_fields = { + 'title': form.cleaned_data['title'], + 'last_edited_at': edited_at, + 'last_edited_by': request.user, + 'last_activity_at': edited_at, + 'last_activity_by': request.user, + 'tagnames': form.cleaned_data['tags'], + 'summary': strip_tags(html)[:120], + 'html': html, + } + + # only save when it's checked + # because wiki doesn't allow to be edited if last version has been enabled already + # and we make sure this in forms. + if ('wiki' in form.cleaned_data and + form.cleaned_data['wiki']): + updated_fields['wiki'] = True + updated_fields['wikified_at'] = edited_at + + Question.objects.filter( + id=question.id).update(**updated_fields) + # Update the Question's tag associations + if tags_changed: + tags_updated = Question.objects.update_tags( + question, form.cleaned_data['tags'], request.user) + # Create a new revision + revision = QuestionRevision( + question = question, + title = form.cleaned_data['title'], + author = request.user, + revised_at = edited_at, + tagnames = form.cleaned_data['tags'], + text = form.cleaned_data['text'], + ) + if form.cleaned_data['summary']: + revision.summary = form.cleaned_data['summary'] + else: + revision.summary = 'No.%s Revision' % latest_revision.revision + revision.save() + + return HttpResponseRedirect(question.get_absolute_url()) + else: + + revision_form = RevisionForm(question, latest_revision) + form = EditQuestionForm(question, latest_revision) + return render_to_response('question_edit.html', { + 'question': question, + 'revision_form': revision_form, + 'form' : form, + 'tags' : _get_tags_cache_json() + }, context_instance=RequestContext(request)) + +@login_required +def edit_answer(request, id): + answer = get_object_or_404(Answer, id=id) + if answer.deleted and not auth.can_view_deleted_post(request.user, answer): + raise Http404 + elif not auth.can_edit_post(request.user, answer): + raise Http404 + else: + latest_revision = answer.get_latest_revision() + if request.method == "POST": + if 'select_revision' in request.POST: + # user has changed revistion number + revision_form = RevisionForm(answer, latest_revision, request.POST) + if revision_form.is_valid(): + # Replace with those from the selected revision + form = EditAnswerForm(answer, + AnswerRevision.objects.get(answer=answer, + revision=revision_form.cleaned_data['revision'])) + else: + form = EditAnswerForm(answer, latest_revision, request.POST) + else: + form = EditAnswerForm(answer, latest_revision, request.POST) + if form.is_valid(): + html = sanitize_html(markdowner.convert(form.cleaned_data['text'])) + if form.has_changed(): + edited_at = datetime.datetime.now() + updated_fields = { + 'last_edited_at': edited_at, + 'last_edited_by': request.user, + 'html': html, + } + Answer.objects.filter(id=answer.id).update(**updated_fields) + + revision = AnswerRevision( + answer=answer, + author=request.user, + revised_at=edited_at, + text=form.cleaned_data['text'] + ) + + if form.cleaned_data['summary']: + revision.summary = form.cleaned_data['summary'] + else: + revision.summary = 'No.%s Revision' % latest_revision.revision + revision.save() + + answer.question.last_activity_at = edited_at + answer.question.last_activity_by = request.user + answer.question.save() + + return HttpResponseRedirect(answer.get_absolute_url()) + else: + revision_form = RevisionForm(answer, latest_revision) + form = EditAnswerForm(answer, latest_revision) + return render_to_response('answer_edit.html', { + 'answer': answer, + 'revision_form': revision_form, + 'form': form, + }, context_instance=RequestContext(request)) + +def answer(request, id):#process a new answer + question = get_object_or_404(Question, id=id) + if request.method == "POST": + form = AnswerForm(question, request.user, request.POST) + if form.is_valid(): + wiki = form.cleaned_data['wiki'] + text = form.cleaned_data['text'] + update_time = datetime.datetime.now() + + if request.user.is_authenticated(): + Answer.objects.create_new( + question=question, + author=request.user, + added_at=update_time, + wiki=wiki, + text=text, + email_notify=form.cleaned_data['email_notify'] + ) + else: + request.session.flush() + html = sanitize_html(markdowner.convert(text)) + summary = strip_tags(html)[:120] + anon = AnonymousAnswer( + question=question, + wiki=wiki, + text=text, + summary=summary, + session_key=request.session.session_key, + ip_addr=request.META['REMOTE_ADDR'], + ) + anon.save() + return HttpResponseRedirect(reverse('user_signin_new_answer')) + + return HttpResponseRedirect(question.get_absolute_url()) + +def __generate_comments_json(obj, type, user):#non-view generates json data for the post comments + comments = obj.comments.all().order_by('id') + # {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null} + json_comments = [] + from forum.templatetags.extra_tags import diff_date + for comment in comments: + comment_user = comment.user + delete_url = "" + if user != None and auth.can_delete_comment(user, comment): + #/posts/392845/comments/219852/delete + #todo translate this url + delete_url = reverse(index) + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id) + json_comments.append({"id" : comment.id, + "object_id" : obj.id, + "comment_age" : diff_date(comment.added_at), + "text" : comment.comment, + "user_display_name" : comment_user.username, + "user_url" : comment_user.get_profile_url(), + "delete_url" : delete_url + }) + + data = simplejson.dumps(json_comments) + return HttpResponse(data, mimetype="application/json") + + +def question_comments(request, id):#ajax handler for loading comments to question + question = get_object_or_404(Question, id=id) + user = request.user + return __comments(request, question, 'question') + +def answer_comments(request, id):#ajax handler for loading comments on answer + answer = get_object_or_404(Answer, id=id) + user = request.user + return __comments(request, answer, 'answer') + +def __comments(request, obj, type):#non-view generic ajax handler to load comments to an object + # only support get post comments by ajax now + user = request.user + if request.is_ajax(): + if request.method == "GET": + response = __generate_comments_json(obj, type, user) + elif request.method == "POST": + if auth.can_add_comments(user,obj): + comment_data = request.POST.get('comment') + comment = Comment(content_object=obj, comment=comment_data, user=request.user) + comment.save() + obj.comment_count = obj.comment_count + 1 + obj.save() + response = __generate_comments_json(obj, type, user) + else: + response = HttpResponseForbidden(mimetype="application/json") + return response + +def delete_comment(request, object_id='', comment_id='', commented_object_type=None):#ajax handler to delete comment + response = None + commented_object = None + if commented_object_type == 'question': + commented_object = Question + elif commented_object_type == 'answer': + commented_object = Answer + + if request.is_ajax(): + comment = get_object_or_404(Comment, id=comment_id) + if auth.can_delete_comment(request.user, comment): + obj = get_object_or_404(commented_object, id=object_id) + obj.comments.remove(comment) + obj.comment_count = obj.comment_count - 1 + obj.save() + user = request.user + return __generate_comments_json(obj, commented_object_type, user) + raise PermissionDenied() |