diff options
34 files changed, 4824 insertions, 4827 deletions
@@ -15,6 +15,8 @@ Used for HTML sanitizer 5. Markdown2 http://code.google.com/p/python-markdown2/ +6. Django Debug Toolbar +http://github.com/robhudson/django-debug-toolbar/tree/master INSTALL STEPS: ----------------------------------------------- diff --git a/development.log b/development.log index 7ad467e0..6b96f8f5 100644 --- a/development.log +++ b/development.log @@ -1,27 +1,36 @@ # development +==Aug 4 2009, Evgeny== +===Changes=== +* commented out LocaleMiddleware - language can be now switched with settings.LANGUAGE_CODE +* added DATABASE_ENGINE line to settings_local +===Merges=== +* Adolfo's slugification of urls +* Added Chaitanyas changes for traditional login/signup and INSTALL file + ==July 26 2009, Evgeny== django_authopenid: considerably changed user interface +[comment] - sorry - this is not done yet log/forum/forms.py: -- added tag input validation using regex -- fixed bug with date type mismatch near self.fields['birthday'] = +* added tag input validation using regex +* fixed bug with date type mismatch near self.fields['birthday'] = in EditUserForm.__init__() /forum/templatetags/extra_tags.py: -- fixed date type mismatch in get_age() +* fixed date type mismatch in get_age() /templates/content/js/com.cnprog.post.js: -- fixed bug with post deletion/recovery +* fixed bug with post deletion/recovery javascript: -- changed to use of non-minified code - better for editing +* changed to use of non-minified code - better for editing and debugging /templates/question.html: -- fixed display of delete/undelete links +* fixed display of delete/undelete links templates: added comments in the beginning/end of each template diff --git a/forum/auth.py b/forum/auth.py index 0608031a..4688a69a 100644 --- a/forum/auth.py +++ b/forum/auth.py @@ -1,443 +1,443 @@ -"""
-Authorisation related functions.
-
-The actions a User is authorised to perform are dependent on their reputation
-and superuser status.
-"""
-import datetime
-from django.contrib.contenttypes.models import ContentType
-from django.db import transaction
-from models import Repute
-from models import Question
-from models import Answer
-from const import TYPE_REPUTATION
-question_type = ContentType.objects.get_for_model(Question)
-answer_type = ContentType.objects.get_for_model(Answer)
-
-VOTE_UP = 15
-FLAG_OFFENSIVE = 15
-POST_IMAGES = 15
-LEAVE_COMMENTS = 50
-UPLOAD_FILES = 60
-VOTE_DOWN = 100
-CLOSE_OWN_QUESTIONS = 250
-RETAG_OTHER_QUESTIONS = 500
-REOPEN_OWN_QUESTIONS = 500
-EDIT_COMMUNITY_WIKI_POSTS = 750
-EDIT_OTHER_POSTS = 2000
-DELETE_COMMENTS = 2000
-VIEW_OFFENSIVE_FLAGS = 2000
-DISABLE_URL_NOFOLLOW = 2000
-CLOSE_OTHER_QUESTIONS = 3000
-LOCK_POSTS = 4000
-
-VOTE_RULES = {
- 'scope_votes_per_user_per_day' : 30, # how many votes of one user has everyday
- 'scope_flags_per_user_per_day' : 5, # how many times user can flag posts everyday
- 'scope_warn_votes_left' : 10, # start when to warn user how many votes left
- 'scope_deny_unvote_days' : 1, # if 1 days passed, user can't cancel votes.
- 'scope_flags_invisible_main_page' : 3, # post doesn't show on main page if has more than 3 offensive flags
- 'scope_flags_delete_post' : 5, # post will be deleted if it has more than 5 offensive flags
-}
-
-REPUTATION_RULES = {
- 'initial_score' : 1,
- 'scope_per_day_by_upvotes' : 200,
- 'gain_by_upvoted' : 10,
- 'gain_by_answer_accepted' : 15,
- 'gain_by_accepting_answer' : 2,
- 'gain_by_downvote_canceled' : 2,
- 'gain_by_canceling_downvote' : 1,
- 'lose_by_canceling_accepted_answer' : -2,
- 'lose_by_accepted_answer_cancled' : -15,
- 'lose_by_downvoted' : -2,
- 'lose_by_flagged' : -2,
- 'lose_by_downvoting' : -1,
- 'lose_by_flagged_lastrevision_3_times': -30,
- 'lose_by_flagged_lastrevision_5_times': -100,
- 'lose_by_upvote_canceled' : -10,
-}
-
-def can_vote_up(user):
- """Determines if a User can vote Questions and Answers up."""
- return user.is_authenticated() and (
- user.reputation >= VOTE_UP or
- user.is_superuser)
-
-def can_flag_offensive(user):
- """Determines if a User can flag Questions and Answers as offensive."""
- return user.is_authenticated() and (
- user.reputation >= FLAG_OFFENSIVE or
- user.is_superuser)
-
-def can_add_comments(user):
- """Determines if a User can add comments to Questions and Answers."""
- return user.is_authenticated() and (
- user.reputation >= LEAVE_COMMENTS or
- user.is_superuser)
-
-def can_vote_down(user):
- """Determines if a User can vote Questions and Answers down."""
- return user.is_authenticated() and (
- user.reputation >= VOTE_DOWN or
- user.is_superuser)
-
-def can_retag_questions(user):
- """Determines if a User can retag Questions."""
- return user.is_authenticated() and (
- RETAG_OTHER_QUESTIONS <= user.reputation < EDIT_OTHER_POSTS or
- user.is_superuser)
-
-def can_edit_post(user, post):
- """Determines if a User can edit the given Question or Answer."""
- return user.is_authenticated() and (
- user.id == post.author_id or
- (post.wiki and user.reputation >= EDIT_COMMUNITY_WIKI_POSTS) or
- user.reputation >= EDIT_OTHER_POSTS or
- user.is_superuser)
-
-def can_delete_comment(user, comment):
- """Determines if a User can delete the given Comment."""
- return user.is_authenticated() and (
- user.id == comment.user_id or
- user.reputation >= DELETE_COMMENTS or
- user.is_superuser)
-
-def can_view_offensive_flags(user):
- """Determines if a User can view offensive flag counts."""
- return user.is_authenticated() and (
- user.reputation >= VIEW_OFFENSIVE_FLAGS or
- user.is_superuser)
-
-def can_close_question(user, question):
- """Determines if a User can close the given Question."""
- return user.is_authenticated() and (
- (user.id == question.author_id and
- user.reputation >= CLOSE_OWN_QUESTIONS) or
- user.reputation >= CLOSE_OTHER_QUESTIONS or
- user.is_superuser)
-
-def can_lock_posts(user):
- """Determines if a User can lock Questions or Answers."""
- return user.is_authenticated() and (
- user.reputation >= LOCK_POSTS or
- user.is_superuser)
-
-def can_follow_url(user):
- """Determines if the URL link can be followed by Google search engine."""
- return user.reputation >= DISABLE_URL_NOFOLLOW
-
-def can_accept_answer(user, question, answer):
- return (user.is_authenticated() and
- question.author != answer.author and
- question.author == user) or user.is_superuser
-
-# now only support to reopen own question except superuser
-def can_reopen_question(user, question):
- return (user.is_authenticated() and
- user.id == question.author_id and
- user.reputation >= REOPEN_OWN_QUESTIONS) or user.is_superuser
-
-def can_delete_post(user, post):
- return (user.is_authenticated() and
- user.id == post.author_id) or user.is_superuser
-
-def can_view_deleted_post(user, post):
- return user.is_superuser
-
-# user preferences view permissions
-def is_user_self(request_user, target_user):
- return (request_user.is_authenticated() and request_user == target_user)
-
-def can_view_user_votes(request_user, target_user):
- return (request_user.is_authenticated() and request_user == target_user)
-
-def can_view_user_preferences(request_user, target_user):
- return (request_user.is_authenticated() and request_user == target_user)
-
-def can_view_user_edit(request_user, target_user):
- return (request_user.is_authenticated() and request_user == target_user)
-
-def can_upload_files(request_user):
- return (request_user.is_authenticated() and request_user.reputation >= UPLOAD_FILES) or \
- request_user.is_superuser
-
-###########################################
-## actions and reputation changes event
-###########################################
-def calculate_reputation(origin, offset):
- result = int(origin) + int(offset)
- return result if result > 0 else 1
-
-@transaction.commit_on_success
-def onFlaggedItem(item, post, user):
-
- item.save()
- post.offensive_flag_count = post.offensive_flag_count + 1
- post.save()
-
- post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged']))
- post.author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged']),
- question=question, reputed_at=datetime.datetime.now(),
- reputation_type=-4,
- reputation=post.author.reputation)
- reputation.save()
-
- #todo: These should be updated to work on same revisions.
- if post.offensive_flag_count == VOTE_RULES['scope_flags_invisible_main_page'] :
- post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']))
- post.author.save()
-
- reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-6,
- reputation=post.author.reputation)
- reputation.save()
-
- elif post.offensive_flag_count == VOTE_RULES['scope_flags_delete_post']:
- post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']))
- post.author.save()
-
- reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-7,
- reputation=post.author.reputation)
- reputation.save()
-
- post.deleted = True
- #post.deleted_at = datetime.datetime.now()
- #post.deleted_by = Admin
- post.save()
-
-
-@transaction.commit_on_success
-def onAnswerAccept(answer, user):
- answer.accepted = True
- answer.accepted_at = datetime.datetime.now()
- answer.question.answer_accepted = True
- answer.save()
- answer.question.save()
-
- answer.author.reputation = calculate_reputation(answer.author.reputation,
- int(REPUTATION_RULES['gain_by_answer_accepted']))
- answer.author.save()
- reputation = Repute(user=answer.author,
- positive=int(REPUTATION_RULES['gain_by_answer_accepted']),
- question=answer.question,
- reputed_at=datetime.datetime.now(),
- reputation_type=2,
- reputation=answer.author.reputation)
- reputation.save()
-
- user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['gain_by_accepting_answer']))
- user.save()
- reputation = Repute(user=user,
- positive=int(REPUTATION_RULES['gain_by_accepting_answer']),
- question=answer.question,
- reputed_at=datetime.datetime.now(),
- reputation_type=3,
- reputation=user.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onAnswerAcceptCanceled(answer, user):
- answer.accepted = False
- answer.accepted_at = None
- answer.question.answer_accepted = False
- answer.save()
- answer.question.save()
-
- answer.author.reputation = calculate_reputation(answer.author.reputation,
- int(REPUTATION_RULES['lose_by_accepted_answer_cancled']))
- answer.author.save()
- reputation = Repute(user=answer.author,
- negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']),
- question=answer.question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-2,
- reputation=answer.author.reputation)
- reputation.save()
-
- user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['lose_by_canceling_accepted_answer']))
- user.save()
- reputation = Repute(user=user,
- negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']),
- question=answer.question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-1,
- reputation=user.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onUpVoted(vote, post, user):
- vote.save()
-
- post.vote_up_count = int(post.vote_up_count) + 1
- post.score = int(post.score) + 1
- post.save()
-
- if not post.wiki:
- author = post.author
- if Repute.objects.get_reputation_by_upvoted_today(author) < int(REPUTATION_RULES['scope_per_day_by_upvotes']):
- author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['gain_by_upvoted']))
- author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=author,
- positive=int(REPUTATION_RULES['gain_by_upvoted']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=1,
- reputation=author.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onUpVotedCanceled(vote, post, user):
- vote.delete()
-
- post.vote_up_count = int(post.vote_up_count) - 1
- if post.vote_up_count < 0:
- post.vote_up_count = 0
- post.score = int(post.score) - 1
- post.save()
-
- if not post.wiki:
- author = post.author
- author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['lose_by_upvote_canceled']))
- author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=author,
- negative=int(REPUTATION_RULES['lose_by_upvote_canceled']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-8,
- reputation=author.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onDownVoted(vote, post, user):
- vote.save()
-
- post.vote_down_count = int(post.vote_down_count) + 1
- post.score = int(post.score) - 1
- post.save()
-
- if not post.wiki:
- author = post.author
- author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['lose_by_downvoted']))
- author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=author,
- negative=int(REPUTATION_RULES['lose_by_downvoted']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-3,
- reputation=author.reputation)
- reputation.save()
-
- user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['lose_by_downvoting']))
- user.save()
-
- reputation = Repute(user=user,
- negative=int(REPUTATION_RULES['lose_by_downvoting']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-5,
- reputation=user.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onDownVotedCanceled(vote, post, user):
- vote.delete()
-
- post.vote_down_count = int(post.vote_down_count) - 1
- if post.vote_down_count < 0:
- post.vote_down_count = 0
- post.score = post.score + 1
- post.save()
-
- if not post.wiki:
- author = post.author
- author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['gain_by_downvote_canceled']))
- author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=author,
- positive=int(REPUTATION_RULES['gain_by_downvote_canceled']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=4,
- reputation=author.reputation)
- reputation.save()
-
- user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['gain_by_canceling_downvote']))
- user.save()
-
- reputation = Repute(user=user,
- positive=int(REPUTATION_RULES['gain_by_canceling_downvote']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=5,
- reputation=user.reputation)
- reputation.save()
-
-def onDeleteCanceled(post, user):
- post.deleted = False
- post.deleted_by = None
- post.deleted_at = None
- post.save()
- for tag in list(post.tags.all()):
- if tag.used_count == 1 and tag.deleted:
- tag.deleted = False
- tag.deleted_by = None
- tag.deleted_at = None
- tag.save()
-
-def onDeleted(post, user):
- post.deleted = True
- post.deleted_by = user
- post.deleted_at = datetime.datetime.now()
- post.save()
-
- for tag in list(post.tags.all()):
- if tag.used_count == 1:
- tag.deleted = True
- tag.deleted_by = user
- tag.deleted_at = datetime.datetime.now()
- tag.save()
+""" +Authorisation related functions. + +The actions a User is authorised to perform are dependent on their reputation +and superuser status. +""" +import datetime +from django.contrib.contenttypes.models import ContentType +from django.db import transaction +from models import Repute +from models import Question +from models import Answer +from const import TYPE_REPUTATION +question_type = ContentType.objects.get_for_model(Question) +answer_type = ContentType.objects.get_for_model(Answer) + +VOTE_UP = 15 +FLAG_OFFENSIVE = 15 +POST_IMAGES = 15 +LEAVE_COMMENTS = 50 +UPLOAD_FILES = 60 +VOTE_DOWN = 100 +CLOSE_OWN_QUESTIONS = 250 +RETAG_OTHER_QUESTIONS = 500 +REOPEN_OWN_QUESTIONS = 500 +EDIT_COMMUNITY_WIKI_POSTS = 750 +EDIT_OTHER_POSTS = 2000 +DELETE_COMMENTS = 2000 +VIEW_OFFENSIVE_FLAGS = 2000 +DISABLE_URL_NOFOLLOW = 2000 +CLOSE_OTHER_QUESTIONS = 3000 +LOCK_POSTS = 4000 + +VOTE_RULES = { + 'scope_votes_per_user_per_day' : 30, # how many votes of one user has everyday + 'scope_flags_per_user_per_day' : 5, # how many times user can flag posts everyday + 'scope_warn_votes_left' : 10, # start when to warn user how many votes left + 'scope_deny_unvote_days' : 1, # if 1 days passed, user can't cancel votes. + 'scope_flags_invisible_main_page' : 3, # post doesn't show on main page if has more than 3 offensive flags + 'scope_flags_delete_post' : 5, # post will be deleted if it has more than 5 offensive flags +} + +REPUTATION_RULES = { + 'initial_score' : 1, + 'scope_per_day_by_upvotes' : 200, + 'gain_by_upvoted' : 10, + 'gain_by_answer_accepted' : 15, + 'gain_by_accepting_answer' : 2, + 'gain_by_downvote_canceled' : 2, + 'gain_by_canceling_downvote' : 1, + 'lose_by_canceling_accepted_answer' : -2, + 'lose_by_accepted_answer_cancled' : -15, + 'lose_by_downvoted' : -2, + 'lose_by_flagged' : -2, + 'lose_by_downvoting' : -1, + 'lose_by_flagged_lastrevision_3_times': -30, + 'lose_by_flagged_lastrevision_5_times': -100, + 'lose_by_upvote_canceled' : -10, +} + +def can_vote_up(user): + """Determines if a User can vote Questions and Answers up.""" + return user.is_authenticated() and ( + user.reputation >= VOTE_UP or + user.is_superuser) + +def can_flag_offensive(user): + """Determines if a User can flag Questions and Answers as offensive.""" + return user.is_authenticated() and ( + user.reputation >= FLAG_OFFENSIVE or + user.is_superuser) + +def can_add_comments(user): + """Determines if a User can add comments to Questions and Answers.""" + return user.is_authenticated() and ( + user.reputation >= LEAVE_COMMENTS or + user.is_superuser) + +def can_vote_down(user): + """Determines if a User can vote Questions and Answers down.""" + return user.is_authenticated() and ( + user.reputation >= VOTE_DOWN or + user.is_superuser) + +def can_retag_questions(user): + """Determines if a User can retag Questions.""" + return user.is_authenticated() and ( + RETAG_OTHER_QUESTIONS <= user.reputation < EDIT_OTHER_POSTS or + user.is_superuser) + +def can_edit_post(user, post): + """Determines if a User can edit the given Question or Answer.""" + return user.is_authenticated() and ( + user.id == post.author_id or + (post.wiki and user.reputation >= EDIT_COMMUNITY_WIKI_POSTS) or + user.reputation >= EDIT_OTHER_POSTS or + user.is_superuser) + +def can_delete_comment(user, comment): + """Determines if a User can delete the given Comment.""" + return user.is_authenticated() and ( + user.id == comment.user_id or + user.reputation >= DELETE_COMMENTS or + user.is_superuser) + +def can_view_offensive_flags(user): + """Determines if a User can view offensive flag counts.""" + return user.is_authenticated() and ( + user.reputation >= VIEW_OFFENSIVE_FLAGS or + user.is_superuser) + +def can_close_question(user, question): + """Determines if a User can close the given Question.""" + return user.is_authenticated() and ( + (user.id == question.author_id and + user.reputation >= CLOSE_OWN_QUESTIONS) or + user.reputation >= CLOSE_OTHER_QUESTIONS or + user.is_superuser) + +def can_lock_posts(user): + """Determines if a User can lock Questions or Answers.""" + return user.is_authenticated() and ( + user.reputation >= LOCK_POSTS or + user.is_superuser) + +def can_follow_url(user): + """Determines if the URL link can be followed by Google search engine.""" + return user.reputation >= DISABLE_URL_NOFOLLOW + +def can_accept_answer(user, question, answer): + return (user.is_authenticated() and + question.author != answer.author and + question.author == user) or user.is_superuser + +# now only support to reopen own question except superuser +def can_reopen_question(user, question): + return (user.is_authenticated() and + user.id == question.author_id and + user.reputation >= REOPEN_OWN_QUESTIONS) or user.is_superuser + +def can_delete_post(user, post): + return (user.is_authenticated() and + user.id == post.author_id) or user.is_superuser + +def can_view_deleted_post(user, post): + return user.is_superuser + +# user preferences view permissions +def is_user_self(request_user, target_user): + return (request_user.is_authenticated() and request_user == target_user) + +def can_view_user_votes(request_user, target_user): + return (request_user.is_authenticated() and request_user == target_user) + +def can_view_user_preferences(request_user, target_user): + return (request_user.is_authenticated() and request_user == target_user) + +def can_view_user_edit(request_user, target_user): + return (request_user.is_authenticated() and request_user == target_user) + +def can_upload_files(request_user): + return (request_user.is_authenticated() and request_user.reputation >= UPLOAD_FILES) or \ + request_user.is_superuser + +########################################### +## actions and reputation changes event +########################################### +def calculate_reputation(origin, offset): + result = int(origin) + int(offset) + return result if result > 0 else 1 + +@transaction.commit_on_success +def onFlaggedItem(item, post, user): + + item.save() + post.offensive_flag_count = post.offensive_flag_count + 1 + post.save() + + post.author.reputation = calculate_reputation(post.author.reputation, + int(REPUTATION_RULES['lose_by_flagged'])) + post.author.save() + + question = post + if ContentType.objects.get_for_model(post) == answer_type: + question = post.question + + reputation = Repute(user=post.author, + negative=int(REPUTATION_RULES['lose_by_flagged']), + question=question, reputed_at=datetime.datetime.now(), + reputation_type=-4, + reputation=post.author.reputation) + reputation.save() + + #todo: These should be updated to work on same revisions. + if post.offensive_flag_count == VOTE_RULES['scope_flags_invisible_main_page'] : + post.author.reputation = calculate_reputation(post.author.reputation, + int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times'])) + post.author.save() + + reputation = Repute(user=post.author, + negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=-6, + reputation=post.author.reputation) + reputation.save() + + elif post.offensive_flag_count == VOTE_RULES['scope_flags_delete_post']: + post.author.reputation = calculate_reputation(post.author.reputation, + int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times'])) + post.author.save() + + reputation = Repute(user=post.author, + negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=-7, + reputation=post.author.reputation) + reputation.save() + + post.deleted = True + #post.deleted_at = datetime.datetime.now() + #post.deleted_by = Admin + post.save() + + +@transaction.commit_on_success +def onAnswerAccept(answer, user): + answer.accepted = True + answer.accepted_at = datetime.datetime.now() + answer.question.answer_accepted = True + answer.save() + answer.question.save() + + answer.author.reputation = calculate_reputation(answer.author.reputation, + int(REPUTATION_RULES['gain_by_answer_accepted'])) + answer.author.save() + reputation = Repute(user=answer.author, + positive=int(REPUTATION_RULES['gain_by_answer_accepted']), + question=answer.question, + reputed_at=datetime.datetime.now(), + reputation_type=2, + reputation=answer.author.reputation) + reputation.save() + + user.reputation = calculate_reputation(user.reputation, + int(REPUTATION_RULES['gain_by_accepting_answer'])) + user.save() + reputation = Repute(user=user, + positive=int(REPUTATION_RULES['gain_by_accepting_answer']), + question=answer.question, + reputed_at=datetime.datetime.now(), + reputation_type=3, + reputation=user.reputation) + reputation.save() + +@transaction.commit_on_success +def onAnswerAcceptCanceled(answer, user): + answer.accepted = False + answer.accepted_at = None + answer.question.answer_accepted = False + answer.save() + answer.question.save() + + answer.author.reputation = calculate_reputation(answer.author.reputation, + int(REPUTATION_RULES['lose_by_accepted_answer_cancled'])) + answer.author.save() + reputation = Repute(user=answer.author, + negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']), + question=answer.question, + reputed_at=datetime.datetime.now(), + reputation_type=-2, + reputation=answer.author.reputation) + reputation.save() + + user.reputation = calculate_reputation(user.reputation, + int(REPUTATION_RULES['lose_by_canceling_accepted_answer'])) + user.save() + reputation = Repute(user=user, + negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']), + question=answer.question, + reputed_at=datetime.datetime.now(), + reputation_type=-1, + reputation=user.reputation) + reputation.save() + +@transaction.commit_on_success +def onUpVoted(vote, post, user): + vote.save() + + post.vote_up_count = int(post.vote_up_count) + 1 + post.score = int(post.score) + 1 + post.save() + + if not post.wiki: + author = post.author + if Repute.objects.get_reputation_by_upvoted_today(author) < int(REPUTATION_RULES['scope_per_day_by_upvotes']): + author.reputation = calculate_reputation(author.reputation, + int(REPUTATION_RULES['gain_by_upvoted'])) + author.save() + + question = post + if ContentType.objects.get_for_model(post) == answer_type: + question = post.question + + reputation = Repute(user=author, + positive=int(REPUTATION_RULES['gain_by_upvoted']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=1, + reputation=author.reputation) + reputation.save() + +@transaction.commit_on_success +def onUpVotedCanceled(vote, post, user): + vote.delete() + + post.vote_up_count = int(post.vote_up_count) - 1 + if post.vote_up_count < 0: + post.vote_up_count = 0 + post.score = int(post.score) - 1 + post.save() + + if not post.wiki: + author = post.author + author.reputation = calculate_reputation(author.reputation, + int(REPUTATION_RULES['lose_by_upvote_canceled'])) + author.save() + + question = post + if ContentType.objects.get_for_model(post) == answer_type: + question = post.question + + reputation = Repute(user=author, + negative=int(REPUTATION_RULES['lose_by_upvote_canceled']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=-8, + reputation=author.reputation) + reputation.save() + +@transaction.commit_on_success +def onDownVoted(vote, post, user): + vote.save() + + post.vote_down_count = int(post.vote_down_count) + 1 + post.score = int(post.score) - 1 + post.save() + + if not post.wiki: + author = post.author + author.reputation = calculate_reputation(author.reputation, + int(REPUTATION_RULES['lose_by_downvoted'])) + author.save() + + question = post + if ContentType.objects.get_for_model(post) == answer_type: + question = post.question + + reputation = Repute(user=author, + negative=int(REPUTATION_RULES['lose_by_downvoted']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=-3, + reputation=author.reputation) + reputation.save() + + user.reputation = calculate_reputation(user.reputation, + int(REPUTATION_RULES['lose_by_downvoting'])) + user.save() + + reputation = Repute(user=user, + negative=int(REPUTATION_RULES['lose_by_downvoting']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=-5, + reputation=user.reputation) + reputation.save() + +@transaction.commit_on_success +def onDownVotedCanceled(vote, post, user): + vote.delete() + + post.vote_down_count = int(post.vote_down_count) - 1 + if post.vote_down_count < 0: + post.vote_down_count = 0 + post.score = post.score + 1 + post.save() + + if not post.wiki: + author = post.author + author.reputation = calculate_reputation(author.reputation, + int(REPUTATION_RULES['gain_by_downvote_canceled'])) + author.save() + + question = post + if ContentType.objects.get_for_model(post) == answer_type: + question = post.question + + reputation = Repute(user=author, + positive=int(REPUTATION_RULES['gain_by_downvote_canceled']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=4, + reputation=author.reputation) + reputation.save() + + user.reputation = calculate_reputation(user.reputation, + int(REPUTATION_RULES['gain_by_canceling_downvote'])) + user.save() + + reputation = Repute(user=user, + positive=int(REPUTATION_RULES['gain_by_canceling_downvote']), + question=question, + reputed_at=datetime.datetime.now(), + reputation_type=5, + reputation=user.reputation) + reputation.save() + +def onDeleteCanceled(post, user): + post.deleted = False + post.deleted_by = None + post.deleted_at = None + post.save() + for tag in list(post.tags.all()): + if tag.used_count == 1 and tag.deleted: + tag.deleted = False + tag.deleted_by = None + tag.deleted_at = None + tag.save() + +def onDeleted(post, user): + post.deleted = True + post.deleted_by = user + post.deleted_at = datetime.datetime.now() + post.save() + + for tag in list(post.tags.all()): + if tag.used_count == 1: + tag.deleted = True + tag.deleted_by = user + tag.deleted_at = datetime.datetime.now() + tag.save() diff --git a/forum/feed.py b/forum/feed.py index a4218630..6374ba71 100644 --- a/forum/feed.py +++ b/forum/feed.py @@ -1,43 +1,43 @@ -#!/usr/bin/env python
-#encoding:utf-8
-#-------------------------------------------------------------------------------
-# Name: Syndication feed class for subsribtion
-# Purpose:
-#
-# Author: Mike
-#
-# Created: 29/01/2009
-# Copyright: (c) CNPROG.COM 2009
-# Licence: GPL V2
-#-------------------------------------------------------------------------------
-from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
-from django.utils.translation import ugettext as _
-from models import Question
-class RssLastestQuestionsFeed(Feed):
- title = _('site title') + _(' - ') + _('site slogan') + _(' - ')+ _('latest questions')
- #EDIT!!!
- link = 'http://where.com/questions/'
- description = _('meta site content')
- #ttl = 10
- copyright = _('copyright message')
-
- def item_link(self, item):
- return '/questions/%s/' % item.id
-
- def item_author_name(self, item):
- return item.author.username
-
- def item_author_link(self, item):
- return item.author.get_profile_url()
-
- def item_pubdate(self, item):
- return item.added_at
-
- def items(self, item):
- return Question.objects.filter(deleted=False).order_by('-added_at')[:30]
-
-def main():
- pass
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python +#encoding:utf-8 +#------------------------------------------------------------------------------- +# Name: Syndication feed class for subsribtion +# Purpose: +# +# Author: Mike +# +# Created: 29/01/2009 +# Copyright: (c) CNPROG.COM 2009 +# Licence: GPL V2 +#------------------------------------------------------------------------------- +from django.contrib.syndication.feeds import Feed, FeedDoesNotExist +from django.utils.translation import ugettext as _ +from models import Question +class RssLastestQuestionsFeed(Feed): + title = _('site title') + _(' - ') + _('site slogan') + _(' - ')+ _('latest questions') + #EDIT!!! + link = 'http://where.com/questions/' + description = _('meta site content') + #ttl = 10 + copyright = _('copyright message') + + def item_link(self, item): + return '/questions/%s/' % item.id + + def item_author_name(self, item): + return item.author.username + + def item_author_link(self, item): + return item.author.get_profile_url() + + def item_pubdate(self, item): + return item.added_at + + def items(self, item): + return Question.objects.filter(deleted=False).order_by('-added_at')[:30] + +def main(): + pass + +if __name__ == '__main__': + main() diff --git a/forum/forms.py b/forum/forms.py index 1b811ad9..9d866720 100644 --- a/forum/forms.py +++ b/forum/forms.py @@ -1,193 +1,193 @@ -import re
-from datetime import date
-from django import forms
-from models import *
-from const import *
-from django.utils.translation import ugettext as _
-
-class TitleField(forms.CharField):
- def __init__(self, *args, **kwargs):
- super(TitleField, self).__init__(*args, **kwargs)
- self.required = True
- self.widget = forms.TextInput(attrs={'size' : 70, 'autocomplete' : 'off'})
- self.max_length = 255
- self.label = _('title')
- self.help_text = _('please enter a descriptive title for your question')
- self.initial = ''
-
- def clean(self, value):
- if len(value) < 10:
- raise forms.ValidationError(_('title must be > 10 characters'))
-
- return value
-
-class EditorField(forms.CharField):
- def __init__(self, *args, **kwargs):
- super(EditorField, self).__init__(*args, **kwargs)
- self.required = True
- self.widget = forms.Textarea(attrs={'id':'editor'})
- self.label = _('content')
- self.help_text = u''
- self.initial = ''
-
- def clean(self, value):
- if len(value) < 10:
- raise forms.ValidationError(_('question content must be > 10 characters'))
-
- return value
-
-class TagNamesField(forms.CharField):
- def __init__(self, *args, **kwargs):
- super(TagNamesField, self).__init__(*args, **kwargs)
- self.required = True
- self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'})
- self.max_length = 255
- self.label = _('tags')
- self.help_text = _('please use space to separate tags (this enables autocomplete feature)')
- self.initial = ''
-
- def clean(self, value):
- value = super(TagNamesField, self).clean(value)
- data = value.strip()
- if len(data) < 1:
- raise forms.ValidationError(_('tags are required'))
- list = data.split(' ')
- list_temp = []
- if len(list) > 5:
- raise forms.ValidationError(_('please use 5 tags or less'))
- for tag in list:
- if len(tag) > 20:
- raise forms.ValidationError(_('tags must be shorter than 20 characters'))
- #take tag regex from settings
- tagname_re = re.compile(r'[a-z0-9]+')
- if not tagname_re.match(tag):
- raise forms.ValidationError(_('please use following characters in tags: letters \'a-z\', numbers, and characters \'.-_#\''))
- # only keep one same tag
- if tag not in list_temp and len(tag.strip()) > 0:
- list_temp.append(tag)
- return u' '.join(list_temp)
-
-class WikiField(forms.BooleanField):
- def __init__(self, *args, **kwargs):
- super(WikiField, self).__init__(*args, **kwargs)
- self.required = False
- self.label = _('community wiki')
- self.help_text = _('if you choose community wiki option, the question and answer do not generate points and name of author will not be shown')
-
-
-class SummaryField(forms.CharField):
- def __init__(self, *args, **kwargs):
- super(SummaryField, self).__init__(*args, **kwargs)
- self.required = False
- self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'})
- self.max_length = 300
- self.label = _('update summary:')
- self.help_text = _('enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)')
-
-class AskForm(forms.Form):
- title = TitleField()
- text = EditorField()
- tags = TagNamesField()
- wiki = WikiField()
-
- openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
- user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
-
-
-
-class AnswerForm(forms.Form):
- text = EditorField()
- wiki = WikiField()
- openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
- user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- def __init__(self, question, *args, **kwargs):
- super(AnswerForm, self).__init__(*args, **kwargs)
- if question.wiki:
- self.fields['wiki'].initial = True
-
-class CloseForm(forms.Form):
- reason = forms.ChoiceField(choices=CLOSE_REASONS)
-
-class RetagQuestionForm(forms.Form):
- tags = TagNamesField()
- # initialize the default values
- def __init__(self, question, *args, **kwargs):
- super(RetagQuestionForm, self).__init__(*args, **kwargs)
- self.fields['tags'].initial = question.tagnames
-
-class RevisionForm(forms.Form):
- """
- Lists revisions of a Question or Answer
- """
- revision = forms.ChoiceField(widget=forms.Select(attrs={'style' : 'width:520px'}))
-
- def __init__(self, post, latest_revision, *args, **kwargs):
- super(RevisionForm, self).__init__(*args, **kwargs)
- revisions = post.revisions.all().values_list(
- 'revision', 'author__username', 'revised_at', 'summary')
- date_format = '%c'
- self.fields['revision'].choices = [
- (r[0], u'%s - %s (%s) %s' % (r[0], r[1], r[2].strftime(date_format), r[3]))
- for r in revisions]
- self.fields['revision'].initial = latest_revision.revision
-
-class EditQuestionForm(forms.Form):
- title = TitleField()
- text = EditorField()
- tags = TagNamesField()
- summary = SummaryField()
-
- def __init__(self, question, revision, *args, **kwargs):
- super(EditQuestionForm, self).__init__(*args, **kwargs)
- self.fields['title'].initial = revision.title
- self.fields['text'].initial = revision.text
- self.fields['tags'].initial = revision.tagnames
- # Once wiki mode is enabled, it can't be disabled
- if not question.wiki:
- self.fields['wiki'] = WikiField()
-
-class EditAnswerForm(forms.Form):
- text = EditorField()
- summary = SummaryField()
-
- def __init__(self, answer, revision, *args, **kwargs):
- super(EditAnswerForm, self).__init__(*args, **kwargs)
- self.fields['text'].initial = revision.text
-
-class EditUserForm(forms.Form):
- email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- realname = forms.CharField(label=_('Real name'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- website = forms.URLField(label=_('Website'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- city = forms.CharField(label=_('Location'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- birthday = forms.DateField(label=_('Date of birth'), help_text=_('will not be shown, used to calculate age, format: YYYY-MM-DD'), required=False, widget=forms.TextInput(attrs={'size' : 35}))
- about = forms.CharField(label=_('Profile'), required=False, widget=forms.Textarea(attrs={'cols' : 60}))
-
- def __init__(self, user, *args, **kwargs):
- super(EditUserForm, self).__init__(*args, **kwargs)
- self.fields['email'].initial = user.email
- self.fields['realname'].initial = user.real_name
- self.fields['website'].initial = user.website
- self.fields['city'].initial = user.location
-
- if user.date_of_birth is not None:
- self.fields['birthday'].initial = user.date_of_birth
- else:
- self.fields['birthday'].initial = '1990-01-01'
- self.fields['about'].initial = user.about
- self.user = user
-
- def clean_email(self):
- """For security reason one unique email in database"""
- if self.user.email != self.cleaned_data['email']:
- if 'email' in self.cleaned_data:
- try:
- user = User.objects.get(email = self.cleaned_data['email'])
- except User.DoesNotExist:
- return self.cleaned_data['email']
- except User.MultipleObjectsReturned:
- raise forms.ValidationError(_('this email has already been registered, please use another one'))
- raise forms.ValidationError(_('this email has already been registered, please use another one'))
- else:
- return self.cleaned_data['email']
+import re +from datetime import date +from django import forms +from models import * +from const import * +from django.utils.translation import ugettext as _ + +class TitleField(forms.CharField): + def __init__(self, *args, **kwargs): + super(TitleField, self).__init__(*args, **kwargs) + self.required = True + self.widget = forms.TextInput(attrs={'size' : 70, 'autocomplete' : 'off'}) + self.max_length = 255 + self.label = _('title') + self.help_text = _('please enter a descriptive title for your question') + self.initial = '' + + def clean(self, value): + if len(value) < 10: + raise forms.ValidationError(_('title must be > 10 characters')) + + return value + +class EditorField(forms.CharField): + def __init__(self, *args, **kwargs): + super(EditorField, self).__init__(*args, **kwargs) + self.required = True + self.widget = forms.Textarea(attrs={'id':'editor'}) + self.label = _('content') + self.help_text = u'' + self.initial = '' + + def clean(self, value): + if len(value) < 10: + raise forms.ValidationError(_('question content must be > 10 characters')) + + return value + +class TagNamesField(forms.CharField): + def __init__(self, *args, **kwargs): + super(TagNamesField, self).__init__(*args, **kwargs) + self.required = True + self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'}) + self.max_length = 255 + self.label = _('tags') + self.help_text = _('please use space to separate tags (this enables autocomplete feature)') + self.initial = '' + + def clean(self, value): + value = super(TagNamesField, self).clean(value) + data = value.strip() + if len(data) < 1: + raise forms.ValidationError(_('tags are required')) + list = data.split(' ') + list_temp = [] + if len(list) > 5: + raise forms.ValidationError(_('please use 5 tags or less')) + for tag in list: + if len(tag) > 20: + raise forms.ValidationError(_('tags must be shorter than 20 characters')) + #take tag regex from settings + tagname_re = re.compile(r'[a-z0-9]+') + if not tagname_re.match(tag): + raise forms.ValidationError(_('please use following characters in tags: letters \'a-z\', numbers, and characters \'.-_#\'')) + # only keep one same tag + if tag not in list_temp and len(tag.strip()) > 0: + list_temp.append(tag) + return u' '.join(list_temp) + +class WikiField(forms.BooleanField): + def __init__(self, *args, **kwargs): + super(WikiField, self).__init__(*args, **kwargs) + self.required = False + self.label = _('community wiki') + self.help_text = _('if you choose community wiki option, the question and answer do not generate points and name of author will not be shown') + + +class SummaryField(forms.CharField): + def __init__(self, *args, **kwargs): + super(SummaryField, self).__init__(*args, **kwargs) + self.required = False + self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'}) + self.max_length = 300 + self.label = _('update summary:') + self.help_text = _('enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)') + +class AskForm(forms.Form): + title = TitleField() + text = EditorField() + tags = TagNamesField() + wiki = WikiField() + + openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'})) + user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + + + +class AnswerForm(forms.Form): + text = EditorField() + wiki = WikiField() + openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'})) + user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + def __init__(self, question, *args, **kwargs): + super(AnswerForm, self).__init__(*args, **kwargs) + if question.wiki: + self.fields['wiki'].initial = True + +class CloseForm(forms.Form): + reason = forms.ChoiceField(choices=CLOSE_REASONS) + +class RetagQuestionForm(forms.Form): + tags = TagNamesField() + # initialize the default values + def __init__(self, question, *args, **kwargs): + super(RetagQuestionForm, self).__init__(*args, **kwargs) + self.fields['tags'].initial = question.tagnames + +class RevisionForm(forms.Form): + """ + Lists revisions of a Question or Answer + """ + revision = forms.ChoiceField(widget=forms.Select(attrs={'style' : 'width:520px'})) + + def __init__(self, post, latest_revision, *args, **kwargs): + super(RevisionForm, self).__init__(*args, **kwargs) + revisions = post.revisions.all().values_list( + 'revision', 'author__username', 'revised_at', 'summary') + date_format = '%c' + self.fields['revision'].choices = [ + (r[0], u'%s - %s (%s) %s' % (r[0], r[1], r[2].strftime(date_format), r[3])) + for r in revisions] + self.fields['revision'].initial = latest_revision.revision + +class EditQuestionForm(forms.Form): + title = TitleField() + text = EditorField() + tags = TagNamesField() + summary = SummaryField() + + def __init__(self, question, revision, *args, **kwargs): + super(EditQuestionForm, self).__init__(*args, **kwargs) + self.fields['title'].initial = revision.title + self.fields['text'].initial = revision.text + self.fields['tags'].initial = revision.tagnames + # Once wiki mode is enabled, it can't be disabled + if not question.wiki: + self.fields['wiki'] = WikiField() + +class EditAnswerForm(forms.Form): + text = EditorField() + summary = SummaryField() + + def __init__(self, answer, revision, *args, **kwargs): + super(EditAnswerForm, self).__init__(*args, **kwargs) + self.fields['text'].initial = revision.text + +class EditUserForm(forms.Form): + email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + realname = forms.CharField(label=_('Real name'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + website = forms.URLField(label=_('Website'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + city = forms.CharField(label=_('Location'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + birthday = forms.DateField(label=_('Date of birth'), help_text=_('will not be shown, used to calculate age, format: YYYY-MM-DD'), required=False, widget=forms.TextInput(attrs={'size' : 35})) + about = forms.CharField(label=_('Profile'), required=False, widget=forms.Textarea(attrs={'cols' : 60})) + + def __init__(self, user, *args, **kwargs): + super(EditUserForm, self).__init__(*args, **kwargs) + self.fields['email'].initial = user.email + self.fields['realname'].initial = user.real_name + self.fields['website'].initial = user.website + self.fields['city'].initial = user.location + + if user.date_of_birth is not None: + self.fields['birthday'].initial = user.date_of_birth + else: + self.fields['birthday'].initial = '1990-01-01' + self.fields['about'].initial = user.about + self.user = user + + def clean_email(self): + """For security reason one unique email in database""" + if self.user.email != self.cleaned_data['email']: + if 'email' in self.cleaned_data: + try: + user = User.objects.get(email = self.cleaned_data['email']) + except User.DoesNotExist: + return self.cleaned_data['email'] + except User.MultipleObjectsReturned: + raise forms.ValidationError(_('this email has already been registered, please use another one')) + raise forms.ValidationError(_('this email has already been registered, please use another one')) + else: + return self.cleaned_data['email'] diff --git a/forum/management/commands/once_award_badges.py b/forum/management/commands/once_award_badges.py index 7074e3db..447e8971 100644 --- a/forum/management/commands/once_award_badges.py +++ b/forum/management/commands/once_award_badges.py @@ -1,348 +1,348 @@ -#!/usr/bin/env python
-#encoding:utf-8
-#-------------------------------------------------------------------------------
-# Name: Award badges command
-# Purpose: This is a command file croning in background process regularly to
-# query database and award badges for user's special acitivities.
-#
-# Author: Mike, Sailing
-#
-# Created: 18/01/2009
-# Copyright: (c) Mike 2009
-# Licence: GPL V2
-#-------------------------------------------------------------------------------
-
-from django.db import connection
-from django.shortcuts import get_object_or_404
-from django.contrib.contenttypes.models import ContentType
-
-from forum.models import *
-from forum.const import *
-from base_command import BaseCommand
-"""
-(1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0),
-(2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0),
-(3, '优秀回答', 3, '优秀回答', '回答好评10次以上', 1, 0),
-(4, '优秀问题', 3, '优秀问题', '问题好评10次以上', 1, 0),
-(5, '评论家', 3, '评论家', '评论10次以上', 0, 0),
-(6, '流行问题', 3, '流行问题', '问题的浏览量超过1000人次', 1, 0),
-(7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0),
-(8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0),
-(9, '批评家', 3, '批评家', '第一次反对票', 0, 0),
-(10, '小编', 3, '小编', '第一次编辑更新', 0, 0),
-(11, '村长', 3, '村长', '第一次重新标签', 0, 0),
-(12, '学者', 3, '学者', '第一次标记答案', 0, 0),
-(13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0),
-(14, '支持者', 3, '支持者', '第一次赞成票', 0, 0),
-(15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0),
-(16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0),
-(17, '自学成才', 3, '自学成才', '回答自己的问题并且有3个以上赞成票', 1, 0),
-(18, '最有价值回答', 1, '最有价值回答', '回答超过100次赞成票', 1, 0),
-(19, '最有价值问题', 1, '最有价值问题', '问题超过100次赞成票', 1, 0),
-(20, '万人迷', 1, '万人迷', '问题被100人以上收藏', 1, 0),
-(21, '著名问题', 1, '著名问题', '问题的浏览量超过10000人次', 1, 0),
-(22, 'alpha用户', 2, 'alpha用户', '内测期间的活跃用户', 0, 0),
-(23, '极好回答', 2, '极好回答', '回答超过25次赞成票', 1, 0),
-(24, '极好问题', 2, '极好问题', '问题超过25次赞成票', 1, 0),
-(25, '受欢迎问题', 2, '受欢迎问题', '问题被25人以上收藏', 1, 0),
-(26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0),
-(27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0),
-(28, '通才', 2, '通才', '在多个标签领域活跃', 0, 0),
-(29, '专家', 2, '专家', '在一个标签领域活跃出众', 0, 0),
-(30, '老鸟', 2, '老鸟', '活跃超过一年的用户', 0, 0),
-(31, '最受关注问题', 2, '最受关注问题', '问题的浏览量超过2500人次', 1, 0),
-(32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0),
-(33, 'beta用户', 2, 'beta用户', 'beta期间活跃参与', 0, 0),
-(34, '导师', 2, '导师', '被指定为最佳答案并且赞成票40以上', 1, 0),
-(35, '巫师', 2, '巫师', '在提问60天之后回答并且赞成票5次以上', 1, 0),
-(36, '分类专家', 2, '分类专家', '创建的标签被50个以上问题使用', 1, 0);
-
-
-TYPE_ACTIVITY_ASK_QUESTION=1
-TYPE_ACTIVITY_ANSWER=2
-TYPE_ACTIVITY_COMMENT_QUESTION=3
-TYPE_ACTIVITY_COMMENT_ANSWER=4
-TYPE_ACTIVITY_UPDATE_QUESTION=5
-TYPE_ACTIVITY_UPDATE_ANSWER=6
-TYPE_ACTIVITY_PRIZE=7
-TYPE_ACTIVITY_MARK_ANSWER=8
-TYPE_ACTIVITY_VOTE_UP=9
-TYPE_ACTIVITY_VOTE_DOWN=10
-TYPE_ACTIVITY_CANCEL_VOTE=11
-TYPE_ACTIVITY_DELETE_QUESTION=12
-TYPE_ACTIVITY_DELETE_ANSWER=13
-TYPE_ACTIVITY_MARK_OFFENSIVE=14
-TYPE_ACTIVITY_UPDATE_TAGS=15
-TYPE_ACTIVITY_FAVORITE=16
-TYPE_ACTIVITY_USER_FULL_UPDATED = 17
-"""
-
-BADGE_AWARD_TYPE_FIRST = {
- TYPE_ACTIVITY_MARK_OFFENSIVE : 7,
- TYPE_ACTIVITY_CANCEL_VOTE: 8,
- TYPE_ACTIVITY_VOTE_DOWN : 9,
- TYPE_ACTIVITY_UPDATE_QUESTION : 10,
- TYPE_ACTIVITY_UPDATE_ANSWER : 10,
- TYPE_ACTIVITY_UPDATE_TAGS : 11,
- TYPE_ACTIVITY_MARK_ANSWER : 12,
- TYPE_ACTIVITY_VOTE_UP : 14,
- TYPE_ACTIVITY_USER_FULL_UPDATED: 16
-
-}
-
-class Command(BaseCommand):
- def handle_noargs(self, **options):
- try:
- self.alpha_user()
- self.beta_user()
- self.first_type_award()
- self.first_ask_be_voted()
- self.first_answer_be_voted()
- self.first_answer_be_voted_10()
- self.vote_count_300()
- self.edit_count_100()
- self.comment_count_10()
- except Exception, e:
- print e
- finally:
- connection.close()
-
- def alpha_user(self):
- """
- Before Jan 25, 2009(Chinese New Year Eve and enter into Beta for CNProg), every registered user
- will be awarded the "Alpha" badge if he has any activities.
- """
- alpha_end_date = date(2009, 1, 25)
- if date.today() < alpha_end_date:
- badge = get_object_or_404(Badge, id=22)
- for user in User.objects.all():
- award = Award.objects.filter(user=user, badge=badge)
- if award and not badge.multiple:
- continue
- activities = Activity.objects.filter(user=user)
- if len(activities) > 0:
- new_award = Award(user=user, badge=badge)
- new_award.save()
-
- def beta_user(self):
- """
- Before Feb 25, 2009, every registered user
- will be awarded the "Beta" badge if he has any activities.
- """
- beta_end_date = date(2009, 2, 25)
- if date.today() < beta_end_date:
- badge = get_object_or_404(Badge, id=33)
- for user in User.objects.all():
- award = Award.objects.filter(user=user, badge=badge)
- if award and not badge.multiple:
- continue
- activities = Activity.objects.filter(user=user)
- if len(activities) > 0:
- new_award = Award(user=user, badge=badge)
- new_award.save()
-
- def first_type_award(self):
- """
- This will award below badges for users first behaviors:
-
- (7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0),
- (8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0),
- (9, '批评家', 3, '批评家', '第一次反对票', 0, 0),
- (10, '小编', 3, '小编', '第一次编辑更新', 0, 0),
- (11, '村长', 3, '村长', '第一次重新标签', 0, 0),
- (12, '学者', 3, '学者', '第一次标记答案', 0, 0),
- (14, '支持者', 3, '支持者', '第一次赞成票', 0, 0),
- (16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0),
- """
- activity_types = ','.join('%s' % item for item in BADGE_AWARD_TYPE_FIRST.keys())
- # ORDER BY user_id, activity_type
- query = "SELECT id, user_id, activity_type, content_type_id, object_id FROM activity WHERE is_auditted = 0 AND activity_type IN (%s) ORDER BY user_id, activity_type" % activity_types
-
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
- # collect activity_id in current process
- activity_ids = []
- last_user_id = 0
- last_activity_type = 0
- for row in rows:
- activity_ids.append(row[0])
- user_id = row[1]
- activity_type = row[2]
- content_type_id = row[3]
- object_id = row[4]
-
- # if the user and activity are same as the last, continue
- if user_id == last_user_id and activity_type == last_activity_type:
- continue;
-
- user = get_object_or_404(User, id=user_id)
- badge = get_object_or_404(Badge, id=BADGE_AWARD_TYPE_FIRST[activity_type])
- content_type = get_object_or_404(ContentType, id=content_type_id)
-
- count = Award.objects.filter(user=user, badge=badge).count()
- if count and not badge.multiple:
- continue
- else:
- # new award
- award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
- award.save()
-
- # set the current user_id and activity_type to last
- last_user_id = user_id
- last_activity_type = activity_type
-
- # update processed rows to auditted
- self.update_activities_auditted(cursor, activity_ids)
- finally:
- cursor.close()
-
- def first_ask_be_voted(self):
- """
- For user asked question and got first upvote, we award him following badge:
-
- (13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0),
- """
- query = "SELECT act.user_id, q.vote_up_count, act.object_id FROM " \
- "activity act, question q WHERE act.activity_type = %s AND " \
- "act.object_id = q.id AND " \
- "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 13)
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
-
- badge = get_object_or_404(Badge, id=13)
- content_type = ContentType.objects.get_for_model(Question)
- awarded_users = []
- for row in rows:
- user_id = row[0]
- vote_up_count = row[1]
- object_id = row[2]
- if vote_up_count > 0 and user_id not in awarded_users:
- user = get_object_or_404(User, id=user_id)
- award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
- award.save()
- awarded_users.append(user_id)
- finally:
- cursor.close()
-
- def first_answer_be_voted(self):
- """
- When user answerd questions and got first upvote, we award him following badge:
-
- (15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0),
- """
- query = "SELECT act.user_id, a.vote_up_count, act.object_id FROM " \
- "activity act, answer a WHERE act.activity_type = %s AND " \
- "act.object_id = a.id AND " \
- "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 15)
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
-
- awarded_users = []
- badge = get_object_or_404(Badge, id=15)
- content_type = ContentType.objects.get_for_model(Answer)
- for row in rows:
- user_id = row[0]
- vote_up_count = row[1]
- object_id = row[2]
- if vote_up_count > 0 and user_id not in awarded_users:
- user = get_object_or_404(User, id=user_id)
- award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
- award.save()
- awarded_users.append(user_id)
- finally:
- cursor.close()
-
- def first_answer_be_voted_10(self):
- """
- (32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0)
- """
- query = "SELECT act.user_id, act.object_id FROM " \
- "activity act, answer a WHERE act.object_id = a.id AND " \
- "act.activity_type = %s AND " \
- "a.vote_up_count >= 10 AND " \
- "act.user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 32)
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
-
- awarded_users = []
- badge = get_object_or_404(Badge, id=32)
- content_type = ContentType.objects.get_for_model(Answer)
- for row in rows:
- user_id = row[0]
- if user_id not in awarded_users:
- user = get_object_or_404(User, id=user_id)
- object_id = row[1]
- award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
- award.save()
- awarded_users.append(user_id)
- finally:
- cursor.close()
-
- def vote_count_300(self):
- """
- (26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0)
- """
- query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \
- "activity_type = %s OR " \
- "activity_type = %s AND " \
- "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \
- "GROUP BY user_id HAVING vote_count >= 300" % (TYPE_ACTIVITY_VOTE_UP, TYPE_ACTIVITY_VOTE_DOWN, 26)
-
- self.__award_for_count_num(query, 26)
-
- def edit_count_100(self):
- """
- (27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0)
- """
- query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \
- "activity_type = %s OR " \
- "activity_type = %s AND " \
- "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \
- "GROUP BY user_id HAVING vote_count >= 100" % (TYPE_ACTIVITY_UPDATE_QUESTION, TYPE_ACTIVITY_UPDATE_ANSWER, 27)
-
- self.__award_for_count_num(query, 27)
-
- def comment_count_10(self):
- """
- (5, '评论家', 3, '评论家', '评论10次以上', 0, 0),
- """
- query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \
- "activity_type = %s OR " \
- "activity_type = %s AND " \
- "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \
- "GROUP BY user_id HAVING vote_count >= 10" % (TYPE_ACTIVITY_COMMENT_QUESTION, TYPE_ACTIVITY_COMMENT_ANSWER, 5)
- self.__award_for_count_num(query, 5)
-
- def __award_for_count_num(self, query, badge):
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
-
- awarded_users = []
- badge = get_object_or_404(Badge, id=badge)
- for row in rows:
- vote_count = row[0]
- user_id = row[1]
-
- if user_id not in awarded_users:
- user = get_object_or_404(User, id=user_id)
- award = Award(user=user, badge=badge)
- award.save()
- awarded_users.append(user_id)
- finally:
- cursor.close()
-
-def main():
- pass
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python +#encoding:utf-8 +#------------------------------------------------------------------------------- +# Name: Award badges command +# Purpose: This is a command file croning in background process regularly to +# query database and award badges for user's special acitivities. +# +# Author: Mike, Sailing +# +# Created: 18/01/2009 +# Copyright: (c) Mike 2009 +# Licence: GPL V2 +#------------------------------------------------------------------------------- + +from django.db import connection +from django.shortcuts import get_object_or_404 +from django.contrib.contenttypes.models import ContentType + +from forum.models import * +from forum.const import * +from base_command import BaseCommand +""" +(1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0), +(2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0), +(3, '优秀回答', 3, '优秀回答', '回答好评10次以上', 1, 0), +(4, '优秀问题', 3, '优秀问题', '问题好评10次以上', 1, 0), +(5, '评论家', 3, '评论家', '评论10次以上', 0, 0), +(6, '流行问题', 3, '流行问题', '问题的浏览量超过1000人次', 1, 0), +(7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0), +(8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0), +(9, '批评家', 3, '批评家', '第一次反对票', 0, 0), +(10, '小编', 3, '小编', '第一次编辑更新', 0, 0), +(11, '村长', 3, '村长', '第一次重新标签', 0, 0), +(12, '学者', 3, '学者', '第一次标记答案', 0, 0), +(13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0), +(14, '支持者', 3, '支持者', '第一次赞成票', 0, 0), +(15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0), +(16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0), +(17, '自学成才', 3, '自学成才', '回答自己的问题并且有3个以上赞成票', 1, 0), +(18, '最有价值回答', 1, '最有价值回答', '回答超过100次赞成票', 1, 0), +(19, '最有价值问题', 1, '最有价值问题', '问题超过100次赞成票', 1, 0), +(20, '万人迷', 1, '万人迷', '问题被100人以上收藏', 1, 0), +(21, '著名问题', 1, '著名问题', '问题的浏览量超过10000人次', 1, 0), +(22, 'alpha用户', 2, 'alpha用户', '内测期间的活跃用户', 0, 0), +(23, '极好回答', 2, '极好回答', '回答超过25次赞成票', 1, 0), +(24, '极好问题', 2, '极好问题', '问题超过25次赞成票', 1, 0), +(25, '受欢迎问题', 2, '受欢迎问题', '问题被25人以上收藏', 1, 0), +(26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0), +(27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0), +(28, '通才', 2, '通才', '在多个标签领域活跃', 0, 0), +(29, '专家', 2, '专家', '在一个标签领域活跃出众', 0, 0), +(30, '老鸟', 2, '老鸟', '活跃超过一年的用户', 0, 0), +(31, '最受关注问题', 2, '最受关注问题', '问题的浏览量超过2500人次', 1, 0), +(32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0), +(33, 'beta用户', 2, 'beta用户', 'beta期间活跃参与', 0, 0), +(34, '导师', 2, '导师', '被指定为最佳答案并且赞成票40以上', 1, 0), +(35, '巫师', 2, '巫师', '在提问60天之后回答并且赞成票5次以上', 1, 0), +(36, '分类专家', 2, '分类专家', '创建的标签被50个以上问题使用', 1, 0); + + +TYPE_ACTIVITY_ASK_QUESTION=1 +TYPE_ACTIVITY_ANSWER=2 +TYPE_ACTIVITY_COMMENT_QUESTION=3 +TYPE_ACTIVITY_COMMENT_ANSWER=4 +TYPE_ACTIVITY_UPDATE_QUESTION=5 +TYPE_ACTIVITY_UPDATE_ANSWER=6 +TYPE_ACTIVITY_PRIZE=7 +TYPE_ACTIVITY_MARK_ANSWER=8 +TYPE_ACTIVITY_VOTE_UP=9 +TYPE_ACTIVITY_VOTE_DOWN=10 +TYPE_ACTIVITY_CANCEL_VOTE=11 +TYPE_ACTIVITY_DELETE_QUESTION=12 +TYPE_ACTIVITY_DELETE_ANSWER=13 +TYPE_ACTIVITY_MARK_OFFENSIVE=14 +TYPE_ACTIVITY_UPDATE_TAGS=15 +TYPE_ACTIVITY_FAVORITE=16 +TYPE_ACTIVITY_USER_FULL_UPDATED = 17 +""" + +BADGE_AWARD_TYPE_FIRST = { + TYPE_ACTIVITY_MARK_OFFENSIVE : 7, + TYPE_ACTIVITY_CANCEL_VOTE: 8, + TYPE_ACTIVITY_VOTE_DOWN : 9, + TYPE_ACTIVITY_UPDATE_QUESTION : 10, + TYPE_ACTIVITY_UPDATE_ANSWER : 10, + TYPE_ACTIVITY_UPDATE_TAGS : 11, + TYPE_ACTIVITY_MARK_ANSWER : 12, + TYPE_ACTIVITY_VOTE_UP : 14, + TYPE_ACTIVITY_USER_FULL_UPDATED: 16 + +} + +class Command(BaseCommand): + def handle_noargs(self, **options): + try: + self.alpha_user() + self.beta_user() + self.first_type_award() + self.first_ask_be_voted() + self.first_answer_be_voted() + self.first_answer_be_voted_10() + self.vote_count_300() + self.edit_count_100() + self.comment_count_10() + except Exception, e: + print e + finally: + connection.close() + + def alpha_user(self): + """ + Before Jan 25, 2009(Chinese New Year Eve and enter into Beta for CNProg), every registered user + will be awarded the "Alpha" badge if he has any activities. + """ + alpha_end_date = date(2009, 1, 25) + if date.today() < alpha_end_date: + badge = get_object_or_404(Badge, id=22) + for user in User.objects.all(): + award = Award.objects.filter(user=user, badge=badge) + if award and not badge.multiple: + continue + activities = Activity.objects.filter(user=user) + if len(activities) > 0: + new_award = Award(user=user, badge=badge) + new_award.save() + + def beta_user(self): + """ + Before Feb 25, 2009, every registered user + will be awarded the "Beta" badge if he has any activities. + """ + beta_end_date = date(2009, 2, 25) + if date.today() < beta_end_date: + badge = get_object_or_404(Badge, id=33) + for user in User.objects.all(): + award = Award.objects.filter(user=user, badge=badge) + if award and not badge.multiple: + continue + activities = Activity.objects.filter(user=user) + if len(activities) > 0: + new_award = Award(user=user, badge=badge) + new_award.save() + + def first_type_award(self): + """ + This will award below badges for users first behaviors: + + (7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0), + (8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0), + (9, '批评家', 3, '批评家', '第一次反对票', 0, 0), + (10, '小编', 3, '小编', '第一次编辑更新', 0, 0), + (11, '村长', 3, '村长', '第一次重新标签', 0, 0), + (12, '学者', 3, '学者', '第一次标记答案', 0, 0), + (14, '支持者', 3, '支持者', '第一次赞成票', 0, 0), + (16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0), + """ + activity_types = ','.join('%s' % item for item in BADGE_AWARD_TYPE_FIRST.keys()) + # ORDER BY user_id, activity_type + query = "SELECT id, user_id, activity_type, content_type_id, object_id FROM activity WHERE is_auditted = 0 AND activity_type IN (%s) ORDER BY user_id, activity_type" % activity_types + + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + # collect activity_id in current process + activity_ids = [] + last_user_id = 0 + last_activity_type = 0 + for row in rows: + activity_ids.append(row[0]) + user_id = row[1] + activity_type = row[2] + content_type_id = row[3] + object_id = row[4] + + # if the user and activity are same as the last, continue + if user_id == last_user_id and activity_type == last_activity_type: + continue; + + user = get_object_or_404(User, id=user_id) + badge = get_object_or_404(Badge, id=BADGE_AWARD_TYPE_FIRST[activity_type]) + content_type = get_object_or_404(ContentType, id=content_type_id) + + count = Award.objects.filter(user=user, badge=badge).count() + if count and not badge.multiple: + continue + else: + # new award + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + + # set the current user_id and activity_type to last + last_user_id = user_id + last_activity_type = activity_type + + # update processed rows to auditted + self.update_activities_auditted(cursor, activity_ids) + finally: + cursor.close() + + def first_ask_be_voted(self): + """ + For user asked question and got first upvote, we award him following badge: + + (13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0), + """ + query = "SELECT act.user_id, q.vote_up_count, act.object_id FROM " \ + "activity act, question q WHERE act.activity_type = %s AND " \ + "act.object_id = q.id AND " \ + "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 13) + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + badge = get_object_or_404(Badge, id=13) + content_type = ContentType.objects.get_for_model(Question) + awarded_users = [] + for row in rows: + user_id = row[0] + vote_up_count = row[1] + object_id = row[2] + if vote_up_count > 0 and user_id not in awarded_users: + user = get_object_or_404(User, id=user_id) + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + awarded_users.append(user_id) + finally: + cursor.close() + + def first_answer_be_voted(self): + """ + When user answerd questions and got first upvote, we award him following badge: + + (15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0), + """ + query = "SELECT act.user_id, a.vote_up_count, act.object_id FROM " \ + "activity act, answer a WHERE act.activity_type = %s AND " \ + "act.object_id = a.id AND " \ + "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 15) + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + awarded_users = [] + badge = get_object_or_404(Badge, id=15) + content_type = ContentType.objects.get_for_model(Answer) + for row in rows: + user_id = row[0] + vote_up_count = row[1] + object_id = row[2] + if vote_up_count > 0 and user_id not in awarded_users: + user = get_object_or_404(User, id=user_id) + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + awarded_users.append(user_id) + finally: + cursor.close() + + def first_answer_be_voted_10(self): + """ + (32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0) + """ + query = "SELECT act.user_id, act.object_id FROM " \ + "activity act, answer a WHERE act.object_id = a.id AND " \ + "act.activity_type = %s AND " \ + "a.vote_up_count >= 10 AND " \ + "act.user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 32) + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + awarded_users = [] + badge = get_object_or_404(Badge, id=32) + content_type = ContentType.objects.get_for_model(Answer) + for row in rows: + user_id = row[0] + if user_id not in awarded_users: + user = get_object_or_404(User, id=user_id) + object_id = row[1] + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) + award.save() + awarded_users.append(user_id) + finally: + cursor.close() + + def vote_count_300(self): + """ + (26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0) + """ + query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \ + "activity_type = %s OR " \ + "activity_type = %s AND " \ + "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \ + "GROUP BY user_id HAVING vote_count >= 300" % (TYPE_ACTIVITY_VOTE_UP, TYPE_ACTIVITY_VOTE_DOWN, 26) + + self.__award_for_count_num(query, 26) + + def edit_count_100(self): + """ + (27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0) + """ + query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \ + "activity_type = %s OR " \ + "activity_type = %s AND " \ + "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \ + "GROUP BY user_id HAVING vote_count >= 100" % (TYPE_ACTIVITY_UPDATE_QUESTION, TYPE_ACTIVITY_UPDATE_ANSWER, 27) + + self.__award_for_count_num(query, 27) + + def comment_count_10(self): + """ + (5, '评论家', 3, '评论家', '评论10次以上', 0, 0), + """ + query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \ + "activity_type = %s OR " \ + "activity_type = %s AND " \ + "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \ + "GROUP BY user_id HAVING vote_count >= 10" % (TYPE_ACTIVITY_COMMENT_QUESTION, TYPE_ACTIVITY_COMMENT_ANSWER, 5) + self.__award_for_count_num(query, 5) + + def __award_for_count_num(self, query, badge): + cursor = connection.cursor() + try: + cursor.execute(query) + rows = cursor.fetchall() + + awarded_users = [] + badge = get_object_or_404(Badge, id=badge) + for row in rows: + vote_count = row[0] + user_id = row[1] + + if user_id not in awarded_users: + user = get_object_or_404(User, id=user_id) + award = Award(user=user, badge=badge) + award.save() + awarded_users.append(user_id) + finally: + cursor.close() + +def main(): + pass + +if __name__ == '__main__': + main() diff --git a/forum/managers.py b/forum/managers.py index 0f22c59c..2e3e4186 100644 --- a/forum/managers.py +++ b/forum/managers.py @@ -1,259 +1,259 @@ -import datetime
-import logging
-from django.contrib.auth.models import User, UserManager
-from django.db import connection, models, transaction
-from django.db.models import Q
-from forum.models import *
-from urllib import quote, unquote
-
-class QuestionManager(models.Manager):
- def get_translation_questions(self, orderby, page_size):
- questions = self.filter(deleted=False, author__id__in=[28,29]).order_by(orderby)[:page_size]
- return questions
-
- def get_questions_by_pagesize(self, orderby, page_size):
- questions = self.filter(deleted=False).order_by(orderby)[:page_size]
- return questions
-
- def get_questions_by_tag(self, tagname, orderby):
- questions = self.filter(deleted=False, tags__name = unquote(tagname)).order_by(orderby)
- return questions
-
- def get_unanswered_questions(self, orderby):
- questions = self.filter(deleted=False, answer_count=0).order_by(orderby)
- return questions
-
- def get_questions(self, orderby):
- questions = self.filter(deleted=False).order_by(orderby)
- return questions
-
- def update_tags(self, question, tagnames, user):
- """
- Updates Tag associations for a question to match the given
- tagname string.
-
- Returns ``True`` if tag usage counts were updated as a result,
- ``False`` otherwise.
- """
- from forum.models import Tag
- current_tags = list(question.tags.all())
- current_tagnames = set(t.name for t in current_tags)
- updated_tagnames = set(t for t in tagnames.split(' ') if t)
- modified_tags = []
-
- removed_tags = [t for t in current_tags
- if t.name not in updated_tagnames]
- if removed_tags:
- modified_tags.extend(removed_tags)
- question.tags.remove(*removed_tags)
-
- added_tagnames = updated_tagnames - current_tagnames
- if added_tagnames:
- added_tags = Tag.objects.get_or_create_multiple(added_tagnames,
- user)
- modified_tags.extend(added_tags)
- question.tags.add(*added_tags)
-
- if modified_tags:
- Tag.objects.update_use_counts(modified_tags)
- return True
-
- return False
-
- def update_answer_count(self, question):
- """
- Executes an UPDATE query to update denormalised data with the
- number of answers the given question has.
- """
-
- # for some reasons, this Answer class failed to be imported,
- # although we have imported all classes from models on top.
- from forum.models import Answer
- self.filter(id=question.id).update(
- answer_count=Answer.objects.get_answers_from_question(question).count())
-
- def update_view_count(self, question):
- """
- update counter+1 when user browse question page
- """
- self.filter(id=question.id).update(view_count = question.view_count + 1)
-
- def update_favorite_count(self, question):
- """
- update favourite_count for given question
- """
- from forum.models import FavoriteQuestion
- self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count())
-
- def get_similar_questions(self, question):
- """
- Get 10 similar questions for given one.
- This will search the same tag list for give question(by exactly same string) first.
- Questions with the individual tags will be added to list if above questions are not full.
- """
- #print datetime.datetime.now()
- from forum.models import Question
- questions = list(Question.objects.filter(tagnames = question.tagnames).all())
-
- tags_list = question.tags.all()
- for tag in tags_list:
- extend_questions = Question.objects.filter(tags__id = tag.id)[:50]
- for item in extend_questions:
- if item not in questions and len(questions) < 10:
- questions.append(item)
-
- #print datetime.datetime.now()
- return questions
-
-class TagManager(models.Manager):
- UPDATE_USED_COUNTS_QUERY = (
- 'UPDATE tag '
- 'SET used_count = ('
- 'SELECT COUNT(*) FROM question_tags '
- 'WHERE tag_id = tag.id'
- ') '
- 'WHERE id IN (%s)')
-
- def get_valid_tags(self, page_size):
- from forum.models import Tag
- tags = Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-id")[:page_size]
- return tags
-
- def get_or_create_multiple(self, names, user):
- """
- Fetches a list of Tags with the given names, creating any Tags
- which don't exist when necesssary.
- """
- tags = list(self.filter(name__in=names))
- #Set all these tag visible
- for tag in tags:
- if tag.deleted:
- tag.deleted = False
- tag.deleted_by = None
- tag.deleted_at = None
- tag.save()
-
- if len(tags) < len(names):
- existing_names = set(tag.name for tag in tags)
- new_names = [name for name in names if name not in existing_names]
- tags.extend([self.create(name=name, created_by=user)
- for name in new_names if self.filter(name=name).count() == 0 and len(name.strip()) > 0])
-
- return tags
-
- def update_use_counts(self, tags):
- """Updates the given Tags with their current use counts."""
- if not tags:
- return
- cursor = connection.cursor()
- query = self.UPDATE_USED_COUNTS_QUERY % ','.join(['%s'] * len(tags))
- cursor.execute(query, [tag.id for tag in tags])
- transaction.commit_unless_managed()
-
- def get_tags_by_questions(self, questions):
- question_ids = []
- for question in questions:
- question_ids.append(question.id)
-
- question_ids_str = ','.join([str(id) for id in question_ids])
- related_tags = self.extra(
- tables=['tag', 'question_tags'],
- where=["tag.id = question_tags.tag_id AND question_tags.question_id IN (" + question_ids_str + ")"]
- ).distinct()
-
- return related_tags
-
-class AnswerManager(models.Manager):
- GET_ANSWERS_FROM_USER_QUESTIONS = u'SELECT answer.* FROM answer INNER JOIN question ON answer.question_id = question.id WHERE question.author_id =%s AND answer.author_id <> %s'
- def get_answers_from_question(self, question, user=None):
- """
- Retrieves visibile answers for the given question. Delete answers
- are only visibile to the person who deleted them.
- """
-
- if user is None or not user.is_authenticated():
- return self.filter(question=question, deleted=False)
- else:
- return self.filter(Q(question=question),
- Q(deleted=False) | Q(deleted_by=user))
-
- def get_answers_from_questions(self, user_id):
- """
- Retrieves visibile answers for the given question. Which are not included own answers
- """
- cursor = connection.cursor()
- cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id])
- return cursor.fetchall()
-
-class VoteManager(models.Manager):
- COUNT_UP_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = 1"
- COUNT_DOWN_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = -1"
- COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = DATE(NOW())"
- def get_up_vote_count_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_UP_VOTE_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
- else:
- return 0
-
- def get_down_vote_count_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_DOWN_VOTE_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
- else:
- return 0
-
- def get_votes_count_today_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-
-class FlaggedItemManager(models.Manager):
- COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = DATE(NOW())"
- def get_flagged_items_count_today(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-
-class ReputeManager(models.Manager):
- COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = DATE(NOW())"
- def get_reputation_by_upvoted_today(self, user):
- """
- For one user in one day, he can only earn rep till certain score (ep. +200)
- by upvoted(also substracted from upvoted canceled). This is because we need
- to prohibit gaming system by upvoting/cancel again and again.
- """
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-class AwardManager(models.Manager):
- def get_recent_awards(self):
- awards = super(AwardManager, self).extra(
- select={'badge_id': 'badge.id', 'badge_name':'badge.name',
- 'badge_description': 'badge.description', 'badge_type': 'badge.type',
- 'user_id': 'auth_user.id', 'user_name': 'auth_user.username'
- },
- tables=['award', 'badge', 'auth_user'],
- order_by=['-awarded_at'],
- where=['auth_user.id=award.user_id AND badge_id=badge.id'],
- ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name')
- return awards
+import datetime +import logging +from django.contrib.auth.models import User, UserManager +from django.db import connection, models, transaction +from django.db.models import Q +from forum.models import * +from urllib import quote, unquote + +class QuestionManager(models.Manager): + def get_translation_questions(self, orderby, page_size): + questions = self.filter(deleted=False, author__id__in=[28,29]).order_by(orderby)[:page_size] + return questions + + def get_questions_by_pagesize(self, orderby, page_size): + questions = self.filter(deleted=False).order_by(orderby)[:page_size] + return questions + + def get_questions_by_tag(self, tagname, orderby): + questions = self.filter(deleted=False, tags__name = unquote(tagname)).order_by(orderby) + return questions + + def get_unanswered_questions(self, orderby): + questions = self.filter(deleted=False, answer_count=0).order_by(orderby) + return questions + + def get_questions(self, orderby): + questions = self.filter(deleted=False).order_by(orderby) + return questions + + def update_tags(self, question, tagnames, user): + """ + Updates Tag associations for a question to match the given + tagname string. + + Returns ``True`` if tag usage counts were updated as a result, + ``False`` otherwise. + """ + from forum.models import Tag + current_tags = list(question.tags.all()) + current_tagnames = set(t.name for t in current_tags) + updated_tagnames = set(t for t in tagnames.split(' ') if t) + modified_tags = [] + + removed_tags = [t for t in current_tags + if t.name not in updated_tagnames] + if removed_tags: + modified_tags.extend(removed_tags) + question.tags.remove(*removed_tags) + + added_tagnames = updated_tagnames - current_tagnames + if added_tagnames: + added_tags = Tag.objects.get_or_create_multiple(added_tagnames, + user) + modified_tags.extend(added_tags) + question.tags.add(*added_tags) + + if modified_tags: + Tag.objects.update_use_counts(modified_tags) + return True + + return False + + def update_answer_count(self, question): + """ + Executes an UPDATE query to update denormalised data with the + number of answers the given question has. + """ + + # for some reasons, this Answer class failed to be imported, + # although we have imported all classes from models on top. + from forum.models import Answer + self.filter(id=question.id).update( + answer_count=Answer.objects.get_answers_from_question(question).count()) + + def update_view_count(self, question): + """ + update counter+1 when user browse question page + """ + self.filter(id=question.id).update(view_count = question.view_count + 1) + + def update_favorite_count(self, question): + """ + update favourite_count for given question + """ + from forum.models import FavoriteQuestion + self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count()) + + def get_similar_questions(self, question): + """ + Get 10 similar questions for given one. + This will search the same tag list for give question(by exactly same string) first. + Questions with the individual tags will be added to list if above questions are not full. + """ + #print datetime.datetime.now() + from forum.models import Question + questions = list(Question.objects.filter(tagnames = question.tagnames).all()) + + tags_list = question.tags.all() + for tag in tags_list: + extend_questions = Question.objects.filter(tags__id = tag.id)[:50] + for item in extend_questions: + if item not in questions and len(questions) < 10: + questions.append(item) + + #print datetime.datetime.now() + return questions + +class TagManager(models.Manager): + UPDATE_USED_COUNTS_QUERY = ( + 'UPDATE tag ' + 'SET used_count = (' + 'SELECT COUNT(*) FROM question_tags ' + 'WHERE tag_id = tag.id' + ') ' + 'WHERE id IN (%s)') + + def get_valid_tags(self, page_size): + from forum.models import Tag + tags = Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-id")[:page_size] + return tags + + def get_or_create_multiple(self, names, user): + """ + Fetches a list of Tags with the given names, creating any Tags + which don't exist when necesssary. + """ + tags = list(self.filter(name__in=names)) + #Set all these tag visible + for tag in tags: + if tag.deleted: + tag.deleted = False + tag.deleted_by = None + tag.deleted_at = None + tag.save() + + if len(tags) < len(names): + existing_names = set(tag.name for tag in tags) + new_names = [name for name in names if name not in existing_names] + tags.extend([self.create(name=name, created_by=user) + for name in new_names if self.filter(name=name).count() == 0 and len(name.strip()) > 0]) + + return tags + + def update_use_counts(self, tags): + """Updates the given Tags with their current use counts.""" + if not tags: + return + cursor = connection.cursor() + query = self.UPDATE_USED_COUNTS_QUERY % ','.join(['%s'] * len(tags)) + cursor.execute(query, [tag.id for tag in tags]) + transaction.commit_unless_managed() + + def get_tags_by_questions(self, questions): + question_ids = [] + for question in questions: + question_ids.append(question.id) + + question_ids_str = ','.join([str(id) for id in question_ids]) + related_tags = self.extra( + tables=['tag', 'question_tags'], + where=["tag.id = question_tags.tag_id AND question_tags.question_id IN (" + question_ids_str + ")"] + ).distinct() + + return related_tags + +class AnswerManager(models.Manager): + GET_ANSWERS_FROM_USER_QUESTIONS = u'SELECT answer.* FROM answer INNER JOIN question ON answer.question_id = question.id WHERE question.author_id =%s AND answer.author_id <> %s' + def get_answers_from_question(self, question, user=None): + """ + Retrieves visibile answers for the given question. Delete answers + are only visibile to the person who deleted them. + """ + + if user is None or not user.is_authenticated(): + return self.filter(question=question, deleted=False) + else: + return self.filter(Q(question=question), + Q(deleted=False) | Q(deleted_by=user)) + + def get_answers_from_questions(self, user_id): + """ + Retrieves visibile answers for the given question. Which are not included own answers + """ + cursor = connection.cursor() + cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id]) + return cursor.fetchall() + +class VoteManager(models.Manager): + COUNT_UP_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = 1" + COUNT_DOWN_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = -1" + COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = DATE(NOW())" + def get_up_vote_count_from_user(self, user): + if user is not None: + cursor = connection.cursor() + cursor.execute(self.COUNT_UP_VOTE_BY_USER, [user.id]) + row = cursor.fetchone() + return row[0] + else: + return 0 + + def get_down_vote_count_from_user(self, user): + if user is not None: + cursor = connection.cursor() + cursor.execute(self.COUNT_DOWN_VOTE_BY_USER, [user.id]) + row = cursor.fetchone() + return row[0] + else: + return 0 + + def get_votes_count_today_from_user(self, user): + if user is not None: + cursor = connection.cursor() + cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id]) + row = cursor.fetchone() + return row[0] + + else: + return 0 + +class FlaggedItemManager(models.Manager): + COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = DATE(NOW())" + def get_flagged_items_count_today(self, user): + if user is not None: + cursor = connection.cursor() + cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id]) + row = cursor.fetchone() + return row[0] + + else: + return 0 + +class ReputeManager(models.Manager): + COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = DATE(NOW())" + def get_reputation_by_upvoted_today(self, user): + """ + For one user in one day, he can only earn rep till certain score (ep. +200) + by upvoted(also substracted from upvoted canceled). This is because we need + to prohibit gaming system by upvoting/cancel again and again. + """ + if user is not None: + cursor = connection.cursor() + cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id]) + row = cursor.fetchone() + return row[0] + + else: + return 0 +class AwardManager(models.Manager): + def get_recent_awards(self): + awards = super(AwardManager, self).extra( + select={'badge_id': 'badge.id', 'badge_name':'badge.name', + 'badge_description': 'badge.description', 'badge_type': 'badge.type', + 'user_id': 'auth_user.id', 'user_name': 'auth_user.username' + }, + tables=['award', 'badge', 'auth_user'], + order_by=['-awarded_at'], + where=['auth_user.id=award.user_id AND badge_id=badge.id'], + ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name') + return awards diff --git a/forum/models.py b/forum/models.py index 570db274..b966ccb0 100644 --- a/forum/models.py +++ b/forum/models.py @@ -1,654 +1,654 @@ -# encoding:utf-8
-import datetime
-import hashlib
-from urllib import quote_plus, urlencode
-from django.db import models
-from django.utils.html import strip_tags
-from django.core.urlresolvers import reverse
-from django.contrib.auth.models import User
-from django.contrib.contenttypes import generic
-from django.contrib.contenttypes.models import ContentType
-from django.template.defaultfilters import slugify
-from django.db.models.signals import post_delete, post_save, pre_save
-from django.utils.translation import ugettext as _
-import django.dispatch
-
-from forum.managers import *
-from const import *
-
-class Tag(models.Model):
- name = models.CharField(max_length=255, unique=True)
- created_by = models.ForeignKey(User, related_name='created_tags')
- deleted = models.BooleanField(default=False)
- deleted_at = models.DateTimeField(null=True, blank=True)
- deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_tags')
- # Denormalised data
- used_count = models.PositiveIntegerField(default=0)
-
- objects = TagManager()
-
- class Meta:
- db_table = u'tag'
- ordering = ('-used_count', 'name')
-
- def __unicode__(self):
- return self.name
-
-class Comment(models.Model):
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- user = models.ForeignKey(User, related_name='comments')
- comment = models.CharField(max_length=300)
- added_at = models.DateTimeField(default=datetime.datetime.now)
-
- class Meta:
- ordering = ('-added_at',)
- db_table = u'comment'
- def __unicode__(self):
- return self.comment
-
-class Vote(models.Model):
- VOTE_UP = +1
- VOTE_DOWN = -1
- VOTE_CHOICES = (
- (VOTE_UP, u'Up'),
- (VOTE_DOWN, u'Down'),
- )
-
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- user = models.ForeignKey(User, related_name='votes')
- vote = models.SmallIntegerField(choices=VOTE_CHOICES)
- voted_at = models.DateTimeField(default=datetime.datetime.now)
-
- objects = VoteManager()
-
- class Meta:
- unique_together = ('content_type', 'object_id', 'user')
- db_table = u'vote'
- def __unicode__(self):
- return '[%s] voted at %s: %s' %(self.user, self.voted_at, self.vote)
-
- def is_upvote(self):
- return self.vote == self.VOTE_UP
-
- def is_downvote(self):
- return self.vote == self.VOTE_DOWN
-
-class FlaggedItem(models.Model):
- """A flag on a Question or Answer indicating offensive content."""
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- user = models.ForeignKey(User, related_name='flagged_items')
- flagged_at = models.DateTimeField(default=datetime.datetime.now)
-
- objects = FlaggedItemManager()
-
- class Meta:
- unique_together = ('content_type', 'object_id', 'user')
- db_table = u'flagged_item'
- def __unicode__(self):
- return '[%s] flagged at %s' %(self.user, self.flagged_at)
-
-class Question(models.Model):
- title = models.CharField(max_length=300)
- author = models.ForeignKey(User, related_name='questions')
- added_at = models.DateTimeField(default=datetime.datetime.now)
- tags = models.ManyToManyField(Tag, related_name='questions')
- # Status
- wiki = models.BooleanField(default=False)
- wikified_at = models.DateTimeField(null=True, blank=True)
- answer_accepted = models.BooleanField(default=False)
- closed = models.BooleanField(default=False)
- closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions')
- closed_at = models.DateTimeField(null=True, blank=True)
- close_reason = models.SmallIntegerField(choices=CLOSE_REASONS, null=True, blank=True)
- deleted = models.BooleanField(default=False)
- deleted_at = models.DateTimeField(null=True, blank=True)
- deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_questions')
- locked = models.BooleanField(default=False)
- locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_questions')
- locked_at = models.DateTimeField(null=True, blank=True)
- # Denormalised data
- score = models.IntegerField(default=0)
- vote_up_count = models.IntegerField(default=0)
- vote_down_count = models.IntegerField(default=0)
- answer_count = models.PositiveIntegerField(default=0)
- comment_count = models.PositiveIntegerField(default=0)
- view_count = models.PositiveIntegerField(default=0)
- offensive_flag_count = models.SmallIntegerField(default=0)
- favourite_count = models.PositiveIntegerField(default=0)
- last_edited_at = models.DateTimeField(null=True, blank=True)
- last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_questions')
- last_activity_at = models.DateTimeField(default=datetime.datetime.now)
- last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions')
- tagnames = models.CharField(max_length=125)
- summary = models.CharField(max_length=180)
- html = models.TextField()
- comments = generic.GenericRelation(Comment)
- votes = generic.GenericRelation(Vote)
- flagged_items = generic.GenericRelation(FlaggedItem)
-
- objects = QuestionManager()
-
- def save(self, **kwargs):
- """
- Overridden to manually manage addition of tags when the object
- is first saved.
-
- This is required as we're using ``tagnames`` as the sole means of
- adding and editing tags.
- """
- initial_addition = (self.id is None)
- super(Question, self).save(**kwargs)
- if initial_addition:
- tags = Tag.objects.get_or_create_multiple(self.tagname_list(),
- self.author)
- self.tags.add(*tags)
- Tag.objects.update_use_counts(tags)
-
- def tagname_list(self):
- """Creates a list of Tag names from the ``tagnames`` attribute."""
- return [name for name in self.tagnames.split(u' ')]
-
- def get_absolute_url(self):
- return '%s%s' % (reverse('question', args=[self.id]), self.title)
-
- def has_favorite_by_user(self, user):
- if not user.is_authenticated():
- return False
- return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0
-
- def get_answer_count_by_user(self, user_id):
- query_set = Answer.objects.filter(author__id=user_id)
- return query_set.filter(question=self).count()
-
- def get_question_title(self):
- if self.closed:
- attr = CONST['closed']
- elif self.deleted:
- attr = CONST['deleted']
- else:
- attr = None
- return u'%s %s' % (self.title, attr) if attr is not None else self.title
-
- def get_revision_url(self):
- return reverse('question_revisions', args=[self.id])
-
- def get_latest_revision(self):
- return self.revisions.all()[0]
-
- def __unicode__(self):
- return self.title
-
- class Meta:
- db_table = u'question'
-
-class QuestionRevision(models.Model):
- """A revision of a Question."""
- question = models.ForeignKey(Question, related_name='revisions')
- revision = models.PositiveIntegerField(blank=True)
- title = models.CharField(max_length=300)
- author = models.ForeignKey(User, related_name='question_revisions')
- revised_at = models.DateTimeField()
- tagnames = models.CharField(max_length=125)
- summary = models.CharField(max_length=300, blank=True)
- text = models.TextField()
-
- class Meta:
- db_table = u'question_revision'
- ordering = ('-revision',)
-
- def get_question_title(self):
- return self.question.title
-
- def get_absolute_url(self):
- return '/questions/%s/revisions' % (self.question.id)
-
- def save(self, **kwargs):
- """Looks up the next available revision number."""
- if not self.revision:
- self.revision = QuestionRevision.objects.filter(
- question=self.question).values_list('revision',
- flat=True)[0] + 1
- super(QuestionRevision, self).save(**kwargs)
-
- def __unicode__(self):
- return u'revision %s of %s' % (self.revision, self.title)
-
-class Answer(models.Model):
- question = models.ForeignKey(Question, related_name='answers')
- author = models.ForeignKey(User, related_name='answers')
- added_at = models.DateTimeField(default=datetime.datetime.now)
- # Status
- wiki = models.BooleanField(default=False)
- wikified_at = models.DateTimeField(null=True, blank=True)
- accepted = models.BooleanField(default=False)
- accepted_at = models.DateTimeField(null=True, blank=True)
- deleted = models.BooleanField(default=False)
- deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_answers')
- locked = models.BooleanField(default=False)
- locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_answers')
- locked_at = models.DateTimeField(null=True, blank=True)
- # Denormalised data
- score = models.IntegerField(default=0)
- vote_up_count = models.IntegerField(default=0)
- vote_down_count = models.IntegerField(default=0)
- comment_count = models.PositiveIntegerField(default=0)
- offensive_flag_count = models.SmallIntegerField(default=0)
- last_edited_at = models.DateTimeField(null=True, blank=True)
- last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_answers')
- html = models.TextField()
- comments = generic.GenericRelation(Comment)
- votes = generic.GenericRelation(Vote)
- flagged_items = generic.GenericRelation(FlaggedItem)
-
- objects = AnswerManager()
-
- def get_user_vote(self, user):
- votes = self.votes.filter(user=user)
- if votes.count() > 0:
- return votes[0]
- else:
- return None
-
- def get_latest_revision(self):
- return self.revisions.all()[0]
-
- def get_question_title(self):
- return self.question.title
-
- def get_absolute_url(self):
- return '%s%s#%s' % (reverse('question', args=[self.question.id]), self.question.title, self.id)
-
- class Meta:
- db_table = u'answer'
-
- def __unicode__(self):
- return self.html
-
-class AnswerRevision(models.Model):
- """A revision of an Answer."""
- answer = models.ForeignKey(Answer, related_name='revisions')
- revision = models.PositiveIntegerField()
- author = models.ForeignKey(User, related_name='answer_revisions')
- revised_at = models.DateTimeField()
- summary = models.CharField(max_length=300, blank=True)
- text = models.TextField()
-
- def get_absolute_url(self):
- return '/answers/%s/revisions' % (self.answer.id)
-
- def get_question_title(self):
- return self.answer.question.title
-
- class Meta:
- db_table = u'answer_revision'
- ordering = ('-revision',)
-
- def save(self, **kwargs):
- """Looks up the next available revision number if not set."""
- if not self.revision:
- self.revision = AnswerRevision.objects.filter(
- answer=self.answer).values_list('revision',
- flat=True)[0] + 1
- super(AnswerRevision, self).save(**kwargs)
-
-class FavoriteQuestion(models.Model):
- """A favorite Question of a User."""
- question = models.ForeignKey(Question)
- user = models.ForeignKey(User, related_name='user_favorite_questions')
- added_at = models.DateTimeField(default=datetime.datetime.now)
- class Meta:
- db_table = u'favorite_question'
- def __unicode__(self):
- return '[%s] favorited at %s' %(self.user, self.added_at)
-
-class Badge(models.Model):
- """Awarded for notable actions performed on the site by Users."""
- GOLD = 1
- SILVER = 2
- BRONZE = 3
- TYPE_CHOICES = (
- (GOLD, _('gold')),
- (SILVER, _('silver')),
- (BRONZE, _('bronze')),
- )
-
- name = models.CharField(max_length=50)
- type = models.SmallIntegerField(choices=TYPE_CHOICES)
- slug = models.SlugField(max_length=50, blank=True)
- description = models.CharField(max_length=300)
- multiple = models.BooleanField(default=False)
- # Denormalised data
- awarded_count = models.PositiveIntegerField(default=0)
-
- class Meta:
- db_table = u'badge'
- ordering = ('name',)
- unique_together = ('name', 'type')
-
- def __unicode__(self):
- return u'%s: %s' % (self.get_type_display(), self.name)
-
- def save(self, **kwargs):
- if not self.slug:
- self.slug = self.name#slugify(self.name)
- super(Badge, self).save(**kwargs)
-
- def get_absolute_url(self):
- return '%s%s/' % (reverse('badge', args=[self.id]), self.slug)
-
-class Award(models.Model):
- """The awarding of a Badge to a User."""
- user = models.ForeignKey(User, related_name='award_user')
- badge = models.ForeignKey(Badge, related_name='award_badge')
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- awarded_at = models.DateTimeField(default=datetime.datetime.now)
- notified = models.BooleanField(default=False)
- objects = AwardManager()
-
- def __unicode__(self):
- return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.name, self.awarded_at)
-
- class Meta:
- db_table = u'award'
-
-class Repute(models.Model):
- """The reputation histories for user"""
- user = models.ForeignKey(User)
- positive = models.SmallIntegerField(default=0)
- negative = models.SmallIntegerField(default=0)
- question = models.ForeignKey(Question)
- reputed_at = models.DateTimeField(default=datetime.datetime.now)
- reputation_type = models.SmallIntegerField(choices=TYPE_REPUTATION)
- reputation = models.IntegerField(default=1)
- objects = ReputeManager()
-
- def __unicode__(self):
- return u'[%s]\' reputation changed at %s' % (self.user.username, self.reputed_at)
-
- class Meta:
- db_table = u'repute'
-
-class Activity(models.Model):
- """
- We keep some history data for user activities
- """
- user = models.ForeignKey(User)
- activity_type = models.SmallIntegerField(choices=TYPE_ACTIVITY)
- active_at = models.DateTimeField(default=datetime.datetime.now)
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- is_auditted = models.BooleanField(default=False)
-
- def __unicode__(self):
- return u'[%s] was active at %s' % (self.user.username, self.active_at)
-
- class Meta:
- db_table = u'activity'
-
-class Book(models.Model):
- """
- Model for book info
- """
- user = models.ForeignKey(User)
- title = models.CharField(max_length=255)
- short_name = models.CharField(max_length=255)
- author = models.CharField(max_length=255)
- price = models.DecimalField(max_digits=6, decimal_places=2)
- pages = models.SmallIntegerField()
- published_at = models.DateTimeField()
- publication = models.CharField(max_length=255)
- cover_img = models.CharField(max_length=255)
- tagnames = models.CharField(max_length=125)
- added_at = models.DateTimeField()
- last_edited_at = models.DateTimeField()
- questions = models.ManyToManyField(Question, related_name='book', db_table='book_question')
-
- def get_absolute_url(self):
- return '%s' % reverse('book', args=[self.short_name])
-
- def __unicode__(self):
- return self.title
- class Meta:
- db_table = u'book'
-
-class BookAuthorInfo(models.Model):
- """
- Model for book author info
- """
- user = models.ForeignKey(User)
- book = models.ForeignKey(Book)
- blog_url = models.CharField(max_length=255)
- added_at = models.DateTimeField()
- last_edited_at = models.DateTimeField()
-
- class Meta:
- db_table = u'book_author_info'
-
-class BookAuthorRss(models.Model):
- """
- Model for book author blog rss
- """
- user = models.ForeignKey(User)
- book = models.ForeignKey(Book)
- title = models.CharField(max_length=255)
- url = models.CharField(max_length=255)
- rss_created_at = models.DateTimeField()
- added_at = models.DateTimeField()
-
- class Meta:
- db_table = u'book_author_rss'
-
-# User extend properties
-QUESTIONS_PER_PAGE_CHOICES = (
- (10, u'10'),
- (30, u'30'),
- (50, u'50'),
-)
-
-User.add_to_class('reputation', models.PositiveIntegerField(default=1))
-User.add_to_class('gravatar', models.CharField(max_length=32))
-User.add_to_class('favorite_questions',
- models.ManyToManyField(Question, through=FavoriteQuestion,
- related_name='favorited_by'))
-User.add_to_class('badges', models.ManyToManyField(Badge, through=Award,
- related_name='awarded_to'))
-User.add_to_class('gold', models.SmallIntegerField(default=0))
-User.add_to_class('silver', models.SmallIntegerField(default=0))
-User.add_to_class('bronze', models.SmallIntegerField(default=0))
-User.add_to_class('questions_per_page',
- models.SmallIntegerField(choices=QUESTIONS_PER_PAGE_CHOICES, default=10))
-User.add_to_class('last_seen',
- models.DateTimeField(default=datetime.datetime.now))
-User.add_to_class('real_name', models.CharField(max_length=100, blank=True))
-User.add_to_class('website', models.URLField(max_length=200, blank=True))
-User.add_to_class('location', models.CharField(max_length=100, blank=True))
-User.add_to_class('date_of_birth', models.DateField(null=True, blank=True))
-User.add_to_class('about', models.TextField(blank=True))
-
-# custom signal
-tags_updated = django.dispatch.Signal(providing_args=["question"])
-edit_question_or_answer = django.dispatch.Signal(providing_args=["instance", "modified_by"])
-delete_post_or_answer = django.dispatch.Signal(providing_args=["instance", "deleted_by"])
-mark_offensive = django.dispatch.Signal(providing_args=["instance", "mark_by"])
-user_updated = django.dispatch.Signal(providing_args=["instance", "updated_by"])
-def get_messages(self):
- messages = []
- for m in self.message_set.all():
- messages.append(m.message)
- return messages
-
-def delete_messages(self):
- self.message_set.all().delete()
-
-def get_profile_url(self):
- """Returns the URL for this User's profile."""
- return '%s%s/' % (reverse('user', args=[self.id]), self.username)
-User.add_to_class('get_profile_url', get_profile_url)
-User.add_to_class('get_messages', get_messages)
-User.add_to_class('delete_messages', delete_messages)
-
-def calculate_gravatar_hash(instance, **kwargs):
- """Calculates a User's gravatar hash from their email address."""
- if kwargs.get('raw', False):
- return
- instance.gravatar = hashlib.md5(instance.email).hexdigest()
-
-def record_ask_event(instance, created, **kwargs):
- if created:
- activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ASK_QUESTION)
- activity.save()
-
-def record_answer_event(instance, created, **kwargs):
- if created:
- activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ANSWER)
- activity.save()
-
-def record_comment_event(instance, created, **kwargs):
- if created:
- from django.contrib.contenttypes.models import ContentType
- question_type = ContentType.objects.get_for_model(Question)
- question_type_id = question_type.id
- type = TYPE_ACTIVITY_COMMENT_QUESTION if instance.content_type_id == question_type_id else TYPE_ACTIVITY_COMMENT_ANSWER
- activity = Activity(user=instance.user, active_at=instance.added_at, content_object=instance, activity_type=type)
- activity.save()
-
-def record_revision_question_event(instance, created, **kwargs):
- if created and instance.revision <> 1:
- activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_QUESTION)
- activity.save()
-
-def record_revision_answer_event(instance, created, **kwargs):
- if created and instance.revision <> 1:
- activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_ANSWER)
- activity.save()
-
-def record_award_event(instance, created, **kwargs):
- """
- After we awarded a badge to user, we need to record this activity and notify user.
- We also recaculate awarded_count of this badge and user information.
- """
- if created:
- activity = Activity(user=instance.user, active_at=instance.awarded_at, content_object=instance,
- activity_type=TYPE_ACTIVITY_PRIZE)
- activity.save()
-
- instance.badge.awarded_count += 1
- instance.badge.save()
-
- if instance.badge.type == Badge.GOLD:
- instance.user.gold += 1
- if instance.badge.type == Badge.SILVER:
- instance.user.silver += 1
- if instance.badge.type == Badge.BRONZE:
- instance.user.bronze += 1
- instance.user.save()
-
-def notify_award_message(instance, created, **kwargs):
- """
- Notify users when they have been awarded badges by using Django message.
- """
- if created:
- user = instance.user
- user.message_set.create(message=u"%s" % instance.badge.name)
-
-def record_answer_accepted(instance, created, **kwargs):
- """
- when answer is accepted, we record this for question author - who accepted it.
- """
- if not created and instance.accepted:
- activity = Activity(user=instance.question.author, active_at=datetime.datetime.now(), \
- content_object=instance, activity_type=TYPE_ACTIVITY_MARK_ANSWER)
- activity.save()
-
-def update_last_seen(instance, created, **kwargs):
- """
- when user has activities, we update 'last_seen' time stamp for him
- """
- user = instance.user
- user.last_seen = datetime.datetime.now()
- user.save()
-
-def record_vote(instance, created, **kwargs):
- """
- when user have voted
- """
- if created:
- if instance.vote == 1:
- vote_type = TYPE_ACTIVITY_VOTE_UP
- else:
- vote_type = TYPE_ACTIVITY_VOTE_DOWN
-
- activity = Activity(user=instance.user, active_at=instance.voted_at, content_object=instance, activity_type=vote_type)
- activity.save()
-
-def record_cancel_vote(instance, **kwargs):
- """
- when user canceled vote, the vote will be deleted.
- """
- activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_CANCEL_VOTE)
- activity.save()
-
-def record_delete_question(instance, delete_by, **kwargs):
- """
- when user deleted the question
- """
- if instance.__class__ == "Question":
- activity_type = TYPE_ACTIVITY_DELETE_QUESTION
- else:
- activity_type = TYPE_ACTIVITY_DELETE_ANSWER
-
- activity = Activity(user=delete_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=activity_type)
- activity.save()
-
-def record_mark_offensive(instance, mark_by, **kwargs):
- activity = Activity(user=mark_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_MARK_OFFENSIVE)
- activity.save()
-
-def record_update_tags(question, **kwargs):
- """
- when user updated tags of the question
- """
- activity = Activity(user=question.author, active_at=datetime.datetime.now(), content_object=question, activity_type=TYPE_ACTIVITY_UPDATE_TAGS)
- activity.save()
-
-def record_favorite_question(instance, created, **kwargs):
- """
- when user add the question in him favorite questions list.
- """
- if created:
- activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_FAVORITE)
- activity.save()
-
-def record_user_full_updated(instance, **kwargs):
- activity = Activity(user=instance, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_USER_FULL_UPDATED)
- activity.save()
-
-#signal for User modle save changes
-pre_save.connect(calculate_gravatar_hash, sender=User)
-post_save.connect(record_ask_event, sender=Question)
-post_save.connect(record_answer_event, sender=Answer)
-post_save.connect(record_comment_event, sender=Comment)
-post_save.connect(record_revision_question_event, sender=QuestionRevision)
-post_save.connect(record_revision_answer_event, sender=AnswerRevision)
-post_save.connect(record_award_event, sender=Award)
-post_save.connect(notify_award_message, sender=Award)
-post_save.connect(record_answer_accepted, sender=Answer)
-post_save.connect(update_last_seen, sender=Activity)
-post_save.connect(record_vote, sender=Vote)
-post_delete.connect(record_cancel_vote, sender=Vote)
-delete_post_or_answer.connect(record_delete_question, sender=Question)
-delete_post_or_answer.connect(record_delete_question, sender=Answer)
-mark_offensive.connect(record_mark_offensive, sender=Question)
-mark_offensive.connect(record_mark_offensive, sender=Answer)
-tags_updated.connect(record_update_tags, sender=Question)
-post_save.connect(record_favorite_question, sender=FavoriteQuestion)
-user_updated.connect(record_user_full_updated, sender=User)
+# encoding:utf-8 +import datetime +import hashlib +from urllib import quote_plus, urlencode +from django.db import models +from django.utils.html import strip_tags +from django.core.urlresolvers import reverse +from django.contrib.auth.models import User +from django.contrib.contenttypes import generic +from django.contrib.contenttypes.models import ContentType +from django.template.defaultfilters import slugify +from django.db.models.signals import post_delete, post_save, pre_save +from django.utils.translation import ugettext as _ +import django.dispatch + +from forum.managers import * +from const import * + +class Tag(models.Model): + name = models.CharField(max_length=255, unique=True) + created_by = models.ForeignKey(User, related_name='created_tags') + deleted = models.BooleanField(default=False) + deleted_at = models.DateTimeField(null=True, blank=True) + deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_tags') + # Denormalised data + used_count = models.PositiveIntegerField(default=0) + + objects = TagManager() + + class Meta: + db_table = u'tag' + ordering = ('-used_count', 'name') + + def __unicode__(self): + return self.name + +class Comment(models.Model): + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + user = models.ForeignKey(User, related_name='comments') + comment = models.CharField(max_length=300) + added_at = models.DateTimeField(default=datetime.datetime.now) + + class Meta: + ordering = ('-added_at',) + db_table = u'comment' + def __unicode__(self): + return self.comment + +class Vote(models.Model): + VOTE_UP = +1 + VOTE_DOWN = -1 + VOTE_CHOICES = ( + (VOTE_UP, u'Up'), + (VOTE_DOWN, u'Down'), + ) + + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + user = models.ForeignKey(User, related_name='votes') + vote = models.SmallIntegerField(choices=VOTE_CHOICES) + voted_at = models.DateTimeField(default=datetime.datetime.now) + + objects = VoteManager() + + class Meta: + unique_together = ('content_type', 'object_id', 'user') + db_table = u'vote' + def __unicode__(self): + return '[%s] voted at %s: %s' %(self.user, self.voted_at, self.vote) + + def is_upvote(self): + return self.vote == self.VOTE_UP + + def is_downvote(self): + return self.vote == self.VOTE_DOWN + +class FlaggedItem(models.Model): + """A flag on a Question or Answer indicating offensive content.""" + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + user = models.ForeignKey(User, related_name='flagged_items') + flagged_at = models.DateTimeField(default=datetime.datetime.now) + + objects = FlaggedItemManager() + + class Meta: + unique_together = ('content_type', 'object_id', 'user') + db_table = u'flagged_item' + def __unicode__(self): + return '[%s] flagged at %s' %(self.user, self.flagged_at) + +class Question(models.Model): + title = models.CharField(max_length=300) + author = models.ForeignKey(User, related_name='questions') + added_at = models.DateTimeField(default=datetime.datetime.now) + tags = models.ManyToManyField(Tag, related_name='questions') + # Status + wiki = models.BooleanField(default=False) + wikified_at = models.DateTimeField(null=True, blank=True) + answer_accepted = models.BooleanField(default=False) + closed = models.BooleanField(default=False) + closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions') + closed_at = models.DateTimeField(null=True, blank=True) + close_reason = models.SmallIntegerField(choices=CLOSE_REASONS, null=True, blank=True) + deleted = models.BooleanField(default=False) + deleted_at = models.DateTimeField(null=True, blank=True) + deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_questions') + locked = models.BooleanField(default=False) + locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_questions') + locked_at = models.DateTimeField(null=True, blank=True) + # Denormalised data + score = models.IntegerField(default=0) + vote_up_count = models.IntegerField(default=0) + vote_down_count = models.IntegerField(default=0) + answer_count = models.PositiveIntegerField(default=0) + comment_count = models.PositiveIntegerField(default=0) + view_count = models.PositiveIntegerField(default=0) + offensive_flag_count = models.SmallIntegerField(default=0) + favourite_count = models.PositiveIntegerField(default=0) + last_edited_at = models.DateTimeField(null=True, blank=True) + last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_questions') + last_activity_at = models.DateTimeField(default=datetime.datetime.now) + last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions') + tagnames = models.CharField(max_length=125) + summary = models.CharField(max_length=180) + html = models.TextField() + comments = generic.GenericRelation(Comment) + votes = generic.GenericRelation(Vote) + flagged_items = generic.GenericRelation(FlaggedItem) + + objects = QuestionManager() + + def save(self, **kwargs): + """ + Overridden to manually manage addition of tags when the object + is first saved. + + This is required as we're using ``tagnames`` as the sole means of + adding and editing tags. + """ + initial_addition = (self.id is None) + super(Question, self).save(**kwargs) + if initial_addition: + tags = Tag.objects.get_or_create_multiple(self.tagname_list(), + self.author) + self.tags.add(*tags) + Tag.objects.update_use_counts(tags) + + def tagname_list(self): + """Creates a list of Tag names from the ``tagnames`` attribute.""" + return [name for name in self.tagnames.split(u' ')] + + def get_absolute_url(self): + return '%s%s' % (reverse('question', args=[self.id]), self.title.replace(' ', '-')) + + def has_favorite_by_user(self, user): + if not user.is_authenticated(): + return False + return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0 + + def get_answer_count_by_user(self, user_id): + query_set = Answer.objects.filter(author__id=user_id) + return query_set.filter(question=self).count() + + def get_question_title(self): + if self.closed: + attr = CONST['closed'] + elif self.deleted: + attr = CONST['deleted'] + else: + attr = None + return u'%s %s' % (self.title, attr) if attr is not None else self.title + + def get_revision_url(self): + return reverse('question_revisions', args=[self.id]) + + def get_latest_revision(self): + return self.revisions.all()[0] + + def __unicode__(self): + return self.title + + class Meta: + db_table = u'question' + +class QuestionRevision(models.Model): + """A revision of a Question.""" + question = models.ForeignKey(Question, related_name='revisions') + revision = models.PositiveIntegerField(blank=True) + title = models.CharField(max_length=300) + author = models.ForeignKey(User, related_name='question_revisions') + revised_at = models.DateTimeField() + tagnames = models.CharField(max_length=125) + summary = models.CharField(max_length=300, blank=True) + text = models.TextField() + + class Meta: + db_table = u'question_revision' + ordering = ('-revision',) + + def get_question_title(self): + return self.question.title + + def get_absolute_url(self): + return '/questions/%s/revisions' % (self.question.id) + + def save(self, **kwargs): + """Looks up the next available revision number.""" + if not self.revision: + self.revision = QuestionRevision.objects.filter( + question=self.question).values_list('revision', + flat=True)[0] + 1 + super(QuestionRevision, self).save(**kwargs) + + def __unicode__(self): + return u'revision %s of %s' % (self.revision, self.title) + +class Answer(models.Model): + question = models.ForeignKey(Question, related_name='answers') + author = models.ForeignKey(User, related_name='answers') + added_at = models.DateTimeField(default=datetime.datetime.now) + # Status + wiki = models.BooleanField(default=False) + wikified_at = models.DateTimeField(null=True, blank=True) + accepted = models.BooleanField(default=False) + accepted_at = models.DateTimeField(null=True, blank=True) + deleted = models.BooleanField(default=False) + deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_answers') + locked = models.BooleanField(default=False) + locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_answers') + locked_at = models.DateTimeField(null=True, blank=True) + # Denormalised data + score = models.IntegerField(default=0) + vote_up_count = models.IntegerField(default=0) + vote_down_count = models.IntegerField(default=0) + comment_count = models.PositiveIntegerField(default=0) + offensive_flag_count = models.SmallIntegerField(default=0) + last_edited_at = models.DateTimeField(null=True, blank=True) + last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_answers') + html = models.TextField() + comments = generic.GenericRelation(Comment) + votes = generic.GenericRelation(Vote) + flagged_items = generic.GenericRelation(FlaggedItem) + + objects = AnswerManager() + + def get_user_vote(self, user): + votes = self.votes.filter(user=user) + if votes.count() > 0: + return votes[0] + else: + return None + + def get_latest_revision(self): + return self.revisions.all()[0] + + def get_question_title(self): + return self.question.title + + def get_absolute_url(self): + return '%s%s#%s' % (reverse('question', args=[self.question.id]), self.question.title, self.id) + + class Meta: + db_table = u'answer' + + def __unicode__(self): + return self.html + +class AnswerRevision(models.Model): + """A revision of an Answer.""" + answer = models.ForeignKey(Answer, related_name='revisions') + revision = models.PositiveIntegerField() + author = models.ForeignKey(User, related_name='answer_revisions') + revised_at = models.DateTimeField() + summary = models.CharField(max_length=300, blank=True) + text = models.TextField() + + def get_absolute_url(self): + return '/answers/%s/revisions' % (self.answer.id) + + def get_question_title(self): + return self.answer.question.title + + class Meta: + db_table = u'answer_revision' + ordering = ('-revision',) + + def save(self, **kwargs): + """Looks up the next available revision number if not set.""" + if not self.revision: + self.revision = AnswerRevision.objects.filter( + answer=self.answer).values_list('revision', + flat=True)[0] + 1 + super(AnswerRevision, self).save(**kwargs) + +class FavoriteQuestion(models.Model): + """A favorite Question of a User.""" + question = models.ForeignKey(Question) + user = models.ForeignKey(User, related_name='user_favorite_questions') + added_at = models.DateTimeField(default=datetime.datetime.now) + class Meta: + db_table = u'favorite_question' + def __unicode__(self): + return '[%s] favorited at %s' %(self.user, self.added_at) + +class Badge(models.Model): + """Awarded for notable actions performed on the site by Users.""" + GOLD = 1 + SILVER = 2 + BRONZE = 3 + TYPE_CHOICES = ( + (GOLD, _('gold')), + (SILVER, _('silver')), + (BRONZE, _('bronze')), + ) + + name = models.CharField(max_length=50) + type = models.SmallIntegerField(choices=TYPE_CHOICES) + slug = models.SlugField(max_length=50, blank=True) + description = models.CharField(max_length=300) + multiple = models.BooleanField(default=False) + # Denormalised data + awarded_count = models.PositiveIntegerField(default=0) + + class Meta: + db_table = u'badge' + ordering = ('name',) + unique_together = ('name', 'type') + + def __unicode__(self): + return u'%s: %s' % (self.get_type_display(), self.name) + + def save(self, **kwargs): + if not self.slug: + self.slug = self.name#slugify(self.name) + super(Badge, self).save(**kwargs) + + def get_absolute_url(self): + return '%s%s/' % (reverse('badge', args=[self.id]), self.slug) + +class Award(models.Model): + """The awarding of a Badge to a User.""" + user = models.ForeignKey(User, related_name='award_user') + badge = models.ForeignKey(Badge, related_name='award_badge') + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + awarded_at = models.DateTimeField(default=datetime.datetime.now) + notified = models.BooleanField(default=False) + objects = AwardManager() + + def __unicode__(self): + return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.name, self.awarded_at) + + class Meta: + db_table = u'award' + +class Repute(models.Model): + """The reputation histories for user""" + user = models.ForeignKey(User) + positive = models.SmallIntegerField(default=0) + negative = models.SmallIntegerField(default=0) + question = models.ForeignKey(Question) + reputed_at = models.DateTimeField(default=datetime.datetime.now) + reputation_type = models.SmallIntegerField(choices=TYPE_REPUTATION) + reputation = models.IntegerField(default=1) + objects = ReputeManager() + + def __unicode__(self): + return u'[%s]\' reputation changed at %s' % (self.user.username, self.reputed_at) + + class Meta: + db_table = u'repute' + +class Activity(models.Model): + """ + We keep some history data for user activities + """ + user = models.ForeignKey(User) + activity_type = models.SmallIntegerField(choices=TYPE_ACTIVITY) + active_at = models.DateTimeField(default=datetime.datetime.now) + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + is_auditted = models.BooleanField(default=False) + + def __unicode__(self): + return u'[%s] was active at %s' % (self.user.username, self.active_at) + + class Meta: + db_table = u'activity' + +class Book(models.Model): + """ + Model for book info + """ + user = models.ForeignKey(User) + title = models.CharField(max_length=255) + short_name = models.CharField(max_length=255) + author = models.CharField(max_length=255) + price = models.DecimalField(max_digits=6, decimal_places=2) + pages = models.SmallIntegerField() + published_at = models.DateTimeField() + publication = models.CharField(max_length=255) + cover_img = models.CharField(max_length=255) + tagnames = models.CharField(max_length=125) + added_at = models.DateTimeField() + last_edited_at = models.DateTimeField() + questions = models.ManyToManyField(Question, related_name='book', db_table='book_question') + + def get_absolute_url(self): + return '%s' % reverse('book', args=[self.short_name]) + + def __unicode__(self): + return self.title + class Meta: + db_table = u'book' + +class BookAuthorInfo(models.Model): + """ + Model for book author info + """ + user = models.ForeignKey(User) + book = models.ForeignKey(Book) + blog_url = models.CharField(max_length=255) + added_at = models.DateTimeField() + last_edited_at = models.DateTimeField() + + class Meta: + db_table = u'book_author_info' + +class BookAuthorRss(models.Model): + """ + Model for book author blog rss + """ + user = models.ForeignKey(User) + book = models.ForeignKey(Book) + title = models.CharField(max_length=255) + url = models.CharField(max_length=255) + rss_created_at = models.DateTimeField() + added_at = models.DateTimeField() + + class Meta: + db_table = u'book_author_rss' + +# User extend properties +QUESTIONS_PER_PAGE_CHOICES = ( + (10, u'10'), + (30, u'30'), + (50, u'50'), +) + +User.add_to_class('reputation', models.PositiveIntegerField(default=1)) +User.add_to_class('gravatar', models.CharField(max_length=32)) +User.add_to_class('favorite_questions', + models.ManyToManyField(Question, through=FavoriteQuestion, + related_name='favorited_by')) +User.add_to_class('badges', models.ManyToManyField(Badge, through=Award, + related_name='awarded_to')) +User.add_to_class('gold', models.SmallIntegerField(default=0)) +User.add_to_class('silver', models.SmallIntegerField(default=0)) +User.add_to_class('bronze', models.SmallIntegerField(default=0)) +User.add_to_class('questions_per_page', + models.SmallIntegerField(choices=QUESTIONS_PER_PAGE_CHOICES, default=10)) +User.add_to_class('last_seen', + models.DateTimeField(default=datetime.datetime.now)) +User.add_to_class('real_name', models.CharField(max_length=100, blank=True)) +User.add_to_class('website', models.URLField(max_length=200, blank=True)) +User.add_to_class('location', models.CharField(max_length=100, blank=True)) +User.add_to_class('date_of_birth', models.DateField(null=True, blank=True)) +User.add_to_class('about', models.TextField(blank=True)) + +# custom signal +tags_updated = django.dispatch.Signal(providing_args=["question"]) +edit_question_or_answer = django.dispatch.Signal(providing_args=["instance", "modified_by"]) +delete_post_or_answer = django.dispatch.Signal(providing_args=["instance", "deleted_by"]) +mark_offensive = django.dispatch.Signal(providing_args=["instance", "mark_by"]) +user_updated = django.dispatch.Signal(providing_args=["instance", "updated_by"]) +def get_messages(self): + messages = [] + for m in self.message_set.all(): + messages.append(m.message) + return messages + +def delete_messages(self): + self.message_set.all().delete() + +def get_profile_url(self): + """Returns the URL for this User's profile.""" + return '%s%s/' % (reverse('user', args=[self.id]), self.username) +User.add_to_class('get_profile_url', get_profile_url) +User.add_to_class('get_messages', get_messages) +User.add_to_class('delete_messages', delete_messages) + +def calculate_gravatar_hash(instance, **kwargs): + """Calculates a User's gravatar hash from their email address.""" + if kwargs.get('raw', False): + return + instance.gravatar = hashlib.md5(instance.email).hexdigest() + +def record_ask_event(instance, created, **kwargs): + if created: + activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ASK_QUESTION) + activity.save() + +def record_answer_event(instance, created, **kwargs): + if created: + activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ANSWER) + activity.save() + +def record_comment_event(instance, created, **kwargs): + if created: + from django.contrib.contenttypes.models import ContentType + question_type = ContentType.objects.get_for_model(Question) + question_type_id = question_type.id + type = TYPE_ACTIVITY_COMMENT_QUESTION if instance.content_type_id == question_type_id else TYPE_ACTIVITY_COMMENT_ANSWER + activity = Activity(user=instance.user, active_at=instance.added_at, content_object=instance, activity_type=type) + activity.save() + +def record_revision_question_event(instance, created, **kwargs): + if created and instance.revision <> 1: + activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_QUESTION) + activity.save() + +def record_revision_answer_event(instance, created, **kwargs): + if created and instance.revision <> 1: + activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_ANSWER) + activity.save() + +def record_award_event(instance, created, **kwargs): + """ + After we awarded a badge to user, we need to record this activity and notify user. + We also recaculate awarded_count of this badge and user information. + """ + if created: + activity = Activity(user=instance.user, active_at=instance.awarded_at, content_object=instance, + activity_type=TYPE_ACTIVITY_PRIZE) + activity.save() + + instance.badge.awarded_count += 1 + instance.badge.save() + + if instance.badge.type == Badge.GOLD: + instance.user.gold += 1 + if instance.badge.type == Badge.SILVER: + instance.user.silver += 1 + if instance.badge.type == Badge.BRONZE: + instance.user.bronze += 1 + instance.user.save() + +def notify_award_message(instance, created, **kwargs): + """ + Notify users when they have been awarded badges by using Django message. + """ + if created: + user = instance.user + user.message_set.create(message=u"%s" % instance.badge.name) + +def record_answer_accepted(instance, created, **kwargs): + """ + when answer is accepted, we record this for question author - who accepted it. + """ + if not created and instance.accepted: + activity = Activity(user=instance.question.author, active_at=datetime.datetime.now(), \ + content_object=instance, activity_type=TYPE_ACTIVITY_MARK_ANSWER) + activity.save() + +def update_last_seen(instance, created, **kwargs): + """ + when user has activities, we update 'last_seen' time stamp for him + """ + user = instance.user + user.last_seen = datetime.datetime.now() + user.save() + +def record_vote(instance, created, **kwargs): + """ + when user have voted + """ + if created: + if instance.vote == 1: + vote_type = TYPE_ACTIVITY_VOTE_UP + else: + vote_type = TYPE_ACTIVITY_VOTE_DOWN + + activity = Activity(user=instance.user, active_at=instance.voted_at, content_object=instance, activity_type=vote_type) + activity.save() + +def record_cancel_vote(instance, **kwargs): + """ + when user canceled vote, the vote will be deleted. + """ + activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_CANCEL_VOTE) + activity.save() + +def record_delete_question(instance, delete_by, **kwargs): + """ + when user deleted the question + """ + if instance.__class__ == "Question": + activity_type = TYPE_ACTIVITY_DELETE_QUESTION + else: + activity_type = TYPE_ACTIVITY_DELETE_ANSWER + + activity = Activity(user=delete_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=activity_type) + activity.save() + +def record_mark_offensive(instance, mark_by, **kwargs): + activity = Activity(user=mark_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_MARK_OFFENSIVE) + activity.save() + +def record_update_tags(question, **kwargs): + """ + when user updated tags of the question + """ + activity = Activity(user=question.author, active_at=datetime.datetime.now(), content_object=question, activity_type=TYPE_ACTIVITY_UPDATE_TAGS) + activity.save() + +def record_favorite_question(instance, created, **kwargs): + """ + when user add the question in him favorite questions list. + """ + if created: + activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_FAVORITE) + activity.save() + +def record_user_full_updated(instance, **kwargs): + activity = Activity(user=instance, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_USER_FULL_UPDATED) + activity.save() + +#signal for User modle save changes +pre_save.connect(calculate_gravatar_hash, sender=User) +post_save.connect(record_ask_event, sender=Question) +post_save.connect(record_answer_event, sender=Answer) +post_save.connect(record_comment_event, sender=Comment) +post_save.connect(record_revision_question_event, sender=QuestionRevision) +post_save.connect(record_revision_answer_event, sender=AnswerRevision) +post_save.connect(record_award_event, sender=Award) +post_save.connect(notify_award_message, sender=Award) +post_save.connect(record_answer_accepted, sender=Answer) +post_save.connect(update_last_seen, sender=Activity) +post_save.connect(record_vote, sender=Vote) +post_delete.connect(record_cancel_vote, sender=Vote) +delete_post_or_answer.connect(record_delete_question, sender=Question) +delete_post_or_answer.connect(record_delete_question, sender=Answer) +mark_offensive.connect(record_mark_offensive, sender=Question) +mark_offensive.connect(record_mark_offensive, sender=Answer) +tags_updated.connect(record_update_tags, sender=Question) +post_save.connect(record_favorite_question, sender=FavoriteQuestion) +user_updated.connect(record_user_full_updated, sender=User) diff --git a/forum/templatetags/extra_filters.py b/forum/templatetags/extra_filters.py index 744fa762..cec97920 100644 --- a/forum/templatetags/extra_filters.py +++ b/forum/templatetags/extra_filters.py @@ -1,83 +1,83 @@ -from django import template
-from forum import auth
-
-register = template.Library()
-
-@register.filter
-def can_vote_up(user):
- return auth.can_vote_up(user)
-
-@register.filter
-def can_flag_offensive(user):
- return auth.can_flag_offensive(user)
-
-@register.filter
-def can_add_comments(user):
- return auth.can_add_comments(user)
-
-@register.filter
-def can_vote_down(user):
- return auth.can_vote_down(user)
-
-@register.filter
-def can_retag_questions(user):
- return auth.can_retag_questions(user)
-
-@register.filter
-def can_edit_post(user, post):
- return auth.can_edit_post(user, post)
-
-@register.filter
-def can_delete_comment(user, comment):
- return auth.can_delete_comment(user, comment)
-
-@register.filter
-def can_view_offensive_flags(user):
- return auth.can_view_offensive_flags(user)
-
-@register.filter
-def can_close_question(user, question):
- return auth.can_close_question(user, question)
-
-@register.filter
-def can_lock_posts(user):
- return auth.can_lock_posts(user)
-
-@register.filter
-def can_accept_answer(user, question, answer):
- return auth.can_accept_answer(user, question, answer)
-
-@register.filter
-def can_reopen_question(user, question):
- return auth.can_reopen_question(user, question)
-
-@register.filter
-def can_delete_post(user, post):
- return auth.can_delete_post(user, post)
-
-@register.filter
-def can_view_user_edit(request_user, target_user):
- return auth.can_view_user_edit(request_user, target_user)
-
-@register.filter
-def can_view_user_votes(request_user, target_user):
- return auth.can_view_user_votes(request_user, target_user)
-
-@register.filter
-def can_view_user_preferences(request_user, target_user):
- return auth.can_view_user_preferences(request_user, target_user)
-
-@register.filter
-def is_user_self(request_user, target_user):
- return auth.is_user_self(request_user, target_user)
-
-@register.filter
-def cnprog_intword(number):
- try:
- if 1000 <= number < 10000:
- string = str(number)[0:1]
- return "<span class=""thousand"">%sk</span>" % string
- else:
- return number
- except:
+from django import template +from forum import auth + +register = template.Library() + +@register.filter +def can_vote_up(user): + return auth.can_vote_up(user) + +@register.filter +def can_flag_offensive(user): + return auth.can_flag_offensive(user) + +@register.filter +def can_add_comments(user): + return auth.can_add_comments(user) + +@register.filter +def can_vote_down(user): + return auth.can_vote_down(user) + +@register.filter +def can_retag_questions(user): + return auth.can_retag_questions(user) + +@register.filter +def can_edit_post(user, post): + return auth.can_edit_post(user, post) + +@register.filter +def can_delete_comment(user, comment): + return auth.can_delete_comment(user, comment) + +@register.filter +def can_view_offensive_flags(user): + return auth.can_view_offensive_flags(user) + +@register.filter +def can_close_question(user, question): + return auth.can_close_question(user, question) + +@register.filter +def can_lock_posts(user): + return auth.can_lock_posts(user) + +@register.filter +def can_accept_answer(user, question, answer): + return auth.can_accept_answer(user, question, answer) + +@register.filter +def can_reopen_question(user, question): + return auth.can_reopen_question(user, question) + +@register.filter +def can_delete_post(user, post): + return auth.can_delete_post(user, post) + +@register.filter +def can_view_user_edit(request_user, target_user): + return auth.can_view_user_edit(request_user, target_user) + +@register.filter +def can_view_user_votes(request_user, target_user): + return auth.can_view_user_votes(request_user, target_user) + +@register.filter +def can_view_user_preferences(request_user, target_user): + return auth.can_view_user_preferences(request_user, target_user) + +@register.filter +def is_user_self(request_user, target_user): + return auth.is_user_self(request_user, target_user) + +@register.filter +def cnprog_intword(number): + try: + if 1000 <= number < 10000: + string = str(number)[0:1] + return "<span class=""thousand"">%sk</span>" % string + else: + return number + except: return number
\ No newline at end of file diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py index 1a4d3641..6c826771 100644 --- a/forum/templatetags/extra_tags.py +++ b/forum/templatetags/extra_tags.py @@ -1,240 +1,240 @@ -import time
-import datetime
-import math
-import re
-import logging
-from django import template
-from django.utils.encoding import smart_unicode
-from django.utils.safestring import mark_safe
-from django.utils.timesince import timesince
-from forum.const import *
-from django.utils.translation import ugettext as _
-
-register = template.Library()
-
-GRAVATAR_TEMPLATE = ('<img width="%(size)s" height="%(size)s" '
- 'src="http://www.gravatar.com/avatar/%(gravatar_hash)s'
- '?s=%(size)s&d=identicon&r=PG">')
-
-@register.simple_tag
-def gravatar(user, size):
- """
- Creates an ``<img>`` for a user's Gravatar with a given size.
-
- This tag can accept a User object, or a dict containing the
- appropriate values.
- """
- try:
- gravatar = user['gravatar']
- except (TypeError, AttributeError, KeyError):
- gravatar = user.gravatar
- return mark_safe(GRAVATAR_TEMPLATE % {
- 'size': size,
- 'gravatar_hash': gravatar,
- })
-
-MAX_FONTSIZE = 18
-MIN_FONTSIZE = 12
-@register.simple_tag
-def tag_font_size(max_size, min_size, current_size):
- """
- do a logarithmic mapping calcuation for a proper size for tagging cloud
- Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/
- """
- #avoid invalid calculation
- if current_size == 0:
- current_size = 1
- try:
- weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size))
- except:
- weight = 0
- return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight)
-
-
-LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5
-LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 4
-NUM_PAGES_OUTSIDE_RANGE = 1
-ADJACENT_PAGES = 2
-@register.inclusion_tag("paginator.html")
-def cnprog_paginator(context):
- """
- custom paginator tag
- Inspired from http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/
- """
- if (context["is_paginated"]):
- " Initialize variables "
- in_leading_range = in_trailing_range = False
- pages_outside_leading_range = pages_outside_trailing_range = range(0)
-
- if (context["pages"] <= LEADING_PAGE_RANGE_DISPLAYED):
- in_leading_range = in_trailing_range = True
- page_numbers = [n for n in range(1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
- elif (context["page"] <= LEADING_PAGE_RANGE):
- in_leading_range = True
- page_numbers = [n for n in range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) if n > 0 and n <= context["pages"]]
- pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
- elif (context["page"] > context["pages"] - TRAILING_PAGE_RANGE):
- in_trailing_range = True
- page_numbers = [n for n in range(context["pages"] - TRAILING_PAGE_RANGE_DISPLAYED + 1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
- pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
- else:
- page_numbers = [n for n in range(context["page"] - ADJACENT_PAGES, context["page"] + ADJACENT_PAGES + 1) if n > 0 and n <= context["pages"]]
- pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
- pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
-
- extend_url = context.get('extend_url', '')
- return {
- "base_url": context["base_url"],
- "is_paginated": context["is_paginated"],
- "previous": context["previous"],
- "has_previous": context["has_previous"],
- "next": context["next"],
- "has_next": context["has_next"],
- "page": context["page"],
- "pages": context["pages"],
- "page_numbers": page_numbers,
- "in_leading_range" : in_leading_range,
- "in_trailing_range" : in_trailing_range,
- "pages_outside_leading_range": pages_outside_leading_range,
- "pages_outside_trailing_range": pages_outside_trailing_range,
- "extend_url" : extend_url
- }
-
-@register.inclusion_tag("pagesize.html")
-def cnprog_pagesize(context):
- """
- display the pagesize selection boxes for paginator
- """
- if (context["is_paginated"]):
- return {
- "base_url": context["base_url"],
- "pagesize" : context["pagesize"],
- "is_paginated": context["is_paginated"]
- }
-
-@register.simple_tag
-def get_score_badge(user):
- BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(reputation)s</span>'
- if user.gold > 0 :
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">'
- '<span class="badge1">●</span>'
- '<span class="badgecount">%(gold)s</span>'
- '</span>')
- if user.silver > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">'
- '<span class="silver">●</span>'
- '<span class="badgecount">%(silver)s</span>'
- '</span>')
- if user.bronze > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">'
- '<span class="bronze">●</span>'
- '<span class="badgecount">%(bronze)s</span>'
- '</span>')
- BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
- return mark_safe(BADGE_TEMPLATE % {
- 'reputation' : user.reputation,
- 'gold' : user.gold,
- 'silver' : user.silver,
- 'bronze' : user.bronze,
- 'badgesword' : _('badges'),
- 'reputationword' : _('reputation points'),
- })
-
-@register.simple_tag
-def get_score_badge_by_details(rep, gold, silver, bronze):
- BADGE_TEMPLATE = '<span class="reputation-score" title="%(reputation)s %(repword)s">%(reputation)s</span>'
- if gold > 0 :
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgeword)s">'
- '<span class="badge1">●</span>'
- '<span class="badgecount">%(gold)s</span>'
- '</span>')
- if silver > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgeword)s">'
- '<span class="badge2">●</span>'
- '<span class="badgecount">%(silver)s</span>'
- '</span>')
- if bronze > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgeword)s">'
- '<span class="badge3">●</span>'
- '<span class="badgecount">%(bronze)s</span>'
- '</span>')
- BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
- return mark_safe(BADGE_TEMPLATE % {
- 'reputation' : rep,
- 'gold' : gold,
- 'silver' : silver,
- 'bronze' : bronze,
- 'repword' : _('reputation points'),
- 'badgeword' : _('badges'),
- })
-
-@register.simple_tag
-def get_user_vote_image(dic, key, arrow):
- if dic.has_key(key):
- if int(dic[key]) == int(arrow):
- return '-on'
- return ''
-
-@register.simple_tag
-def get_age(birthday):
- current_time = datetime.datetime(*time.localtime()[0:6])
- year = birthday.year
- month = birthday.month
- day = birthday.day
- diff = current_time - datetime.datetime(year,month,day,0,0,0)
- return diff.days / 365
-
-@register.simple_tag
-def get_total_count(up_count, down_count):
- return up_count + down_count
-
-@register.simple_tag
-def format_number(value):
- strValue = str(value)
- if len(strValue) <= 3:
- return strValue
- result = ''
- first = ''
- pattern = re.compile('(-?\d+)(\d{3})')
- m = re.match(pattern, strValue)
- while m != None:
- first = m.group(1)
- second = m.group(2)
- result = ',' + second + result
- strValue = first + ',' + second
- m = re.match(pattern, strValue)
- return first + result
-
-@register.simple_tag
-def convert2tagname_list(question):
- question['tagnames'] = [name for name in question['tagnames'].split(u' ')]
- return ''
-
-@register.simple_tag
-def diff_date(date, limen=2):
- current_time = datetime.datetime(*time.localtime()[0:6])
- diff = current_time - date
- diff_days = diff.days
- if diff_days > limen:
- return date
- else:
- return timesince(date) + _(' ago')
-
-@register.simple_tag
-def get_latest_changed_timestamp():
- try:
- from time import localtime, strftime
- from os import path
- from django.conf import settings
- root = settings.SITE_SRC_ROOT
- dir = (
- root,
- '%s/forum' % root,
- '%s/templates' % root,
- )
- stamp = (path.getmtime(d) for d in dir)
- latest = max(stamp)
- timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest))
- except:
- timestr = ''
+import time +import datetime +import math +import re +import logging +from django import template +from django.utils.encoding import smart_unicode +from django.utils.safestring import mark_safe +from django.utils.timesince import timesince +from forum.const import * +from django.utils.translation import ugettext as _ + +register = template.Library() + +GRAVATAR_TEMPLATE = ('<img width="%(size)s" height="%(size)s" ' + 'src="http://www.gravatar.com/avatar/%(gravatar_hash)s' + '?s=%(size)s&d=identicon&r=PG">') + +@register.simple_tag +def gravatar(user, size): + """ + Creates an ``<img>`` for a user's Gravatar with a given size. + + This tag can accept a User object, or a dict containing the + appropriate values. + """ + try: + gravatar = user['gravatar'] + except (TypeError, AttributeError, KeyError): + gravatar = user.gravatar + return mark_safe(GRAVATAR_TEMPLATE % { + 'size': size, + 'gravatar_hash': gravatar, + }) + +MAX_FONTSIZE = 18 +MIN_FONTSIZE = 12 +@register.simple_tag +def tag_font_size(max_size, min_size, current_size): + """ + do a logarithmic mapping calcuation for a proper size for tagging cloud + Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/ + """ + #avoid invalid calculation + if current_size == 0: + current_size = 1 + try: + weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size)) + except: + weight = 0 + return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight) + + +LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5 +LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 4 +NUM_PAGES_OUTSIDE_RANGE = 1 +ADJACENT_PAGES = 2 +@register.inclusion_tag("paginator.html") +def cnprog_paginator(context): + """ + custom paginator tag + Inspired from http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/ + """ + if (context["is_paginated"]): + " Initialize variables " + in_leading_range = in_trailing_range = False + pages_outside_leading_range = pages_outside_trailing_range = range(0) + + if (context["pages"] <= LEADING_PAGE_RANGE_DISPLAYED): + in_leading_range = in_trailing_range = True + page_numbers = [n for n in range(1, context["pages"] + 1) if n > 0 and n <= context["pages"]] + elif (context["page"] <= LEADING_PAGE_RANGE): + in_leading_range = True + page_numbers = [n for n in range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) if n > 0 and n <= context["pages"]] + pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)] + elif (context["page"] > context["pages"] - TRAILING_PAGE_RANGE): + in_trailing_range = True + page_numbers = [n for n in range(context["pages"] - TRAILING_PAGE_RANGE_DISPLAYED + 1, context["pages"] + 1) if n > 0 and n <= context["pages"]] + pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)] + else: + page_numbers = [n for n in range(context["page"] - ADJACENT_PAGES, context["page"] + ADJACENT_PAGES + 1) if n > 0 and n <= context["pages"]] + pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)] + pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)] + + extend_url = context.get('extend_url', '') + return { + "base_url": context["base_url"], + "is_paginated": context["is_paginated"], + "previous": context["previous"], + "has_previous": context["has_previous"], + "next": context["next"], + "has_next": context["has_next"], + "page": context["page"], + "pages": context["pages"], + "page_numbers": page_numbers, + "in_leading_range" : in_leading_range, + "in_trailing_range" : in_trailing_range, + "pages_outside_leading_range": pages_outside_leading_range, + "pages_outside_trailing_range": pages_outside_trailing_range, + "extend_url" : extend_url + } + +@register.inclusion_tag("pagesize.html") +def cnprog_pagesize(context): + """ + display the pagesize selection boxes for paginator + """ + if (context["is_paginated"]): + return { + "base_url": context["base_url"], + "pagesize" : context["pagesize"], + "is_paginated": context["is_paginated"] + } + +@register.simple_tag +def get_score_badge(user): + BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(reputation)s</span>' + if user.gold > 0 : + BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">' + '<span class="badge1">●</span>' + '<span class="badgecount">%(gold)s</span>' + '</span>') + if user.silver > 0: + BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">' + '<span class="silver">●</span>' + '<span class="badgecount">%(silver)s</span>' + '</span>') + if user.bronze > 0: + BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">' + '<span class="bronze">●</span>' + '<span class="badgecount">%(bronze)s</span>' + '</span>') + BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict') + return mark_safe(BADGE_TEMPLATE % { + 'reputation' : user.reputation, + 'gold' : user.gold, + 'silver' : user.silver, + 'bronze' : user.bronze, + 'badgesword' : _('badges'), + 'reputationword' : _('reputation points'), + }) + +@register.simple_tag +def get_score_badge_by_details(rep, gold, silver, bronze): + BADGE_TEMPLATE = '<span class="reputation-score" title="%(reputation)s %(repword)s">%(reputation)s</span>' + if gold > 0 : + BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgeword)s">' + '<span class="badge1">●</span>' + '<span class="badgecount">%(gold)s</span>' + '</span>') + if silver > 0: + BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgeword)s">' + '<span class="badge2">●</span>' + '<span class="badgecount">%(silver)s</span>' + '</span>') + if bronze > 0: + BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgeword)s">' + '<span class="badge3">●</span>' + '<span class="badgecount">%(bronze)s</span>' + '</span>') + BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict') + return mark_safe(BADGE_TEMPLATE % { + 'reputation' : rep, + 'gold' : gold, + 'silver' : silver, + 'bronze' : bronze, + 'repword' : _('reputation points'), + 'badgeword' : _('badges'), + }) + +@register.simple_tag +def get_user_vote_image(dic, key, arrow): + if dic.has_key(key): + if int(dic[key]) == int(arrow): + return '-on' + return '' + +@register.simple_tag +def get_age(birthday): + current_time = datetime.datetime(*time.localtime()[0:6]) + year = birthday.year + month = birthday.month + day = birthday.day + diff = current_time - datetime.datetime(year,month,day,0,0,0) + return diff.days / 365 + +@register.simple_tag +def get_total_count(up_count, down_count): + return up_count + down_count + +@register.simple_tag +def format_number(value): + strValue = str(value) + if len(strValue) <= 3: + return strValue + result = '' + first = '' + pattern = re.compile('(-?\d+)(\d{3})') + m = re.match(pattern, strValue) + while m != None: + first = m.group(1) + second = m.group(2) + result = ',' + second + result + strValue = first + ',' + second + m = re.match(pattern, strValue) + return first + result + +@register.simple_tag +def convert2tagname_list(question): + question['tagnames'] = [name for name in question['tagnames'].split(u' ')] + return '' + +@register.simple_tag +def diff_date(date, limen=2): + current_time = datetime.datetime(*time.localtime()[0:6]) + diff = current_time - date + diff_days = diff.days + if diff_days > limen: + return date + else: + return timesince(date) + _(' ago') + +@register.simple_tag +def get_latest_changed_timestamp(): + try: + from time import localtime, strftime + from os import path + from django.conf import settings + root = settings.SITE_SRC_ROOT + dir = ( + root, + '%s/forum' % root, + '%s/templates' % root, + ) + stamp = (path.getmtime(d) for d in dir) + latest = max(stamp) + timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest)) + except: + timestr = '' return timestr
\ No newline at end of file diff --git a/forum/user.py b/forum/user.py index 233baf0c..ed4494d6 100644 --- a/forum/user.py +++ b/forum/user.py @@ -1,75 +1,75 @@ -# coding=utf-8
-from django.utils.translation import ugettext as _
-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 = 'preferences',
- tab_title = _('preferences'),
- tab_description = _('user preference settings'),
- page_title = _('profile - user preferences'),
- view_name = 'user_preferences',
- template_file = 'user_preferences.html'
- )
-)
+# coding=utf-8 +from django.utils.translation import ugettext as _ +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 = 'preferences', + tab_title = _('preferences'), + tab_description = _('user preference settings'), + page_title = _('profile - user preferences'), + view_name = 'user_preferences', + template_file = 'user_preferences.html' + ) +) diff --git a/forum/views.py b/forum/views.py index 6ac172df..2c61d41e 100644 --- a/forum/views.py +++ b/forum/views.py @@ -115,7 +115,7 @@ def questions(request, tagname=None, unanswered=False): # 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") + pagesize = request.session.get("pagesize",10) try: page = int(request.GET.get('page', '1')) except ValueError: diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index fe0aab7c..6801907c 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -3,7 +3,6 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" diff --git a/locale/zh_CN/LC_MESSAGES/django.po b/locale/zh_CN/LC_MESSAGES/django.po index 206f4b6f..a34a35f2 100644 --- a/locale/zh_CN/LC_MESSAGES/django.po +++ b/locale/zh_CN/LC_MESSAGES/django.po @@ -1103,10 +1103,6 @@ msgstr "标签问题" msgid "Active" msgstr "活跃问题" -#: templates/questions.html:125 -msgid "Related tags" -msgstr "相关标签" - #: templates/authopenid/changeemail.html:10 msgid "Account: change email" msgstr "修改电子邮件" @@ -1449,11 +1445,6 @@ msgstr "有关Markdown详细说明" msgid "All questions" msgstr "所有问题" -#: templates/questions.html:25 -#, fuzzy -msgid "Active" -msgstr "活跃问题" - #: templates/questions.html:26 #, fuzzy msgid "Questions with most answers" @@ -1465,7 +1456,6 @@ msgstr "" # index.html #: templates/questions.html:57 templates/questions.html.py:69 -#, fuzzy msgid "Community wiki" msgstr "社区Wiki" @@ -1739,11 +1729,6 @@ msgstr "这个问题被" msgid "this answer has been accepted to be correct" msgstr "有答案已被接受为正确答案" -#: templates/authopenid/changeemail.html:6 -#, fuzzy -msgid "Account: change email" -msgstr "更换电子邮件" - #: templates/authopenid/changeemail.html:9 msgid "" "This is where you can change the email address associated with your account. " @@ -1947,7 +1932,6 @@ msgid "Give your account a new password." msgstr "" #: templates/authopenid/settings.html:30 -#, fuzzy msgid "Change email " msgstr "更换电子邮件" @@ -1,11 +1,11 @@ -#!/usr/bin/env python
-from django.core.management import execute_manager
-try:
- import settings # Assumed to be in the same directory.
-except ImportError:
- import sys
- sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
- sys.exit(1)
-
-if __name__ == "__main__":
- execute_manager(settings)
+#!/usr/bin/env python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings) diff --git a/settings.py b/settings.py index 985de51a..e2d384c1 100644 --- a/settings.py +++ b/settings.py @@ -1,99 +1,100 @@ -# encoding:utf-8
-# Django settings for lanai project.
-import os.path
-
-#DEBUG SETTINGS
-DEBUG = True
-TEMPLATE_DEBUG = DEBUG
-INTERNAL_IPS = ('127.0.0.1',)
-
-#for OpenID auth
-ugettext = lambda s: s
-LOGIN_URL = '/%s%s' % (ugettext('account/'), ugettext('signin/'))
-
-#EMAIL AND ADMINS
-ADMINS = (
- ('CNProg team', 'team@cnprog.com'),
-)
-MANAGERS = ADMINS
-
-SERVER_EMAIL = 'webmaster@cnprog.com'
-DEFAULT_FROM_EMAIL = 'webmaster@cnprog.com'
-EMAIL_HOST_USER = ''
-EMAIL_HOST_PASSWORD = ''
-EMAIL_SUBJECT_PREFIX = '[cnprog.com]'
-EMAIL_HOST='smtp.gmail.com'
-EMAIL_PORT='587'
-EMAIL_USE_TLS=True
-
-#LOCALIZATIONS
-TIME_ZONE = 'Asia/Chongqing Asia/Chungking'
-# LANGUAGE_CODE = 'en-us'
-LANGUAGE_CODE = 'zh-cn'
-SITE_ID = 1
-USE_I18N = True
-
-#OTHER SETTINS
-APP_TITLE = u'CNProg.com 程序员问答社区'
-APP_URL = 'http://www.cnprog.com'
-APP_KEYWORDS = u'技术问答社区,中国程序员,编程技术社区,程序员社区,程序员论坛,程序员wiki,程序员博客'
-APP_DESCRIPTION = u'中国程序员的编程技术问答社区。我们做专业的、可协作编辑的技术问答社区。'
-APP_INTRO = u' <p>CNProg是一个<strong>面向程序员</strong>的可协作编辑的<strong>开放源代码问答社区</strong>。</p><p> 您可以在这里提问各类<strong>程序技术问题</strong> - 问题不分语言和平台。 同时也希望您对力所能及的问题,给予您的宝贵答案。</p>'
-ADMIN_MEDIA_PREFIX = '/admin/media/'
-SECRET_KEY = '$oo^&_m&qwbib=(_4m_n*zn-d=g#s0he5fx9xonnym#8p6yigm'
-# List of callables that know how to import templates from various sources.
-TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.load_template_source',
- 'django.template.loaders.app_directories.load_template_source',
-# 'django.template.loaders.eggs.load_template_source',
-)
-
-MIDDLEWARE_CLASSES = (
- 'django.middleware.gzip.GZipMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.locale.LocaleMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.middleware.transaction.TransactionMiddleware',
- #'django.middleware.sqlprint.SqlPrintingMiddleware',
- 'middleware.pagesize.QuestionsPageSizeMiddleware',
- #'debug_toolbar.middleware.DebugToolbarMiddleware',
-)
-
-TEMPLATE_CONTEXT_PROCESSORS = (
- 'django.core.context_processors.request',
- 'django.core.context_processors.auth',
- 'context.application_settings'
-)
-
-ROOT_URLCONF = 'urls'
-
-TEMPLATE_DIRS = (
- os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
-)
-
-#UPLOAD SETTINGS
-FILE_UPLOAD_TEMP_DIR = os.path.join(os.path.dirname(__file__), 'tmp').replace('\\','/')
-FILE_UPLOAD_HANDLERS = ("django.core.files.uploadhandler.MemoryFileUploadHandler",
- "django.core.files.uploadhandler.TemporaryFileUploadHandler",)
-DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
-# for user upload
-ALLOW_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
-# unit byte
-ALLOW_MAX_FILE_SIZE = 1024 * 1024
-
-INSTALLED_APPS = (
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- 'django.contrib.admin',
- 'django.contrib.humanize',
- 'forum',
- 'django_authopenid',
- 'debug_toolbar' ,
-)
-
-# User settings
-from settings_local import *
-
+# encoding:utf-8 +# Django settings for lanai project. +import os.path + +#DEBUG SETTINGS +DEBUG = True +TEMPLATE_DEBUG = DEBUG +INTERNAL_IPS = ('127.0.0.1',) + +#for OpenID auth +ugettext = lambda s: s +LOGIN_URL = '/%s%s' % (ugettext('account/'), ugettext('signin/')) + +#EMAIL AND ADMINS +ADMINS = ( + ('CNProg team', 'team@cnprog.com'), +) +MANAGERS = ADMINS + +SERVER_EMAIL = '' +DEFAULT_FROM_EMAIL = '' +EMAIL_HOST_USER = '' +EMAIL_HOST_PASSWORD = '' +EMAIL_SUBJECT_PREFIX = '[cnprog.com]' +EMAIL_HOST='smtp.gmail.com' +EMAIL_PORT='587' +EMAIL_USE_TLS=True + +#LOCALIZATIONS +TIME_ZONE = 'Asia/Chongqing Asia/Chungking' +# LANGUAGE_CODE = 'en-us' +SITE_ID = 1 + +#OTHER SETTINS +APP_TITLE = u'CNProg.com 程序员问答社区' +APP_URL = 'http://www.cnprog.com' +APP_KEYWORDS = u'技术问答社区,中国程序员,编程技术社区,程序员社区,程序员论坛,程序员wiki,程序员博客' +APP_DESCRIPTION = u'中国程序员的编程技术问答社区。我们做专业的、可协作编辑的技术问答社区。' +APP_INTRO = u' <p>CNProg是一个<strong>面向程序员</strong>的可协作编辑的<strong>开放源代码问答社区</strong>。</p><p> 您可以在这里提问各类<strong>程序技术问题</strong> - 问题不分语言和平台。 同时也希望您对力所能及的问题,给予您的宝贵答案。</p>' +ADMIN_MEDIA_PREFIX = '/admin/media/' +SECRET_KEY = '$oo^&_m&qwbib=(_4m_n*zn-d=g#s0he5fx9xonnym#8p6yigm' +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.gzip.GZipMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + #'django.middleware.locale.LocaleMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.middleware.transaction.TransactionMiddleware', + #'django.middleware.sqlprint.SqlPrintingMiddleware', + #'middleware.pagesize.QuestionsPageSizeMiddleware', + #'debug_toolbar.middleware.DebugToolbarMiddleware', +) + +TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.core.context_processors.request', + 'django.core.context_processors.auth', + 'context.application_settings', + #'django.core.context_processors.i18n', +) + +ROOT_URLCONF = 'urls' + +TEMPLATE_DIRS = ( + os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'), +) + +#UPLOAD SETTINGS +FILE_UPLOAD_TEMP_DIR = os.path.join(os.path.dirname(__file__), 'tmp').replace('\\','/') +FILE_UPLOAD_HANDLERS = ("django.core.files.uploadhandler.MemoryFileUploadHandler", + "django.core.files.uploadhandler.TemporaryFileUploadHandler",) +DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' +# for user upload +ALLOW_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff') +# unit byte +ALLOW_MAX_FILE_SIZE = 1024 * 1024 + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.admin', + 'django.contrib.humanize', + 'forum', + 'django_authopenid', + 'debug_toolbar' , +) + +# User settings +from settings_local import * + +USE_I18N = True +LANGUAGE_CODE = 'en' diff --git a/templates/about.html b/templates/about.html index 4655a641..eaf0d591 100644 --- a/templates/about.html +++ b/templates/about.html @@ -1,5 +1,5 @@ -<!-- template about.html --> {% extends "base_content.html" %} +<!-- template about.html --> {% load i18n %} {% load extra_tags %} {% load humanize %} diff --git a/templates/authopenid/signin.html b/templates/authopenid/signin.html index aff2f06f..d8a98329 100644 --- a/templates/authopenid/signin.html +++ b/templates/authopenid/signin.html @@ -18,7 +18,7 @@ <div class="login"> <form name="openid_form" action="{% url user_signin %}" method="post"> {{ form2.next }} - <p style="display:none">{% trans "we support two login modes" %}</p> + <p>{% trans "we support two login modes" %}</p> {% if msg %} <p class="warning">{{ msg }}</p> {% endif %} @@ -36,7 +36,7 @@ </p> </fieldset> </form> - <div style="display:none"> + <div> <br> {% if form1.errors %} <p class="errors"> diff --git a/templates/authopenid/signup.html b/templates/authopenid/signup.html index a4460aa3..9ac0aab3 100644 --- a/templates/authopenid/signup.html +++ b/templates/authopenid/signup.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load i18n %} {% block title %}{% spaceless %}{% trans "Signup" %}{% endspaceless %}{% endblock %} {% block content %} diff --git a/templates/content/js/com.cnprog.editor.js b/templates/content/js/com.cnprog.editor.js index 289d9866..6cfa2c74 100644 --- a/templates/content/js/com.cnprog.editor.js +++ b/templates/content/js/com.cnprog.editor.js @@ -1,68 +1,68 @@ -/*
- jQuery TextAreaResizer plugin
- Created on 17th January 2008 by Ryan O'Dell
- Version 1.0.4
-*/(function($){var textarea,staticOffset;var iLastMousePos=0;var iMin=32;var grip;$.fn.TextAreaResizer=function(){return this.each(function(){textarea=$(this).addClass('processed'),staticOffset=null;$(this).wrap('<div class="resizable-textarea"><span></span></div>').parent().append($('<div class="grippie"></div>').bind("mousedown",{el:this},startDrag));var grippie=$('div.grippie',$(this).parent())[0];grippie.style.marginRight=(grippie.offsetWidth-$(this)[0].offsetWidth)+'px'})};function startDrag(e){textarea=$(e.data.el);textarea.blur();iLastMousePos=mousePosition(e).y;staticOffset=textarea.height()-iLastMousePos;textarea.css('opacity',0.25);$(document).mousemove(performDrag).mouseup(endDrag);return false}function performDrag(e){var iThisMousePos=mousePosition(e).y;var iMousePos=staticOffset+iThisMousePos;if(iLastMousePos>=(iThisMousePos)){iMousePos-=5}iLastMousePos=iThisMousePos;iMousePos=Math.max(iMin,iMousePos);textarea.height(iMousePos+'px');if(iMousePos<iMin){endDrag(e)}return false}function endDrag(e){$(document).unbind('mousemove',performDrag).unbind('mouseup',endDrag);textarea.css('opacity',1);textarea.focus();textarea=null;staticOffset=null;iLastMousePos=0}function mousePosition(e){return{x:e.clientX+document.documentElement.scrollLeft,y:e.clientY+document.documentElement.scrollTop}}})(jQuery);
-/*
- * Autocomplete - jQuery plugin 1.0.2
- * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
- */;(function($){$.fn.extend({autocomplete:function(urlOrData,options){var isUrl=typeof urlOrData=="string";options=$.extend({},$.Autocompleter.defaults,{url:isUrl?urlOrData:null,data:isUrl?null:urlOrData,delay:isUrl?$.Autocompleter.defaults.delay:10,max:options&&!options.scroll?10:150},options);options.highlight=options.highlight||function(value){return value;};options.formatMatch=options.formatMatch||options.formatItem;return this.each(function(){new $.Autocompleter(this,options);});},result:function(handler){return this.bind("result",handler);},search:function(handler){return this.trigger("search",[handler]);},flushCache:function(){return this.trigger("flushCache");},setOptions:function(options){return this.trigger("setOptions",[options]);},unautocomplete:function(){return this.trigger("unautocomplete");}});$.Autocompleter=function(input,options){var KEY={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8};var $input=$(input).attr("autocomplete","off").addClass(options.inputClass);var timeout;var previousValue="";var cache=$.Autocompleter.Cache(options);var hasFocus=0;var lastKeyPressCode;var config={mouseDownOnSelect:false};var select=$.Autocompleter.Select(options,input,selectCurrent,config);var blockSubmit;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){lastKeyPressCode=event.keyCode;switch(event.keyCode){case KEY.UP:event.preventDefault();if(select.visible()){select.prev();}else{onChange(0,true);}break;case KEY.DOWN:event.preventDefault();if(select.visible()){select.next();}else{onChange(0,true);}break;case KEY.PAGEUP:event.preventDefault();if(select.visible()){select.pageUp();}else{onChange(0,true);}break;case KEY.PAGEDOWN:event.preventDefault();if(select.visible()){select.pageDown();}else{onChange(0,true);}break;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;}break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){var fn=(arguments.length>1)?arguments[1]:null;function findValueCallback(q,data){var result;if(data&&data.length){for(var i=0;i<data.length;i++){if(data[i].result.toLowerCase()==q.toLowerCase()){result=data[i];break;}}}if(typeof fn=="function")fn(result);else $input.trigger("result",result&&[result.data,result.value]);}$.each(trimWords($input.val()),function(i,value){request(value,findValueCallback,findValueCallback);});}).bind("flushCache",function(){cache.flush();}).bind("setOptions",function(){$.extend(options,arguments[1]);if("data"in arguments[1])cache.populate();}).bind("unautocomplete",function(){select.unbind();$input.unbind();$(input.form).unbind(".autocomplete");});function selectCurrent(){var selected=select.selected();if(!selected)return false;var v=selected.result;previousValue=v;if(options.multiple){var words=trimWords($input.val());if(words.length>1){v=words.slice(0,words.length-1).join(options.multipleSeparator)+options.multipleSeparator+v;}v+=options.multipleSeparator;}$input.val(v);hideResultsNow();$input.trigger("result",[selected.data,selected.value]);return true;}function onChange(crap,skipPrevCheck){if(lastKeyPressCode==KEY.DEL){select.hide();return;}var currentValue=$input.val();if(!skipPrevCheck&¤tValue==previousValue)return;previousValue=currentValue;currentValue=lastWord(currentValue);if(currentValue.length>=options.minChars){$input.addClass(options.loadingClass);if(!options.matchCase)currentValue=currentValue.toLowerCase();request(currentValue,receiveData,hideResultsNow);}else{stopLoading();select.hide();}};function trimWords(value){if(!value){return[""];}var words=value.split(options.multipleSeparator);var result=[];$.each(words,function(i,value){if($.trim(value))result[i]=$.trim(value);});return result;}function lastWord(value){if(!options.multiple)return value;var words=trimWords(value);return words[words.length-1];}function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$.Autocompleter.Selection(input,previousValue.length,previousValue.length+sValue.length);}};function hideResults(){clearTimeout(timeout);timeout=setTimeout(hideResultsNow,200);};function hideResultsNow(){var wasVisible=select.visible();select.hide();clearTimeout(timeout);stopLoading();if(options.mustMatch){$input.search(function(result){if(!result){if(options.multiple){var words=trimWords($input.val()).slice(0,-1);$input.val(words.join(options.multipleSeparator)+(words.length?options.multipleSeparator:""));}else
-$input.val("");}});}if(wasVisible)$.Autocompleter.Selection(input,input.value.length,input.value.length);};function receiveData(q,data){if(data&&data.length&&hasFocus){stopLoading();select.display(data,q);autoFill(q,data[0].value);select.show();}else{hideResultsNow();}};function request(term,success,failure){if(!options.matchCase)term=term.toLowerCase();var data=cache.load(term);if(data&&data.length){success(term,data);}else if((typeof options.url=="string")&&(options.url.length>0)){var extraParams={timestamp:+new Date()};$.each(options.extraParams,function(key,param){extraParams[key]=typeof param=="function"?param():param;});$.ajax({mode:"abort",port:"autocomplete"+input.name,dataType:options.dataType,url:options.url,data:$.extend({q:lastWord(term),limit:options.max},extraParams),success:function(data){var parsed=options.parse&&options.parse(data)||parse(data);cache.add(term,parsed);success(term,parsed);}});}else{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i<rows.length;i++){var row=$.trim(rows[i]);if(row){row=row.split("|");parsed[parsed.length]={data:row,value:row[0],result:options.formatResult&&options.formatResult(row,row[0])||row[0]};}}return parsed;};function stopLoading(){$input.removeClass(options.loadingClass);};};$.Autocompleter.defaults={inputClass:"ac_input",resultsClass:"ac_results",loadingClass:"ac_loading",minChars:1,delay:400,matchCase:false,matchSubset:true,matchContains:false,cacheLength:10,max:100,mustMatch:false,extraParams:{},selectFirst:true,formatItem:function(row){return row[0];},formatMatch:null,autoFill:false,width:0,multiple:false,multipleSeparator:", ",highlight:function(value,term){return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"<strong>$1</strong>");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase)s=s.toLowerCase();var i=s.indexOf(sub);if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();}if(!data[q]){length++;}data[q]=value;}function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i<ol;i++){var rawValue=options.data[i];rawValue=(typeof rawValue=="string")?[rawValue]:rawValue;var value=options.formatMatch(rawValue,i+1,options.data.length);if(value===false)continue;var firstChar=value.charAt(0).toLowerCase();if(!stMatchSets[firstChar])stMatchSets[firstChar]=[];var row={value:value,data:rawValue,result:options.formatResult&&options.formatResult(rawValue)||value};stMatchSets[firstChar].push(row);if(nullData++<options.max){stMatchSets[""].push(row);}};$.each(stMatchSets,function(i,value){options.cacheLength++;add(i,value);});}setTimeout(populate,25);function flush(){data={};length=0;}return{flush:flush,add:add,populate:populate,load:function(q){if(!options.cacheLength||!length)return null;if(!options.url&&options.matchContains){var csub=[];for(var k in data){if(k.length>0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}}return csub;}else
-if(data[q]){return data[q];}else
-if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}}return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit)return;element=$("<div/>").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("<ul/>").appendTo(element).mouseover(function(event){if(target(event).nodeName&&target(event).nodeName.toUpperCase()=='LI'){active=$("li",list).removeClass(CLASSES.ACTIVE).index(target(event));$(target(event)).addClass(CLASSES.ACTIVE);}}).click(function(event){$(target(event)).addClass(CLASSES.ACTIVE);select();input.focus();return false;}).mousedown(function(){config.mouseDownOnSelect=true;}).mouseup(function(){config.mouseDownOnSelect=false;});if(options.width>0)element.css("width",options.width);needsInit=false;}function target(event){var element=event.target;while(element&&element.tagName!="LI")element=element.parentNode;if(!element)return[];return element;}function moveSelect(step){listItems.slice(active,active+1).removeClass(CLASSES.ACTIVE);movePosition(step);var activeItem=listItems.slice(active,active+1).addClass(CLASSES.ACTIVE);if(options.scroll){var offset=0;listItems.slice(0,active).each(function(){offset+=this.offsetHeight;});if((offset+activeItem[0].offsetHeight-list.scrollTop())>list[0].clientHeight){list.scrollTop(offset+activeItem[0].offsetHeight-list.innerHeight());}else if(offset<list.scrollTop()){list.scrollTop(offset);}}};function movePosition(step){active+=step;if(active<0){active=listItems.size()-1;}else if(active>=listItems.size()){active=0;}}function limitNumberOfItems(available){return options.max&&options.max<available?options.max:available;}function fillList(){list.empty();var max=limitNumberOfItems(data.length);for(var i=0;i<max;i++){if(!data[i])continue;var formatted=options.formatItem(data[i].data,i+1,max,data[i].value,term);if(formatted===false)continue;var li=$("<li/>").html(options.highlight(formatted,term)).addClass(i%2==0?"ac_even":"ac_odd").appendTo(list)[0];$.data(li,"ac_data",data[i]);}listItems=list.find("li");if(options.selectFirst){listItems.slice(0,1).addClass(CLASSES.ACTIVE);active=0;}if($.fn.bgiframe)list.bgiframe();}return{display:function(d,q){init();data=d;term=q;fillList();},next:function(){moveSelect(1);},prev:function(){moveSelect(-1);},pageUp:function(){if(active!=0&&active-8<0){moveSelect(-active);}else{moveSelect(-8);}},pageDown:function(){if(active!=listItems.size()-1&&active+8>listItems.size()){moveSelect(listItems.size()-1-active);}else{moveSelect(8);}},hide:function(){element&&element.hide();listItems&&listItems.removeClass(CLASSES.ACTIVE);active=-1;},visible:function(){return element&&element.is(":visible");},current:function(){return this.visible()&&(listItems.filter("."+CLASSES.ACTIVE)[0]||options.selectFirst&&listItems[0]);},show:function(){var offset=$(input).offset();element.css({width:typeof options.width=="string"||options.width>0?options.width:$(input).width(),top:offset.top+input.offsetHeight,left:offset.left}).show();if(options.scroll){list.scrollTop(0);list.css({maxHeight:options.scrollHeight,overflow:'auto'});if($.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var listHeight=0;listItems.each(function(){listHeight+=this.offsetHeight;});var scrollbarsVisible=listHeight>options.scrollHeight;list.css('height',scrollbarsVisible?options.scrollHeight:listHeight);if(!scrollbarsVisible){listItems.width(list.width()-parseInt(listItems.css("padding-left"))-parseInt(listItems.css("padding-right")));}}}},selected:function(){var selected=listItems&&listItems.filter("."+CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);return selected&&selected.length&&$.data(selected[0],"ac_data");},emptyList:function(){list&&list.empty();},unbind:function(){element&&element.remove();}};};$.Autocompleter.Selection=function(field,start,end){if(field.createTextRange){var selRange=field.createTextRange();selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}else if(field.setSelectionRange){field.setSelectionRange(start,end);}else{if(field.selectionStart){field.selectionStart=start;field.selectionEnd=end;}}field.focus();};})(jQuery);
-/*
- * TypeWatch 2.0 - Original by Denny Ferrassoli / Refactored by Charles Christolini
- * Copyright(c) 2007 Denny Ferrassoli - DennyDotNet.com
- * Coprright(c) 2008 Charles Christolini - BinaryPie.com
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
-*/(function(jQuery){jQuery.fn.typeWatch=function(o){var options=jQuery.extend({wait:750,callback:function(){},highlight:true,captureLength:2},o);function checkElement(timer,override){var elTxt=jQuery(timer.el).val();if((elTxt.length>options.captureLength&&elTxt.toUpperCase()!=timer.text)||(override&&elTxt.length>options.captureLength)){timer.text=elTxt.toUpperCase();timer.cb(elTxt)}};function watchElement(elem){if(elem.type.toUpperCase()=="TEXT"||elem.nodeName.toUpperCase()=="TEXTAREA"){var timer={timer:null,text:jQuery(elem).val().toUpperCase(),cb:options.callback,el:elem,wait:options.wait};if(options.highlight){jQuery(elem).focus(function(){this.select()})}var startWatch=function(evt){var timerWait=timer.wait;var overrideBool=false;if(evt.keyCode==13&&this.type.toUpperCase()=="TEXT"){timerWait=1;overrideBool=true}var timerCallbackFx=function(){checkElement(timer,overrideBool)};clearTimeout(timer.timer);timer.timer=setTimeout(timerCallbackFx,timerWait)};jQuery(elem).keydown(startWatch)}};return this.each(function(index){watchElement(this)})}})(jQuery);
-/*
-Ajax upload
-*/jQuery.extend({createUploadIframe:function(d,b){var a="jUploadFrame"+d;if(window.ActiveXObject){var c=document.createElement('<iframe id="'+a+'" name="'+a+'" />');if(typeof b=="boolean"){c.src="javascript:false"}else{if(typeof b=="string"){c.src=b}}}else{var c=document.createElement("iframe");c.id=a;c.name=a}c.style.position="absolute";c.style.top="-1000px";c.style.left="-1000px";document.body.appendChild(c);return c},createUploadForm:function(g,b){var e="jUploadForm"+g;var a="jUploadFile"+g;var d=$('<form action="" method="POST" name="'+e+'" id="'+e+'" enctype="multipart/form-data"></form>');var c=$("#"+b);var f=$(c).clone();$(c).attr("id",a);$(c).before(f);$(c).appendTo(d);$(d).css("position","absolute");$(d).css("top","-1200px");$(d).css("left","-1200px");$(d).appendTo("body");return d},ajaxFileUpload:function(k){k=jQuery.extend({},jQuery.ajaxSettings,k);var a=new Date().getTime();var b=jQuery.createUploadForm(a,k.fileElementId);var i=jQuery.createUploadIframe(a,k.secureuri);var h="jUploadFrame"+a;var j="jUploadForm"+a;if(k.global&&!jQuery.active++){jQuery.event.trigger("ajaxStart")}var c=false;var f={};if(k.global){jQuery.event.trigger("ajaxSend",[f,k])}var d=function(l){var p=document.getElementById(h);try{if(p.contentWindow){f.responseText=p.contentWindow.document.body?p.contentWindow.document.body.innerText:null;f.responseXML=p.contentWindow.document.XMLDocument?p.contentWindow.document.XMLDocument:p.contentWindow.document}else{if(p.contentDocument){f.responseText=p.contentDocument.document.body?p.contentDocument.document.body.textContent||document.body.innerText:null;f.responseXML=p.contentDocument.document.XMLDocument?p.contentDocument.document.XMLDocument:p.contentDocument.document}}}catch(o){jQuery.handleError(k,f,null,o)}if(f||l=="timeout"){c=true;var m;try{m=l!="timeout"?"success":"error";if(m!="error"){var n=jQuery.uploadHttpData(f,k.dataType);if(k.success){k.success(n,m)}if(k.global){jQuery.event.trigger("ajaxSuccess",[f,k])}}else{jQuery.handleError(k,f,m)}}catch(o){m="error";jQuery.handleError(k,f,m,o)}if(k.global){jQuery.event.trigger("ajaxComplete",[f,k])}if(k.global&&!--jQuery.active){jQuery.event.trigger("ajaxStop")}if(k.complete){k.complete(f,m)}jQuery(p).unbind();setTimeout(function(){try{$(p).remove();$(b).remove()}catch(q){jQuery.handleError(k,f,null,q)}},100);f=null}};if(k.timeout>0){setTimeout(function(){if(!c){d("timeout")}},k.timeout)}try{var b=$("#"+j);$(b).attr("action",k.url);$(b).attr("method","POST");$(b).attr("target",h);if(b.encoding){b.encoding="multipart/form-data"}else{b.enctype="multipart/form-data"}$(b).submit()}catch(g){jQuery.handleError(k,f,null,g)}if(window.attachEvent){document.getElementById(h).attachEvent("onload",d)}else{document.getElementById(h).addEventListener("load",d,false)}return{abort:function(){}}},uploadHttpData:function(r,type){var data=!type;data=type=="xml"||data?r.responseXML:r.responseText;if(type=="script"){jQuery.globalEval(data)}if(type=="json"){eval("data = "+data)}if(type=="html"){jQuery("<div>").html(data).evalScripts()}return data}});
-/*Upload call*/
-function ajaxFileUpload(imageUrl)
-{
- $("#loading").ajaxStart(function(){
- $(this).show();
- }).ajaxComplete(function(){
- $(this).hide();
- });
-
- $("#upload").ajaxStart(function(){
- $(this).hide();
- }).ajaxComplete(function(){
- $(this).show();
- });
-
- $.ajaxFileUpload
- (
- {
- url:'/upload/',
- secureuri:false,
- fileElementId:'file-upload',
- dataType: 'xml',
- success: function (data, status)
- {
- var fileURL = $(data).find('file_url').text();
- var error = $(data).find('error').text();
- if(error != ''){
- alert(error);
- }else{
- imageUrl.attr('value', fileURL);
- }
-
- },
- error: function (data, status, e)
- {
- alert(e);
- }
- }
- )
-
- return false;
+/* + jQuery TextAreaResizer plugin + Created on 17th January 2008 by Ryan O'Dell + Version 1.0.4 +*/(function($){var textarea,staticOffset;var iLastMousePos=0;var iMin=32;var grip;$.fn.TextAreaResizer=function(){return this.each(function(){textarea=$(this).addClass('processed'),staticOffset=null;$(this).wrap('<div class="resizable-textarea"><span></span></div>').parent().append($('<div class="grippie"></div>').bind("mousedown",{el:this},startDrag));var grippie=$('div.grippie',$(this).parent())[0];grippie.style.marginRight=(grippie.offsetWidth-$(this)[0].offsetWidth)+'px'})};function startDrag(e){textarea=$(e.data.el);textarea.blur();iLastMousePos=mousePosition(e).y;staticOffset=textarea.height()-iLastMousePos;textarea.css('opacity',0.25);$(document).mousemove(performDrag).mouseup(endDrag);return false}function performDrag(e){var iThisMousePos=mousePosition(e).y;var iMousePos=staticOffset+iThisMousePos;if(iLastMousePos>=(iThisMousePos)){iMousePos-=5}iLastMousePos=iThisMousePos;iMousePos=Math.max(iMin,iMousePos);textarea.height(iMousePos+'px');if(iMousePos<iMin){endDrag(e)}return false}function endDrag(e){$(document).unbind('mousemove',performDrag).unbind('mouseup',endDrag);textarea.css('opacity',1);textarea.focus();textarea=null;staticOffset=null;iLastMousePos=0}function mousePosition(e){return{x:e.clientX+document.documentElement.scrollLeft,y:e.clientY+document.documentElement.scrollTop}}})(jQuery); +/* + * Autocomplete - jQuery plugin 1.0.2 + * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */;(function($){$.fn.extend({autocomplete:function(urlOrData,options){var isUrl=typeof urlOrData=="string";options=$.extend({},$.Autocompleter.defaults,{url:isUrl?urlOrData:null,data:isUrl?null:urlOrData,delay:isUrl?$.Autocompleter.defaults.delay:10,max:options&&!options.scroll?10:150},options);options.highlight=options.highlight||function(value){return value;};options.formatMatch=options.formatMatch||options.formatItem;return this.each(function(){new $.Autocompleter(this,options);});},result:function(handler){return this.bind("result",handler);},search:function(handler){return this.trigger("search",[handler]);},flushCache:function(){return this.trigger("flushCache");},setOptions:function(options){return this.trigger("setOptions",[options]);},unautocomplete:function(){return this.trigger("unautocomplete");}});$.Autocompleter=function(input,options){var KEY={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8};var $input=$(input).attr("autocomplete","off").addClass(options.inputClass);var timeout;var previousValue="";var cache=$.Autocompleter.Cache(options);var hasFocus=0;var lastKeyPressCode;var config={mouseDownOnSelect:false};var select=$.Autocompleter.Select(options,input,selectCurrent,config);var blockSubmit;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){lastKeyPressCode=event.keyCode;switch(event.keyCode){case KEY.UP:event.preventDefault();if(select.visible()){select.prev();}else{onChange(0,true);}break;case KEY.DOWN:event.preventDefault();if(select.visible()){select.next();}else{onChange(0,true);}break;case KEY.PAGEUP:event.preventDefault();if(select.visible()){select.pageUp();}else{onChange(0,true);}break;case KEY.PAGEDOWN:event.preventDefault();if(select.visible()){select.pageDown();}else{onChange(0,true);}break;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;}break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){var fn=(arguments.length>1)?arguments[1]:null;function findValueCallback(q,data){var result;if(data&&data.length){for(var i=0;i<data.length;i++){if(data[i].result.toLowerCase()==q.toLowerCase()){result=data[i];break;}}}if(typeof fn=="function")fn(result);else $input.trigger("result",result&&[result.data,result.value]);}$.each(trimWords($input.val()),function(i,value){request(value,findValueCallback,findValueCallback);});}).bind("flushCache",function(){cache.flush();}).bind("setOptions",function(){$.extend(options,arguments[1]);if("data"in arguments[1])cache.populate();}).bind("unautocomplete",function(){select.unbind();$input.unbind();$(input.form).unbind(".autocomplete");});function selectCurrent(){var selected=select.selected();if(!selected)return false;var v=selected.result;previousValue=v;if(options.multiple){var words=trimWords($input.val());if(words.length>1){v=words.slice(0,words.length-1).join(options.multipleSeparator)+options.multipleSeparator+v;}v+=options.multipleSeparator;}$input.val(v);hideResultsNow();$input.trigger("result",[selected.data,selected.value]);return true;}function onChange(crap,skipPrevCheck){if(lastKeyPressCode==KEY.DEL){select.hide();return;}var currentValue=$input.val();if(!skipPrevCheck&¤tValue==previousValue)return;previousValue=currentValue;currentValue=lastWord(currentValue);if(currentValue.length>=options.minChars){$input.addClass(options.loadingClass);if(!options.matchCase)currentValue=currentValue.toLowerCase();request(currentValue,receiveData,hideResultsNow);}else{stopLoading();select.hide();}};function trimWords(value){if(!value){return[""];}var words=value.split(options.multipleSeparator);var result=[];$.each(words,function(i,value){if($.trim(value))result[i]=$.trim(value);});return result;}function lastWord(value){if(!options.multiple)return value;var words=trimWords(value);return words[words.length-1];}function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$.Autocompleter.Selection(input,previousValue.length,previousValue.length+sValue.length);}};function hideResults(){clearTimeout(timeout);timeout=setTimeout(hideResultsNow,200);};function hideResultsNow(){var wasVisible=select.visible();select.hide();clearTimeout(timeout);stopLoading();if(options.mustMatch){$input.search(function(result){if(!result){if(options.multiple){var words=trimWords($input.val()).slice(0,-1);$input.val(words.join(options.multipleSeparator)+(words.length?options.multipleSeparator:""));}else +$input.val("");}});}if(wasVisible)$.Autocompleter.Selection(input,input.value.length,input.value.length);};function receiveData(q,data){if(data&&data.length&&hasFocus){stopLoading();select.display(data,q);autoFill(q,data[0].value);select.show();}else{hideResultsNow();}};function request(term,success,failure){if(!options.matchCase)term=term.toLowerCase();var data=cache.load(term);if(data&&data.length){success(term,data);}else if((typeof options.url=="string")&&(options.url.length>0)){var extraParams={timestamp:+new Date()};$.each(options.extraParams,function(key,param){extraParams[key]=typeof param=="function"?param():param;});$.ajax({mode:"abort",port:"autocomplete"+input.name,dataType:options.dataType,url:options.url,data:$.extend({q:lastWord(term),limit:options.max},extraParams),success:function(data){var parsed=options.parse&&options.parse(data)||parse(data);cache.add(term,parsed);success(term,parsed);}});}else{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i<rows.length;i++){var row=$.trim(rows[i]);if(row){row=row.split("|");parsed[parsed.length]={data:row,value:row[0],result:options.formatResult&&options.formatResult(row,row[0])||row[0]};}}return parsed;};function stopLoading(){$input.removeClass(options.loadingClass);};};$.Autocompleter.defaults={inputClass:"ac_input",resultsClass:"ac_results",loadingClass:"ac_loading",minChars:1,delay:400,matchCase:false,matchSubset:true,matchContains:false,cacheLength:10,max:100,mustMatch:false,extraParams:{},selectFirst:true,formatItem:function(row){return row[0];},formatMatch:null,autoFill:false,width:0,multiple:false,multipleSeparator:", ",highlight:function(value,term){return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"<strong>$1</strong>");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase)s=s.toLowerCase();var i=s.indexOf(sub);if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();}if(!data[q]){length++;}data[q]=value;}function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i<ol;i++){var rawValue=options.data[i];rawValue=(typeof rawValue=="string")?[rawValue]:rawValue;var value=options.formatMatch(rawValue,i+1,options.data.length);if(value===false)continue;var firstChar=value.charAt(0).toLowerCase();if(!stMatchSets[firstChar])stMatchSets[firstChar]=[];var row={value:value,data:rawValue,result:options.formatResult&&options.formatResult(rawValue)||value};stMatchSets[firstChar].push(row);if(nullData++<options.max){stMatchSets[""].push(row);}};$.each(stMatchSets,function(i,value){options.cacheLength++;add(i,value);});}setTimeout(populate,25);function flush(){data={};length=0;}return{flush:flush,add:add,populate:populate,load:function(q){if(!options.cacheLength||!length)return null;if(!options.url&&options.matchContains){var csub=[];for(var k in data){if(k.length>0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}}return csub;}else +if(data[q]){return data[q];}else +if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}}return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit)return;element=$("<div/>").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("<ul/>").appendTo(element).mouseover(function(event){if(target(event).nodeName&&target(event).nodeName.toUpperCase()=='LI'){active=$("li",list).removeClass(CLASSES.ACTIVE).index(target(event));$(target(event)).addClass(CLASSES.ACTIVE);}}).click(function(event){$(target(event)).addClass(CLASSES.ACTIVE);select();input.focus();return false;}).mousedown(function(){config.mouseDownOnSelect=true;}).mouseup(function(){config.mouseDownOnSelect=false;});if(options.width>0)element.css("width",options.width);needsInit=false;}function target(event){var element=event.target;while(element&&element.tagName!="LI")element=element.parentNode;if(!element)return[];return element;}function moveSelect(step){listItems.slice(active,active+1).removeClass(CLASSES.ACTIVE);movePosition(step);var activeItem=listItems.slice(active,active+1).addClass(CLASSES.ACTIVE);if(options.scroll){var offset=0;listItems.slice(0,active).each(function(){offset+=this.offsetHeight;});if((offset+activeItem[0].offsetHeight-list.scrollTop())>list[0].clientHeight){list.scrollTop(offset+activeItem[0].offsetHeight-list.innerHeight());}else if(offset<list.scrollTop()){list.scrollTop(offset);}}};function movePosition(step){active+=step;if(active<0){active=listItems.size()-1;}else if(active>=listItems.size()){active=0;}}function limitNumberOfItems(available){return options.max&&options.max<available?options.max:available;}function fillList(){list.empty();var max=limitNumberOfItems(data.length);for(var i=0;i<max;i++){if(!data[i])continue;var formatted=options.formatItem(data[i].data,i+1,max,data[i].value,term);if(formatted===false)continue;var li=$("<li/>").html(options.highlight(formatted,term)).addClass(i%2==0?"ac_even":"ac_odd").appendTo(list)[0];$.data(li,"ac_data",data[i]);}listItems=list.find("li");if(options.selectFirst){listItems.slice(0,1).addClass(CLASSES.ACTIVE);active=0;}if($.fn.bgiframe)list.bgiframe();}return{display:function(d,q){init();data=d;term=q;fillList();},next:function(){moveSelect(1);},prev:function(){moveSelect(-1);},pageUp:function(){if(active!=0&&active-8<0){moveSelect(-active);}else{moveSelect(-8);}},pageDown:function(){if(active!=listItems.size()-1&&active+8>listItems.size()){moveSelect(listItems.size()-1-active);}else{moveSelect(8);}},hide:function(){element&&element.hide();listItems&&listItems.removeClass(CLASSES.ACTIVE);active=-1;},visible:function(){return element&&element.is(":visible");},current:function(){return this.visible()&&(listItems.filter("."+CLASSES.ACTIVE)[0]||options.selectFirst&&listItems[0]);},show:function(){var offset=$(input).offset();element.css({width:typeof options.width=="string"||options.width>0?options.width:$(input).width(),top:offset.top+input.offsetHeight,left:offset.left}).show();if(options.scroll){list.scrollTop(0);list.css({maxHeight:options.scrollHeight,overflow:'auto'});if($.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var listHeight=0;listItems.each(function(){listHeight+=this.offsetHeight;});var scrollbarsVisible=listHeight>options.scrollHeight;list.css('height',scrollbarsVisible?options.scrollHeight:listHeight);if(!scrollbarsVisible){listItems.width(list.width()-parseInt(listItems.css("padding-left"))-parseInt(listItems.css("padding-right")));}}}},selected:function(){var selected=listItems&&listItems.filter("."+CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);return selected&&selected.length&&$.data(selected[0],"ac_data");},emptyList:function(){list&&list.empty();},unbind:function(){element&&element.remove();}};};$.Autocompleter.Selection=function(field,start,end){if(field.createTextRange){var selRange=field.createTextRange();selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}else if(field.setSelectionRange){field.setSelectionRange(start,end);}else{if(field.selectionStart){field.selectionStart=start;field.selectionEnd=end;}}field.focus();};})(jQuery); +/* + * TypeWatch 2.0 - Original by Denny Ferrassoli / Refactored by Charles Christolini + * Copyright(c) 2007 Denny Ferrassoli - DennyDotNet.com + * Coprright(c) 2008 Charles Christolini - BinaryPie.com + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html +*/(function(jQuery){jQuery.fn.typeWatch=function(o){var options=jQuery.extend({wait:750,callback:function(){},highlight:true,captureLength:2},o);function checkElement(timer,override){var elTxt=jQuery(timer.el).val();if((elTxt.length>options.captureLength&&elTxt.toUpperCase()!=timer.text)||(override&&elTxt.length>options.captureLength)){timer.text=elTxt.toUpperCase();timer.cb(elTxt)}};function watchElement(elem){if(elem.type.toUpperCase()=="TEXT"||elem.nodeName.toUpperCase()=="TEXTAREA"){var timer={timer:null,text:jQuery(elem).val().toUpperCase(),cb:options.callback,el:elem,wait:options.wait};if(options.highlight){jQuery(elem).focus(function(){this.select()})}var startWatch=function(evt){var timerWait=timer.wait;var overrideBool=false;if(evt.keyCode==13&&this.type.toUpperCase()=="TEXT"){timerWait=1;overrideBool=true}var timerCallbackFx=function(){checkElement(timer,overrideBool)};clearTimeout(timer.timer);timer.timer=setTimeout(timerCallbackFx,timerWait)};jQuery(elem).keydown(startWatch)}};return this.each(function(index){watchElement(this)})}})(jQuery); +/* +Ajax upload +*/jQuery.extend({createUploadIframe:function(d,b){var a="jUploadFrame"+d;if(window.ActiveXObject){var c=document.createElement('<iframe id="'+a+'" name="'+a+'" />');if(typeof b=="boolean"){c.src="javascript:false"}else{if(typeof b=="string"){c.src=b}}}else{var c=document.createElement("iframe");c.id=a;c.name=a}c.style.position="absolute";c.style.top="-1000px";c.style.left="-1000px";document.body.appendChild(c);return c},createUploadForm:function(g,b){var e="jUploadForm"+g;var a="jUploadFile"+g;var d=$('<form action="" method="POST" name="'+e+'" id="'+e+'" enctype="multipart/form-data"></form>');var c=$("#"+b);var f=$(c).clone();$(c).attr("id",a);$(c).before(f);$(c).appendTo(d);$(d).css("position","absolute");$(d).css("top","-1200px");$(d).css("left","-1200px");$(d).appendTo("body");return d},ajaxFileUpload:function(k){k=jQuery.extend({},jQuery.ajaxSettings,k);var a=new Date().getTime();var b=jQuery.createUploadForm(a,k.fileElementId);var i=jQuery.createUploadIframe(a,k.secureuri);var h="jUploadFrame"+a;var j="jUploadForm"+a;if(k.global&&!jQuery.active++){jQuery.event.trigger("ajaxStart")}var c=false;var f={};if(k.global){jQuery.event.trigger("ajaxSend",[f,k])}var d=function(l){var p=document.getElementById(h);try{if(p.contentWindow){f.responseText=p.contentWindow.document.body?p.contentWindow.document.body.innerText:null;f.responseXML=p.contentWindow.document.XMLDocument?p.contentWindow.document.XMLDocument:p.contentWindow.document}else{if(p.contentDocument){f.responseText=p.contentDocument.document.body?p.contentDocument.document.body.textContent||document.body.innerText:null;f.responseXML=p.contentDocument.document.XMLDocument?p.contentDocument.document.XMLDocument:p.contentDocument.document}}}catch(o){jQuery.handleError(k,f,null,o)}if(f||l=="timeout"){c=true;var m;try{m=l!="timeout"?"success":"error";if(m!="error"){var n=jQuery.uploadHttpData(f,k.dataType);if(k.success){k.success(n,m)}if(k.global){jQuery.event.trigger("ajaxSuccess",[f,k])}}else{jQuery.handleError(k,f,m)}}catch(o){m="error";jQuery.handleError(k,f,m,o)}if(k.global){jQuery.event.trigger("ajaxComplete",[f,k])}if(k.global&&!--jQuery.active){jQuery.event.trigger("ajaxStop")}if(k.complete){k.complete(f,m)}jQuery(p).unbind();setTimeout(function(){try{$(p).remove();$(b).remove()}catch(q){jQuery.handleError(k,f,null,q)}},100);f=null}};if(k.timeout>0){setTimeout(function(){if(!c){d("timeout")}},k.timeout)}try{var b=$("#"+j);$(b).attr("action",k.url);$(b).attr("method","POST");$(b).attr("target",h);if(b.encoding){b.encoding="multipart/form-data"}else{b.enctype="multipart/form-data"}$(b).submit()}catch(g){jQuery.handleError(k,f,null,g)}if(window.attachEvent){document.getElementById(h).attachEvent("onload",d)}else{document.getElementById(h).addEventListener("load",d,false)}return{abort:function(){}}},uploadHttpData:function(r,type){var data=!type;data=type=="xml"||data?r.responseXML:r.responseText;if(type=="script"){jQuery.globalEval(data)}if(type=="json"){eval("data = "+data)}if(type=="html"){jQuery("<div>").html(data).evalScripts()}return data}}); +/*Upload call*/ +function ajaxFileUpload(imageUrl) +{ + $("#loading").ajaxStart(function(){ + $(this).show(); + }).ajaxComplete(function(){ + $(this).hide(); + }); + + $("#upload").ajaxStart(function(){ + $(this).hide(); + }).ajaxComplete(function(){ + $(this).show(); + }); + + $.ajaxFileUpload + ( + { + url:'/upload/', + secureuri:false, + fileElementId:'file-upload', + dataType: 'xml', + success: function (data, status) + { + var fileURL = $(data).find('file_url').text(); + var error = $(data).find('error').text(); + if(error != ''){ + alert(error); + }else{ + imageUrl.attr('value', fileURL); + } + + }, + error: function (data, status, e) + { + alert(e); + } + } + ) + + return false; }
\ No newline at end of file diff --git a/templates/content/js/com.cnprog.post.js b/templates/content/js/com.cnprog.post.js index e3101ea7..723e34ae 100644 --- a/templates/content/js/com.cnprog.post.js +++ b/templates/content/js/com.cnprog.post.js @@ -1,616 +1,616 @@ -/*
-Scripts for cnprog.com
-Project Name: Lanai
-All Rights Resevred 2008. CNPROG.COM
-*/
-var lanai =
-{
- /**
- * Finds any <pre><code></code></pre> tags which aren't registered for
- * pretty printing, adds the appropriate class name and invokes prettify.
- */
- highlightSyntax: function(){
- var styled = false;
- $("pre code").parent().each(function(){
- if (!$(this).hasClass('prettyprint')){
- $(this).addClass('prettyprint');
- styled = true;
- }
- });
-
- if (styled){
- prettyPrint();
- }
- }
-};
-
-var Vote = function(){
- // All actions are related to a question
- var questionId;
- // The object we operate on actually. It can be a question or an answer.
- var postId;
- var questionAuthorId;
- var currentUserId;
- var answerContainerIdPrefix = 'answer-container-';
- var voteContainerId = 'vote-buttons';
- var imgIdPrefixAccept = 'answer-img-accept-';
- var imgClassPrefixFavorite = 'question-img-favorite';
- var imgIdPrefixQuestionVoteup = 'question-img-upvote-';
- var imgIdPrefixQuestionVotedown = 'question-img-downvote-';
- var imgIdPrefixAnswerVoteup = 'answer-img-upvote-';
- var imgIdPrefixAnswerVotedown = 'answer-img-downvote-';
- var divIdFavorite = 'favorite-number';
- var commentLinkIdPrefix = 'comment-';
- var voteNumberClass = "vote-number";
- var offensiveIdPrefixQuestionFlag = 'question-offensive-flag-';
- var offensiveIdPrefixAnswerFlag = 'answer-offensive-flag-';
- var offensiveClassFlag = 'offensive-flag';
- var questionControlsId = 'question-controls';
- var removeQuestionLinkIdPrefix = 'question-delete-link-';
- var removeAnswerLinkIdPrefix = 'answer-delete-link-';
-
- var acceptAnonymousMessage = $.i18n._('insufficient privilege');
- var acceptOwnAnswerMessage = $.i18n._('cannot pick own answer as best');
- var favoriteAnonymousMessage = $.i18n._('anonymous user cannot select favorite questions')
- + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
- + $.i18n._('please login') + "</a>";
- var voteAnonymousMessage = $.i18n._('anonymous users cannot vote')
- + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
- + $.i18n._('please login') + "</a>";
- var upVoteRequiredScoreMessage = $.i18n._('>15 points requried to upvote')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var downVoteRequiredScoreMessage = $.i18n._('>100 points requried to downvote')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var voteOwnDeniedMessage = $.i18n._('cannot vote for own posts');
- var voteRequiredMoreVotes = $.i18n._('daily vote cap exhausted')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var voteDenyCancelMessage = $.i18n._('cannot revoke old vote')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var offensiveConfirmation = $.i18n._('please confirm offensive');
- var offensiveAnonymousMessage = $.i18n._('anonymous users cannot flag offensive posts')
- + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
- + $.i18n._('please login') + "</a>";
- var offensiveTwiceMessage = $.i18n._('cannot flag message as offensive twice')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var offensiveNoFlagsLeftMessage = $.i18n._('flag offensive cap exhausted')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var offensiveNoPermissionMessage = $.i18n._('need >15 points to report spam')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var removeConfirmation = $.i18n._('confirm delete');
- var removeAnonymousMessage = $.i18n._('anonymous users cannot delete/undelete');
- var recoveredMessage = $.i18n._('post recovered');
- var deletedMessage = $.i18n._('post deleted');
-
- var VoteType = {
- acceptAnswer : 0,
- questionUpVote : 1,
- questionDownVote : 2,
- favorite : 4,
- answerUpVote: 5,
- answerDownVote:6,
- offensiveQuestion : 7,
- offensiveAnswer:8,
- removeQuestion: 9,
- removeAnswer:10
- };
-
- var getFavoriteButton = function(){
- var favoriteButton = 'div.'+ voteContainerId +' img[class='+ imgClassPrefixFavorite +']';
- return $(favoriteButton);
- };
- var getFavoriteNumber = function(){
- var favoriteNumber = '#'+ divIdFavorite ;
- return $(favoriteNumber);
- };
- var getQuestionVoteUpButton = function(){
- var questionVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVoteup +']';
- return $(questionVoteUpButton);
- };
- var getQuestionVoteDownButton = function(){
- var questionVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVotedown +']';
- return $(questionVoteDownButton);
- };
- var getAnswerVoteUpButtons = function(){
- var answerVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVoteup +']';
- return $(answerVoteUpButton);
- };
- var getAnswerVoteDownButtons = function(){
- var answerVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVotedown +']';
- return $(answerVoteDownButton);
- };
- var getAnswerVoteUpButton = function(id){
- var answerVoteUpButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVoteup + id + ']';
- return $(answerVoteUpButton);
- };
- var getAnswerVoteDownButton = function(id){
- var answerVoteDownButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVotedown + id + ']';
- return $(answerVoteDownButton);
- };
-
- var getOffensiveQuestionFlag = function(){
- var offensiveQuestionFlag = 'table[id=question-table] span[class='+ offensiveClassFlag +']';
- return $(offensiveQuestionFlag);
- };
-
- var getOffensiveAnswerFlags = function(){
- var offensiveQuestionFlag = 'div.answer span[class='+ offensiveClassFlag +']';
- return $(offensiveQuestionFlag);
- };
-
- var getremoveQuestionLink = function(){
- var removeQuestionLink = 'div#question-controls a[id^='+ removeQuestionLinkIdPrefix +']';
- return $(removeQuestionLink);
- };
-
- var getremoveAnswersLinks = function(){
- var removeAnswerLinks = 'div.answer-controls a[id^='+ removeAnswerLinkIdPrefix +']';
- return $(removeAnswerLinks);
- };
-
- var setVoteImage = function(voteType, undo, object){
- var flag = undo ? "" : "-on";
- var arrow = (voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote) ? "up" : "down";
- object.attr("src", "/content/images/vote-arrow-"+ arrow + flag +".png");
-
- // if undo voting, then undo the pair of arrows.
- if(undo){
- if(voteType == VoteType.questionUpVote || voteType == VoteType.questionDownVote){
- $(getQuestionVoteUpButton()).attr("src", "/content/images/vote-arrow-up.png");
- $(getQuestionVoteDownButton()).attr("src", "/content/images/vote-arrow-down.png");
- }
- else{
- $(getAnswerVoteUpButton(postId)).attr("src", "/content/images/vote-arrow-up.png");
- $(getAnswerVoteDownButton(postId)).attr("src", "/content/images/vote-arrow-down.png");
- }
- }
- };
-
- var setVoteNumber = function(object, number){
- var voteNumber = object.parent('div.'+ voteContainerId).find('div.'+ voteNumberClass);
- $(voteNumber).text(number);
- };
-
- var bindEvents = function(){
- // accept answers
- if(questionAuthorId == currentUserId){
- var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
- $(acceptedButtons).unbind('click').click(function(event){
- Vote.accept($(event.target))
- });
- }
- // set favorite question
- var favoriteButton = getFavoriteButton();
- favoriteButton.unbind('click').click(function(event){
- Vote.favorite($(event.target))
- });
-
- // question vote up
- var questionVoteUpButton = getQuestionVoteUpButton();
- questionVoteUpButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.questionUpVote)
- });
-
- var questionVoteDownButton = getQuestionVoteDownButton();
- questionVoteDownButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.questionDownVote)
- });
-
- var answerVoteUpButton = getAnswerVoteUpButtons();
- answerVoteUpButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.answerUpVote)
- });
-
- var answerVoteDownButton = getAnswerVoteDownButtons();
- answerVoteDownButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.answerDownVote)
- });
-
- getOffensiveQuestionFlag().unbind('click').click(function(event){
- Vote.offensive(this, VoteType.offensiveQuestion)
- });
-
- getOffensiveAnswerFlags().unbind('click').click(function(event){
- Vote.offensive(this, VoteType.offensiveAnswer)
- });
-
- getremoveQuestionLink().unbind('click').click(function(event){
- Vote.remove(this, VoteType.removeQuestion);
- });
-
- getremoveAnswersLinks().unbind('click').click(function(event){
- Vote.remove(this, VoteType.removeAnswer)
- });
- };
-
- var submit = function(object, voteType, callback) {
- $.ajax({
- type: "POST",
- cache: false,
- dataType: "json",
- url: "/questions/" + questionId + "/vote/",
- data: { "type": voteType, "postId": postId },
- error: handleFail,
- success: function(data){callback(object, voteType, data)}});
- };
-
- var handleFail = function(xhr, msg){
- alert("Callback invoke error: " + msg)
- };
-
- // callback function for Accept Answer action
- var callback_accept = function(object, voteType, data){
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, acceptAnonymousMessage);
- }
- else if(data.allowed == "-1"){
- showMessage(object, acceptOwnAnswerMessage);
- }
- else if(data.status == "1"){
- object.attr("src", "/content/images/vote-accepted.png");
- $("#"+answerContainerIdPrefix+postId).removeClass("accepted-answer");
- $("#"+commentLinkIdPrefix+postId).removeClass("comment-link-accepted");
- }
- else if(data.success == "1"){
- var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
- $(acceptedButtons).attr("src", "/content/images/vote-accepted.png");
- var answers = ("div[id^="+answerContainerIdPrefix +"]");
- $(answers).removeClass("accepted-answer");
- var commentLinks = ("div[id^="+answerContainerIdPrefix +"] div[id^="+ commentLinkIdPrefix +"]");
- $(commentLinks).removeClass("comment-link-accepted");
-
- object.attr("src", "/content/images/vote-accepted-on.png");
- $("#"+answerContainerIdPrefix+postId).addClass("accepted-answer");
- $("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted");
- }
- else{
- showMessage(object, data.message);
- }
- };
-
- var callback_favorite = function(object, voteType, data){
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
- }
- else if(data.status == "1"){
- object.attr("src", "/content/images/vote-favorite-off.png");
- var fav = getFavoriteNumber();
- fav.removeClass("my-favorite-number");
- if(data.count == 0)
- data.count = '';
- fav.text(data.count);
- }
- else if(data.success == "1"){
- object.attr("src", "/content/images/vote-favorite-on.png");
- var fav = getFavoriteNumber();
- fav.text(data.count);
- fav.addClass("my-favorite-number");
- }
- else{
- showMessage(object, data.message);
- }
- };
-
- var callback_vote = function(object, voteType, data){
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
- }
- else if(data.allowed == "-3"){
- showMessage(object, voteRequiredMoreVotes);
- }
- else if(data.allowed == "-2"){
- if(voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote){
- showMessage(object, upVoteRequiredScoreMessage);
- }
- else if(voteType == VoteType.questionDownVote || voteType == VoteType.answerDownVote){
- showMessage(object, downVoteRequiredScoreMessage);
- }
- }
- else if(data.allowed == "-1"){
- showMessage(object, voteOwnDeniedMessage);
- }
- else if(data.status == "2"){
- showMessage(object, voteDenyCancelMessage);
- }
- else if(data.status == "1"){
- setVoteImage(voteType, true, object);
- setVoteNumber(object, data.count);
- }
- else if(data.success == "1"){
- setVoteImage(voteType, false, object);
- setVoteNumber(object, data.count);
- if(data.message.length > 0)
- showMessage(object, data.message);
- }
- };
-
- var callback_offensive = function(object, voteType, data){
- object = $(object);
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
- }
- else if(data.allowed == "-3"){
- showMessage(object, offensiveNoFlagsLeftMessage);
- }
- else if(data.allowed == "-2"){
- showMessage(object, offensiveNoPermissionMessage);
- }
- else if(data.status == "1"){
- showMessage(object, offensiveTwiceMessage);
- }
- else if(data.success == "1"){
- $(object).children('span[class=darkred]').text("("+ data.count +")");
- }
- };
-
- var callback_remove = function(object, voteType, data){
- alert(data.status);
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, removeAnonymousMessage.replace("{{QuestionID}}", questionId));
- }
- else if (data.success == "1"){
- if (removeActionType == 'delete'){
- postNode.addClass('deleted');
- postRemoveLink.innerHTML = $.i18n._('undelete');
- showMessage(object, deletedMessage);
- }
- else if (removeActionType == 'undelete') {
- postNode.removeClass('deleted');
- postRemoveLink.innerHTML = $.i18n._('delete');
- showMessage(object, recoveredMessage);
- }
- }
- };
-
- return {
- init : function(qId, questionAuthor, userId){
- questionId = qId;
- questionAuthorId = questionAuthor;
- currentUserId = userId;
- bindEvents();
- },
-
- // Accept answer public function
- accept: function(object){
- postId = object.attr("id").substring(imgIdPrefixAccept.length);
- submit(object, VoteType.acceptAnswer, callback_accept);
- },
-
- favorite: function(object){
- if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
- return false;
- }
- submit(object, VoteType.favorite, callback_favorite);
- },
-
- vote: function(object, voteType){
- if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
- return false;
- }
- if(voteType == VoteType.answerUpVote){
- postId = object.attr("id").substring(imgIdPrefixAnswerVoteup.length);
- }
- else if(voteType == VoteType.answerDownVote){
- postId = object.attr("id").substring(imgIdPrefixAnswerVotedown.length);
- }
-
- submit(object, voteType, callback_vote);
- },
-
- offensive: function(object, voteType){
- if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage($(object), offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
- return false;
- }
- if(confirm(offensiveConfirmation)){
- postId = object.id.substr(object.id.lastIndexOf('-') + 1);
- submit(object, voteType, callback_offensive);
- }
- },
-
- remove: function(object, voteType){
- if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage($(object), removeAnonymousMessage.replace("{{QuestionID}}", questionId));
- return false;
- }
- if(confirm(removeConfirmation)){
- bits = object.id.split('-');
- postId = bits.pop();/* this seems to be used within submit! */
- postType = bits.shift();
-
- if (postType == 'answer'){
- postNode = $('#answer-container-' + postId);
- postRemoveLink = object;
- if (postNode.hasClass('deleted')){
- removeActionType = 'undelete';
- }
- else {
- removeActionType = 'delete';
- }
- }
- submit($(object), voteType, callback_remove);
-
-
- }
- }
- }
-} ();
-
-
-// site comments
-function createComments(type) {
- var objectType = type;
- var jDivInit = function(id) {
- return $("#comments-" + objectType + '-' + id);
- };
-
- var appendLoaderImg = function(id) {
- appendLoader("#comments-" + objectType + '-' + id + " div.comments");
- };
-
- var canPostComments = function(id, jDiv) {
- var jHidden = jDiv.siblings("#can-post-comments-" + objectType + '-' + id);
- return jHidden.val().toLowerCase() == "true";
- };
-
- var renderForm = function(id, jDiv) {
- var formId = "form-comments-" + objectType + "-" + id;
- if (canPostComments(id, jDiv)) {
- if (jDiv.find("#" + formId).length == 0) {
- var form = '<form id="' + formId + '" class="post-comments"><div>';
- form += '<textarea name="comment" cols="60" rows="5" maxlength="300" onblur="'+ objectType +'Comments.updateTextCounter(this)" ';
- form += 'onfocus="' + objectType + 'Comments.updateTextCounter(this)" onkeyup="'+ objectType +'Comments.updateTextCounter(this)"></textarea>';
- form += '<input type="submit" value="'
- + $.i18n._('add comment') + '" /><br><span class="text-counter"></span>';
- form += '<span class="form-error"></span></div></form>';
-
- jDiv.append(form);
-
- setupFormValidation("#" + formId,
- { comment: { required: true, minlength: 10} }, '',
- function() { postComment(id, formId); });
- }
- }
- else {
- var divId = "comments-rep-needed-" + objectType + '-' + id;
- if (jDiv.find("#" + divId).length == 0) {
- jDiv.append('<div id="' + divId + '" style="color:red">'
- + $.i18n._('to comment, need') + ' ' +
- + repNeededForComments + ' ' + $.i18n._('community reputation points')
- + '<a href="/faq" class="comment-user">' + $.i18n._('please see') + 'faq</a></span>');
- }
- }
- };
-
- var getComments = function(id, jDiv) {
- appendLoaderImg(id);
- $.getJSON("/" + objectType + "s/" + id + "/comments/", function(json) { showComments(id, json); });
- };
-
- var showComments = function(id, json) {
- var jDiv = jDivInit(id);
-
- jDiv = jDiv.find("div.comments"); // this div should contain any fetched comments..
- jDiv.find("div[id^='comment-" + objectType + "-'" + "]").remove(); // clean previous calls..
-
- removeLoader();
-
- if (json && json.length > 0) {
- for (var i = 0; i < json.length; i++)
- renderComment(jDiv, json[i]);
-
- jDiv.children().show();
- }
- };
-
- // {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
- var renderComment = function(jDiv, json) {
- var html = '<div id="comment-' + objectType + "-" + json.id + '" style="display:none">' + json.text;
- html += json.user_url ? ' – <a href="' + json.user_url + '"' : '<span';
- html += ' class="comment-user">' + json.user_display_name + (json.user_url ? '</a>' : '</span>');
- html += ' <span class="comment-date">(' + json.add_date + ')</span>';
-
- if (json.delete_url) {
- var img = "/content/images/close-small.png";
- var imgHover = "/content/images/close-small-hover.png";
- html += '<img onclick="' + objectType + 'Comments.deleteComment($(this), ' + json.object_id + ', \'' + json.delete_url + '\')" src="' + img;
- html += '" onmouseover="$(this).attr(\'src\', \'' + imgHover + '\')" onmouseout="$(this).attr(\'src\', \'' + img
- html += '\')" title="' + $.i18n._('delete this comment') + '" />';
- }
-
- html += '</div>';
-
- jDiv.append(html);
- };
-
- var postComment = function(id, formId) {
- appendLoaderImg(id);
-
- var formSelector = "#" + formId;
- var textarea = $(formSelector + " textarea");
-
- $.ajax({
- type: "POST",
- url: "/" + objectType + "s/" + id + "/comments/",
- dataType: "json",
- data: { comment: textarea.val() },
- success: function(json) {
- showComments(id, json);
- textarea.val("");
- commentsFactory[objectType].updateTextCounter(textarea);
- enableSubmitButton(formSelector);
- },
- error: function(res, textStatus, errorThrown) {
- removeLoader();
- showMessage(formSelector, res.responseText);
- enableSubmitButton(formSelector);
- }
- });
- };
-
- // public methods..
- return {
-
- init: function() {
- // Setup "show comments" clicks..
- $("a[id^='comments-link-" + objectType + "-" + "']").unbind("click").click(function() { commentsFactory[objectType].show($(this).attr("id").substr(("comments-link-" + objectType + "-").length)); });
- },
-
- show: function(id) {
- var jDiv = jDivInit(id);
- getComments(id, jDiv);
- renderForm(id, jDiv);
- jDiv.show();
- if (canPostComments(id, jDiv)) jDiv.find("textarea").get(0).focus();
- jDiv.siblings("a").unbind("click").click(function(){
- commentsFactory[objectType].hide(id);
- }).text($.i18n._('hide comments'));
- },
-
- hide: function(id) {
- var jDiv = jDivInit(id);
- var len = jDiv.children("div.comments").children().length;
- var anchorText = len == 0 ? $.i18n._('add a comment') : $.i18n._('comments') + ' (<b>' + len + "</b>)";
-
- jDiv.hide();
- jDiv.siblings("a").unbind("click").click(function() { commentsFactory[objectType].show(id); }).html(anchorText);
- jDiv.children("div.comments").children().hide();
- },
-
- deleteComment: function(jImg, id, deleteUrl) {
- if (confirm($.i18n._('confirm delete comment'))) {
- jImg.hide();
- appendLoaderImg(id);
- $.post(deleteUrl, { dataNeeded: "forIIS7" }, function(json) {
- showComments(id, json);
- }, "json");
- }
- },
-
- updateTextCounter: function(textarea) {
- var length = textarea.value ? textarea.value.length : 0;
- var color = length > 270 ? "#f00" : length > 200 ? "#f60" : "#999";
- var jSpan = $(textarea).siblings("span.text-counter");
- jSpan.html($.i18n._('can write')
- + (300 - length) + ' '
- + $.i18n._('characters')).css("color", color);
- }
- };
-}
-
-var questionComments = createComments('question');
-var answerComments = createComments('answer');
-
-$().ready(function() {
- questionComments.init();
- answerComments.init();
-});
-
-var commentsFactory = {'question' : questionComments, 'answer' : answerComments};
-
-/*
-Prettify
-http://www.apache.org/licenses/LICENSE-2.0
-*/
+/* +Scripts for cnprog.com +Project Name: Lanai +All Rights Resevred 2008. CNPROG.COM +*/ +var lanai = +{ + /** + * Finds any <pre><code></code></pre> tags which aren't registered for + * pretty printing, adds the appropriate class name and invokes prettify. + */ + highlightSyntax: function(){ + var styled = false; + $("pre code").parent().each(function(){ + if (!$(this).hasClass('prettyprint')){ + $(this).addClass('prettyprint'); + styled = true; + } + }); + + if (styled){ + prettyPrint(); + } + } +}; + +var Vote = function(){ + // All actions are related to a question + var questionId; + // The object we operate on actually. It can be a question or an answer. + var postId; + var questionAuthorId; + var currentUserId; + var answerContainerIdPrefix = 'answer-container-'; + var voteContainerId = 'vote-buttons'; + var imgIdPrefixAccept = 'answer-img-accept-'; + var imgClassPrefixFavorite = 'question-img-favorite'; + var imgIdPrefixQuestionVoteup = 'question-img-upvote-'; + var imgIdPrefixQuestionVotedown = 'question-img-downvote-'; + var imgIdPrefixAnswerVoteup = 'answer-img-upvote-'; + var imgIdPrefixAnswerVotedown = 'answer-img-downvote-'; + var divIdFavorite = 'favorite-number'; + var commentLinkIdPrefix = 'comment-'; + var voteNumberClass = "vote-number"; + var offensiveIdPrefixQuestionFlag = 'question-offensive-flag-'; + var offensiveIdPrefixAnswerFlag = 'answer-offensive-flag-'; + var offensiveClassFlag = 'offensive-flag'; + var questionControlsId = 'question-controls'; + var removeQuestionLinkIdPrefix = 'question-delete-link-'; + var removeAnswerLinkIdPrefix = 'answer-delete-link-'; + + var acceptAnonymousMessage = $.i18n._('insufficient privilege'); + var acceptOwnAnswerMessage = $.i18n._('cannot pick own answer as best'); + var favoriteAnonymousMessage = $.i18n._('anonymous user cannot select favorite questions') + + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>" + + $.i18n._('please login') + "</a>"; + var voteAnonymousMessage = $.i18n._('anonymous users cannot vote') + + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>" + + $.i18n._('please login') + "</a>"; + var upVoteRequiredScoreMessage = $.i18n._('>15 points requried to upvote') + + $.i18n._('please see') + "<a href='/faq'>faq</a>"; + var downVoteRequiredScoreMessage = $.i18n._('>100 points requried to downvote') + + $.i18n._('please see') + "<a href='/faq'>faq</a>"; + var voteOwnDeniedMessage = $.i18n._('cannot vote for own posts'); + var voteRequiredMoreVotes = $.i18n._('daily vote cap exhausted') + + $.i18n._('please see') + "<a href='/faq'>faq</a>"; + var voteDenyCancelMessage = $.i18n._('cannot revoke old vote') + + $.i18n._('please see') + "<a href='/faq'>faq</a>"; + var offensiveConfirmation = $.i18n._('please confirm offensive'); + var offensiveAnonymousMessage = $.i18n._('anonymous users cannot flag offensive posts') + + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>" + + $.i18n._('please login') + "</a>"; + var offensiveTwiceMessage = $.i18n._('cannot flag message as offensive twice') + + $.i18n._('please see') + "<a href='/faq'>faq</a>"; + var offensiveNoFlagsLeftMessage = $.i18n._('flag offensive cap exhausted') + + $.i18n._('please see') + "<a href='/faq'>faq</a>"; + var offensiveNoPermissionMessage = $.i18n._('need >15 points to report spam') + + $.i18n._('please see') + "<a href='/faq'>faq</a>"; + var removeConfirmation = $.i18n._('confirm delete'); + var removeAnonymousMessage = $.i18n._('anonymous users cannot delete/undelete'); + var recoveredMessage = $.i18n._('post recovered'); + var deletedMessage = $.i18n._('post deleted'); + + var VoteType = { + acceptAnswer : 0, + questionUpVote : 1, + questionDownVote : 2, + favorite : 4, + answerUpVote: 5, + answerDownVote:6, + offensiveQuestion : 7, + offensiveAnswer:8, + removeQuestion: 9, + removeAnswer:10 + }; + + var getFavoriteButton = function(){ + var favoriteButton = 'div.'+ voteContainerId +' img[class='+ imgClassPrefixFavorite +']'; + return $(favoriteButton); + }; + var getFavoriteNumber = function(){ + var favoriteNumber = '#'+ divIdFavorite ; + return $(favoriteNumber); + }; + var getQuestionVoteUpButton = function(){ + var questionVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVoteup +']'; + return $(questionVoteUpButton); + }; + var getQuestionVoteDownButton = function(){ + var questionVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVotedown +']'; + return $(questionVoteDownButton); + }; + var getAnswerVoteUpButtons = function(){ + var answerVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVoteup +']'; + return $(answerVoteUpButton); + }; + var getAnswerVoteDownButtons = function(){ + var answerVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVotedown +']'; + return $(answerVoteDownButton); + }; + var getAnswerVoteUpButton = function(id){ + var answerVoteUpButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVoteup + id + ']'; + return $(answerVoteUpButton); + }; + var getAnswerVoteDownButton = function(id){ + var answerVoteDownButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVotedown + id + ']'; + return $(answerVoteDownButton); + }; + + var getOffensiveQuestionFlag = function(){ + var offensiveQuestionFlag = 'table[id=question-table] span[class='+ offensiveClassFlag +']'; + return $(offensiveQuestionFlag); + }; + + var getOffensiveAnswerFlags = function(){ + var offensiveQuestionFlag = 'div.answer span[class='+ offensiveClassFlag +']'; + return $(offensiveQuestionFlag); + }; + + var getremoveQuestionLink = function(){ + var removeQuestionLink = 'div#question-controls a[id^='+ removeQuestionLinkIdPrefix +']'; + return $(removeQuestionLink); + }; + + var getremoveAnswersLinks = function(){ + var removeAnswerLinks = 'div.answer-controls a[id^='+ removeAnswerLinkIdPrefix +']'; + return $(removeAnswerLinks); + }; + + var setVoteImage = function(voteType, undo, object){ + var flag = undo ? "" : "-on"; + var arrow = (voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote) ? "up" : "down"; + object.attr("src", "/content/images/vote-arrow-"+ arrow + flag +".png"); + + // if undo voting, then undo the pair of arrows. + if(undo){ + if(voteType == VoteType.questionUpVote || voteType == VoteType.questionDownVote){ + $(getQuestionVoteUpButton()).attr("src", "/content/images/vote-arrow-up.png"); + $(getQuestionVoteDownButton()).attr("src", "/content/images/vote-arrow-down.png"); + } + else{ + $(getAnswerVoteUpButton(postId)).attr("src", "/content/images/vote-arrow-up.png"); + $(getAnswerVoteDownButton(postId)).attr("src", "/content/images/vote-arrow-down.png"); + } + } + }; + + var setVoteNumber = function(object, number){ + var voteNumber = object.parent('div.'+ voteContainerId).find('div.'+ voteNumberClass); + $(voteNumber).text(number); + }; + + var bindEvents = function(){ + // accept answers + if(questionAuthorId == currentUserId){ + var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']'; + $(acceptedButtons).unbind('click').click(function(event){ + Vote.accept($(event.target)) + }); + } + // set favorite question + var favoriteButton = getFavoriteButton(); + favoriteButton.unbind('click').click(function(event){ + Vote.favorite($(event.target)) + }); + + // question vote up + var questionVoteUpButton = getQuestionVoteUpButton(); + questionVoteUpButton.unbind('click').click(function(event){ + Vote.vote($(event.target), VoteType.questionUpVote) + }); + + var questionVoteDownButton = getQuestionVoteDownButton(); + questionVoteDownButton.unbind('click').click(function(event){ + Vote.vote($(event.target), VoteType.questionDownVote) + }); + + var answerVoteUpButton = getAnswerVoteUpButtons(); + answerVoteUpButton.unbind('click').click(function(event){ + Vote.vote($(event.target), VoteType.answerUpVote) + }); + + var answerVoteDownButton = getAnswerVoteDownButtons(); + answerVoteDownButton.unbind('click').click(function(event){ + Vote.vote($(event.target), VoteType.answerDownVote) + }); + + getOffensiveQuestionFlag().unbind('click').click(function(event){ + Vote.offensive(this, VoteType.offensiveQuestion) + }); + + getOffensiveAnswerFlags().unbind('click').click(function(event){ + Vote.offensive(this, VoteType.offensiveAnswer) + }); + + getremoveQuestionLink().unbind('click').click(function(event){ + Vote.remove(this, VoteType.removeQuestion); + }); + + getremoveAnswersLinks().unbind('click').click(function(event){ + Vote.remove(this, VoteType.removeAnswer) + }); + }; + + var submit = function(object, voteType, callback) { + $.ajax({ + type: "POST", + cache: false, + dataType: "json", + url: "/questions/" + questionId + "/vote/", + data: { "type": voteType, "postId": postId }, + error: handleFail, + success: function(data){callback(object, voteType, data)}}); + }; + + var handleFail = function(xhr, msg){ + alert("Callback invoke error: " + msg) + }; + + // callback function for Accept Answer action + var callback_accept = function(object, voteType, data){ + if(data.allowed == "0" && data.success == "0"){ + showMessage(object, acceptAnonymousMessage); + } + else if(data.allowed == "-1"){ + showMessage(object, acceptOwnAnswerMessage); + } + else if(data.status == "1"){ + object.attr("src", "/content/images/vote-accepted.png"); + $("#"+answerContainerIdPrefix+postId).removeClass("accepted-answer"); + $("#"+commentLinkIdPrefix+postId).removeClass("comment-link-accepted"); + } + else if(data.success == "1"){ + var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']'; + $(acceptedButtons).attr("src", "/content/images/vote-accepted.png"); + var answers = ("div[id^="+answerContainerIdPrefix +"]"); + $(answers).removeClass("accepted-answer"); + var commentLinks = ("div[id^="+answerContainerIdPrefix +"] div[id^="+ commentLinkIdPrefix +"]"); + $(commentLinks).removeClass("comment-link-accepted"); + + object.attr("src", "/content/images/vote-accepted-on.png"); + $("#"+answerContainerIdPrefix+postId).addClass("accepted-answer"); + $("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted"); + } + else{ + showMessage(object, data.message); + } + }; + + var callback_favorite = function(object, voteType, data){ + if(data.allowed == "0" && data.success == "0"){ + showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId)); + } + else if(data.status == "1"){ + object.attr("src", "/content/images/vote-favorite-off.png"); + var fav = getFavoriteNumber(); + fav.removeClass("my-favorite-number"); + if(data.count == 0) + data.count = ''; + fav.text(data.count); + } + else if(data.success == "1"){ + object.attr("src", "/content/images/vote-favorite-on.png"); + var fav = getFavoriteNumber(); + fav.text(data.count); + fav.addClass("my-favorite-number"); + } + else{ + showMessage(object, data.message); + } + }; + + var callback_vote = function(object, voteType, data){ + if(data.allowed == "0" && data.success == "0"){ + showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId)); + } + else if(data.allowed == "-3"){ + showMessage(object, voteRequiredMoreVotes); + } + else if(data.allowed == "-2"){ + if(voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote){ + showMessage(object, upVoteRequiredScoreMessage); + } + else if(voteType == VoteType.questionDownVote || voteType == VoteType.answerDownVote){ + showMessage(object, downVoteRequiredScoreMessage); + } + } + else if(data.allowed == "-1"){ + showMessage(object, voteOwnDeniedMessage); + } + else if(data.status == "2"){ + showMessage(object, voteDenyCancelMessage); + } + else if(data.status == "1"){ + setVoteImage(voteType, true, object); + setVoteNumber(object, data.count); + } + else if(data.success == "1"){ + setVoteImage(voteType, false, object); + setVoteNumber(object, data.count); + if(data.message.length > 0) + showMessage(object, data.message); + } + }; + + var callback_offensive = function(object, voteType, data){ + object = $(object); + if(data.allowed == "0" && data.success == "0"){ + showMessage(object, offensiveAnonymousMessage.replace("{{QuestionID}}", questionId)); + } + else if(data.allowed == "-3"){ + showMessage(object, offensiveNoFlagsLeftMessage); + } + else if(data.allowed == "-2"){ + showMessage(object, offensiveNoPermissionMessage); + } + else if(data.status == "1"){ + showMessage(object, offensiveTwiceMessage); + } + else if(data.success == "1"){ + $(object).children('span[class=darkred]').text("("+ data.count +")"); + } + }; + + var callback_remove = function(object, voteType, data){ + alert(data.status); + if(data.allowed == "0" && data.success == "0"){ + showMessage(object, removeAnonymousMessage.replace("{{QuestionID}}", questionId)); + } + else if (data.success == "1"){ + if (removeActionType == 'delete'){ + postNode.addClass('deleted'); + postRemoveLink.innerHTML = $.i18n._('undelete'); + showMessage(object, deletedMessage); + } + else if (removeActionType == 'undelete') { + postNode.removeClass('deleted'); + postRemoveLink.innerHTML = $.i18n._('delete'); + showMessage(object, recoveredMessage); + } + } + }; + + return { + init : function(qId, questionAuthor, userId){ + questionId = qId; + questionAuthorId = questionAuthor; + currentUserId = userId; + bindEvents(); + }, + + // Accept answer public function + accept: function(object){ + postId = object.attr("id").substring(imgIdPrefixAccept.length); + submit(object, VoteType.acceptAnswer, callback_accept); + }, + + favorite: function(object){ + if(!currentUserId || currentUserId.toUpperCase() == "NONE"){ + showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId)); + return false; + } + submit(object, VoteType.favorite, callback_favorite); + }, + + vote: function(object, voteType){ + if(!currentUserId || currentUserId.toUpperCase() == "NONE"){ + showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId)); + return false; + } + if(voteType == VoteType.answerUpVote){ + postId = object.attr("id").substring(imgIdPrefixAnswerVoteup.length); + } + else if(voteType == VoteType.answerDownVote){ + postId = object.attr("id").substring(imgIdPrefixAnswerVotedown.length); + } + + submit(object, voteType, callback_vote); + }, + + offensive: function(object, voteType){ + if(!currentUserId || currentUserId.toUpperCase() == "NONE"){ + showMessage($(object), offensiveAnonymousMessage.replace("{{QuestionID}}", questionId)); + return false; + } + if(confirm(offensiveConfirmation)){ + postId = object.id.substr(object.id.lastIndexOf('-') + 1); + submit(object, voteType, callback_offensive); + } + }, + + remove: function(object, voteType){ + if(!currentUserId || currentUserId.toUpperCase() == "NONE"){ + showMessage($(object), removeAnonymousMessage.replace("{{QuestionID}}", questionId)); + return false; + } + if(confirm(removeConfirmation)){ + bits = object.id.split('-'); + postId = bits.pop();/* this seems to be used within submit! */ + postType = bits.shift(); + + if (postType == 'answer'){ + postNode = $('#answer-container-' + postId); + postRemoveLink = object; + if (postNode.hasClass('deleted')){ + removeActionType = 'undelete'; + } + else { + removeActionType = 'delete'; + } + } + submit($(object), voteType, callback_remove); + + + } + } + } +} (); + + +// site comments +function createComments(type) { + var objectType = type; + var jDivInit = function(id) { + return $("#comments-" + objectType + '-' + id); + }; + + var appendLoaderImg = function(id) { + appendLoader("#comments-" + objectType + '-' + id + " div.comments"); + }; + + var canPostComments = function(id, jDiv) { + var jHidden = jDiv.siblings("#can-post-comments-" + objectType + '-' + id); + return jHidden.val().toLowerCase() == "true"; + }; + + var renderForm = function(id, jDiv) { + var formId = "form-comments-" + objectType + "-" + id; + if (canPostComments(id, jDiv)) { + if (jDiv.find("#" + formId).length == 0) { + var form = '<form id="' + formId + '" class="post-comments"><div>'; + form += '<textarea name="comment" cols="60" rows="5" maxlength="300" onblur="'+ objectType +'Comments.updateTextCounter(this)" '; + form += 'onfocus="' + objectType + 'Comments.updateTextCounter(this)" onkeyup="'+ objectType +'Comments.updateTextCounter(this)"></textarea>'; + form += '<input type="submit" value="' + + $.i18n._('add comment') + '" /><br><span class="text-counter"></span>'; + form += '<span class="form-error"></span></div></form>'; + + jDiv.append(form); + + setupFormValidation("#" + formId, + { comment: { required: true, minlength: 10} }, '', + function() { postComment(id, formId); }); + } + } + else { + var divId = "comments-rep-needed-" + objectType + '-' + id; + if (jDiv.find("#" + divId).length == 0) { + jDiv.append('<div id="' + divId + '" style="color:red">' + + $.i18n._('to comment, need') + ' ' + + + repNeededForComments + ' ' + $.i18n._('community reputation points') + + '<a href="/faq" class="comment-user">' + $.i18n._('please see') + 'faq</a></span>'); + } + } + }; + + var getComments = function(id, jDiv) { + appendLoaderImg(id); + $.getJSON("/" + objectType + "s/" + id + "/comments/", function(json) { showComments(id, json); }); + }; + + var showComments = function(id, json) { + var jDiv = jDivInit(id); + + jDiv = jDiv.find("div.comments"); // this div should contain any fetched comments.. + jDiv.find("div[id^='comment-" + objectType + "-'" + "]").remove(); // clean previous calls.. + + removeLoader(); + + if (json && json.length > 0) { + for (var i = 0; i < json.length; i++) + renderComment(jDiv, json[i]); + + jDiv.children().show(); + } + }; + + // {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null} + var renderComment = function(jDiv, json) { + var html = '<div id="comment-' + objectType + "-" + json.id + '" style="display:none">' + json.text; + html += json.user_url ? ' – <a href="' + json.user_url + '"' : '<span'; + html += ' class="comment-user">' + json.user_display_name + (json.user_url ? '</a>' : '</span>'); + html += ' <span class="comment-date">(' + json.add_date + ')</span>'; + + if (json.delete_url) { + var img = "/content/images/close-small.png"; + var imgHover = "/content/images/close-small-hover.png"; + html += '<img onclick="' + objectType + 'Comments.deleteComment($(this), ' + json.object_id + ', \'' + json.delete_url + '\')" src="' + img; + html += '" onmouseover="$(this).attr(\'src\', \'' + imgHover + '\')" onmouseout="$(this).attr(\'src\', \'' + img + html += '\')" title="' + $.i18n._('delete this comment') + '" />'; + } + + html += '</div>'; + + jDiv.append(html); + }; + + var postComment = function(id, formId) { + appendLoaderImg(id); + + var formSelector = "#" + formId; + var textarea = $(formSelector + " textarea"); + + $.ajax({ + type: "POST", + url: "/" + objectType + "s/" + id + "/comments/", + dataType: "json", + data: { comment: textarea.val() }, + success: function(json) { + showComments(id, json); + textarea.val(""); + commentsFactory[objectType].updateTextCounter(textarea); + enableSubmitButton(formSelector); + }, + error: function(res, textStatus, errorThrown) { + removeLoader(); + showMessage(formSelector, res.responseText); + enableSubmitButton(formSelector); + } + }); + }; + + // public methods.. + return { + + init: function() { + // Setup "show comments" clicks.. + $("a[id^='comments-link-" + objectType + "-" + "']").unbind("click").click(function() { commentsFactory[objectType].show($(this).attr("id").substr(("comments-link-" + objectType + "-").length)); }); + }, + + show: function(id) { + var jDiv = jDivInit(id); + getComments(id, jDiv); + renderForm(id, jDiv); + jDiv.show(); + if (canPostComments(id, jDiv)) jDiv.find("textarea").get(0).focus(); + jDiv.siblings("a").unbind("click").click(function(){ + commentsFactory[objectType].hide(id); + }).text($.i18n._('hide comments')); + }, + + hide: function(id) { + var jDiv = jDivInit(id); + var len = jDiv.children("div.comments").children().length; + var anchorText = len == 0 ? $.i18n._('add a comment') : $.i18n._('comments') + ' (<b>' + len + "</b>)"; + + jDiv.hide(); + jDiv.siblings("a").unbind("click").click(function() { commentsFactory[objectType].show(id); }).html(anchorText); + jDiv.children("div.comments").children().hide(); + }, + + deleteComment: function(jImg, id, deleteUrl) { + if (confirm($.i18n._('confirm delete comment'))) { + jImg.hide(); + appendLoaderImg(id); + $.post(deleteUrl, { dataNeeded: "forIIS7" }, function(json) { + showComments(id, json); + }, "json"); + } + }, + + updateTextCounter: function(textarea) { + var length = textarea.value ? textarea.value.length : 0; + var color = length > 270 ? "#f00" : length > 200 ? "#f60" : "#999"; + var jSpan = $(textarea).siblings("span.text-counter"); + jSpan.html($.i18n._('can write') + + (300 - length) + ' ' + + $.i18n._('characters')).css("color", color); + } + }; +} + +var questionComments = createComments('question'); +var answerComments = createComments('answer'); + +$().ready(function() { + questionComments.init(); + answerComments.init(); +}); + +var commentsFactory = {'question' : questionComments, 'answer' : answerComments}; + +/* +Prettify +http://www.apache.org/licenses/LICENSE-2.0 +*/ var PR_SHOULD_USE_CONTINUATION = true; var PR_TAB_WIDTH = 8; var PR_normalizedHtml; var PR; var prettyPrintOne; var prettyPrint; function _pr_isIE6() { var isIE6 = navigator && navigator.userAgent && /\bMSIE 6\./.test(navigator.userAgent); _pr_isIE6 = function() { return isIE6; }; return isIE6; } (function() { function wordSet(words) { words = words.split(/ /g); var set = {}; for (var i = words.length; --i >= 0; ) { var w = words[i]; if (w) { set[w] = null; } } return set; } var FLOW_CONTROL_KEYWORDS = "break continue do else for if return while "; var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " + "double enum extern float goto int long register short signed sizeof " + "static struct switch typedef union unsigned void volatile "; var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " + "new operator private protected public this throw true try "; var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " + "concept concept_map const_cast constexpr decltype " + "dynamic_cast explicit export friend inline late_check " + "mutable namespace nullptr reinterpret_cast static_assert static_cast " + "template typeid typename typeof using virtual wchar_t where "; var JAVA_KEYWORDS = COMMON_KEYWORDS + "boolean byte extends final finally implements import instanceof null " + "native package strictfp super synchronized throws transient "; var CSHARP_KEYWORDS = JAVA_KEYWORDS + "as base by checked decimal delegate descending event " + "fixed foreach from group implicit in interface internal into is lock " + "object out override orderby params readonly ref sbyte sealed " + "stackalloc string select uint ulong unchecked unsafe ushort var "; var JSCRIPT_KEYWORDS = COMMON_KEYWORDS + "debugger eval export function get null set undefined var with " + "Infinity NaN "; var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " + "goto if import last local my next no our print package redo require " + "sub undef unless until use wantarray while BEGIN END "; var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " + "elif except exec finally from global import in is lambda " + "nonlocal not or pass print raise try with yield " + "False True None "; var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" + " defined elsif end ensure false in module next nil not or redo rescue " + "retry self super then true undef unless until when yield BEGIN END "; var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " + "function in local set then until "; var ALL_KEYWORDS = (CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS + PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS); var PR_STRING = 'str'; var PR_KEYWORD = 'kwd'; var PR_COMMENT = 'com'; var PR_TYPE = 'typ'; var PR_LITERAL = 'lit'; var PR_PUNCTUATION = 'pun'; var PR_PLAIN = 'pln'; var PR_TAG = 'tag'; var PR_DECLARATION = 'dec'; var PR_SOURCE = 'src'; var PR_ATTRIB_NAME = 'atn'; var PR_ATTRIB_VALUE = 'atv'; var PR_NOCODE = 'nocode'; function isWordChar(ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); } function spliceArrayInto(inserted, container, containerPosition, countReplaced) { inserted.unshift(containerPosition, countReplaced || 0); try { container.splice.apply(container, inserted); } finally { inserted.splice(0, 2); } } var REGEXP_PRECEDER_PATTERN = function() { var preceders = ["!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=", "&=", "(", "*", "*=", "+=", ",", "-=", "->", "/", "/=", ":", "::", ";", "<", "<<", "<<=", "<=", "=", "==", "===", ">", ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[", "^", "^=", "^^", "^^=", "{", "|", "|=", "||", "||=", "~", "break", "case", "continue", "delete", "do", "else", "finally", "instanceof", "return", "throw", "try", "typeof"]; var pattern = '(?:' + '(?:(?:^|[^0-9.])\\.{1,3})|' + '(?:(?:^|[^\\+])\\+)|' + '(?:(?:^|[^\\-])-)'; for (var i = 0; i < preceders.length; ++i) { var preceder = preceders[i]; if (isWordChar(preceder.charAt(0))) { pattern += '|\\b' + preceder; } else { pattern += '|' + preceder.replace(/([^=<>:&])/g, '\\$1'); } } pattern += '|^)\\s*$'; return new RegExp(pattern); } (); var pr_amp = /&/g; var pr_lt = /</g; var pr_gt = />/g; var pr_quot = /\"/g; function attribToHtml(str) { return str.replace(pr_amp, '&').replace(pr_lt, '<').replace(pr_gt, '>').replace(pr_quot, '"'); } function textToHtml(str) { return str.replace(pr_amp, '&').replace(pr_lt, '<').replace(pr_gt, '>'); } var pr_ltEnt = /</g; var pr_gtEnt = />/g; var pr_aposEnt = /'/g; var pr_quotEnt = /"/g; var pr_ampEnt = /&/g; var pr_nbspEnt = / /g; function htmlToText(html) { var pos = html.indexOf('&'); if (pos < 0) { return html; } for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0; ) { var end = html.indexOf(';', pos); if (end >= 0) { var num = html.substring(pos + 3, end); var radix = 10; if (num && num.charAt(0) === 'x') { num = num.substring(1); radix = 16; } var codePoint = parseInt(num, radix); if (!isNaN(codePoint)) { html = (html.substring(0, pos) + String.fromCharCode(codePoint) + html.substring(end + 1)); } } } return html.replace(pr_ltEnt, '<').replace(pr_gtEnt, '>').replace(pr_aposEnt, "'").replace(pr_quotEnt, '"').replace(pr_ampEnt, '&').replace(pr_nbspEnt, ' '); } function isRawContent(node) { return 'XMP' === node.tagName; } function normalizedHtml(node, out) { switch (node.nodeType) { case 1: var name = node.tagName.toLowerCase(); out.push('<', name); for (var i = 0; i < node.attributes.length; ++i) { var attr = node.attributes[i]; if (!attr.specified) { continue; } out.push(' '); normalizedHtml(attr, out); } out.push('>'); for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } if (node.firstChild || !/^(?:br|link|img)$/.test(name)) { out.push('<\/', name, '>'); } break; case 2: out.push(node.name.toLowerCase(), '="', attribToHtml(node.value), '"'); break; case 3: case 4: out.push(textToHtml(node.nodeValue)); break; } } var PR_innerHtmlWorks = null; function getInnerHtml(node) { if (null === PR_innerHtmlWorks) { var testNode = document.createElement('PRE'); testNode.appendChild(document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />')); PR_innerHtmlWorks = !/</.test(testNode.innerHTML); } if (PR_innerHtmlWorks) { var content = node.innerHTML; if (isRawContent(node)) { content = textToHtml(content); } return content; } var out = []; for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } return out.join(''); } function makeTabExpander(tabWidth) { var SPACES = ' '; var charInLine = 0; return function(plainText) { var out = null; var pos = 0; for (var i = 0, n = plainText.length; i < n; ++i) { var ch = plainText.charAt(i); switch (ch) { case '\t': if (!out) { out = []; } out.push(plainText.substring(pos, i)); var nSpaces = tabWidth - (charInLine % tabWidth); charInLine += nSpaces; for (; nSpaces >= 0; nSpaces -= SPACES.length) { out.push(SPACES.substring(0, nSpaces)); } pos = i + 1; break; case '\n': charInLine = 0; break; default: ++charInLine; } } if (!out) { return plainText; } out.push(plainText.substring(pos)); return out.join(''); }; } var pr_chunkPattern = /(?:[^<]+|<!--[\s\S]*?-->|<!\[CDATA\[([\s\S]*?)\]\]>|<\/?[a-zA-Z][^>]*>|<)/g; var pr_commentPrefix = /^<!--/; var pr_cdataPrefix = /^<\[CDATA\[/; var pr_brPrefix = /^<br\b/i; var pr_tagNameRe = /^<(\/?)([a-zA-Z]+)/; function extractTags(s) { var matches = s.match(pr_chunkPattern); var sourceBuf = []; var sourceBufLen = 0; var extractedTags = []; if (matches) { for (var i = 0, n = matches.length; i < n; ++i) { var match = matches[i]; if (match.length > 1 && match.charAt(0) === '<') { if (pr_commentPrefix.test(match)) { continue; } if (pr_cdataPrefix.test(match)) { sourceBuf.push(match.substring(9, match.length - 3)); sourceBufLen += match.length - 12; } else if (pr_brPrefix.test(match)) { sourceBuf.push('\n'); ++sourceBufLen; } else { if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) { var name = match.match(pr_tagNameRe)[2]; var depth = 1; end_tag_loop: for (var j = i + 1; j < n; ++j) { var name2 = matches[j].match(pr_tagNameRe); if (name2 && name2[2] === name) { if (name2[1] === '/') { if (--depth === 0) { break end_tag_loop; } } else { ++depth; } } } if (j < n) { extractedTags.push(sourceBufLen, matches.slice(i, j + 1).join('')); i = j; } else { extractedTags.push(sourceBufLen, match); } } else { extractedTags.push(sourceBufLen, match); } } } else { var literalText = htmlToText(match); sourceBuf.push(literalText); sourceBufLen += literalText.length; } } } return { source: sourceBuf.join(''), tags: extractedTags }; } function isNoCodeTag(tag) { return !!tag.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g, ' $1="$2$3$4"').match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/); } function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) { var shortcuts = {}; (function() { var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns); for (var i = allPatterns.length; --i >= 0; ) { var patternParts = allPatterns[i]; var shortcutChars = patternParts[3]; if (shortcutChars) { for (var c = shortcutChars.length; --c >= 0; ) { shortcuts[shortcutChars.charAt(c)] = patternParts; } } } })(); var nPatterns = fallthroughStylePatterns.length; var notWs = /\S/; return function(sourceCode, opt_basePos) { opt_basePos = opt_basePos || 0; var decorations = [opt_basePos, PR_PLAIN]; var lastToken = ''; var pos = 0; var tail = sourceCode; while (tail.length) { var style; var token = null; var match; var patternParts = shortcuts[tail.charAt(0)]; if (patternParts) { match = tail.match(patternParts[1]); token = match[0]; style = patternParts[0]; } else { for (var i = 0; i < nPatterns; ++i) { patternParts = fallthroughStylePatterns[i]; var contextPattern = patternParts[2]; if (contextPattern && !contextPattern.test(lastToken)) { continue; } match = tail.match(patternParts[1]); if (match) { token = match[0]; style = patternParts[0]; break; } } if (!token) { style = PR_PLAIN; token = tail.substring(0, 1); } } decorations.push(opt_basePos + pos, style); pos += token.length; tail = tail.substring(token.length); if (style !== PR_COMMENT && notWs.test(token)) { lastToken = token; } } return decorations; }; } var PR_MARKUP_LEXER = createSimpleLexer([], [[PR_PLAIN, /^[^<]+/, null], [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/, null], [PR_COMMENT, /^<!--[\s\S]*?(?:-->|$)/, null], [PR_SOURCE, /^<\?[\s\S]*?(?:\?>|$)/, null], [PR_SOURCE, /^<%[\s\S]*?(?:%>|$)/, null], [PR_SOURCE, /^<(script|style|xmp)\b[^>]*>[\s\S]*?<\/\1\b[^>]*>/i, null], [PR_TAG, /^<\/?\w[^<>]*>/, null]]); var PR_SOURCE_CHUNK_PARTS = /^(<[^>]*>)([\s\S]*)(<\/[^>]*>)$/; function tokenizeMarkup(source) { var decorations = PR_MARKUP_LEXER(source); for (var i = 0; i < decorations.length; i += 2) { if (decorations[i + 1] === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var sourceChunk = source.substring(start, end); var match = sourceChunk.match(PR_SOURCE_CHUNK_PARTS); if (match) { decorations.splice(i, 2, start, PR_TAG, start + match[1].length, PR_SOURCE, start + match[1].length + (match[2] || '').length, PR_TAG); } } } return decorations; } var PR_TAG_LEXER = createSimpleLexer([[PR_ATTRIB_VALUE, /^\'[^\']*(?:\'|$)/, null, "'"], [PR_ATTRIB_VALUE, /^\"[^\"]*(?:\"|$)/, null, '"'], [PR_PUNCTUATION, /^[<>\/=]+/, null, '<>/=']], [[PR_TAG, /^[\w:\-]+/, /^</], [PR_ATTRIB_VALUE, /^[\w\-]+/, /^=/], [PR_ATTRIB_NAME, /^[\w:\-]+/, null], [PR_PLAIN, /^\s+/, null, ' \t\r\n']]); function splitTagAttributes(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_TAG) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var chunk = source.substring(start, end); var subDecorations = PR_TAG_LEXER(chunk, start); spliceArrayInto(subDecorations, decorations, i, 2); i += subDecorations.length - 2; } } return decorations; } function sourceDecorator(options) { var shortcutStylePatterns = [], fallthroughStylePatterns = []; if (options.tripleQuotedStrings) { shortcutStylePatterns.push([PR_STRING, /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/, null, '\'"']); } else if (options.multiLineStrings) { shortcutStylePatterns.push([PR_STRING, /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/, null, '\'"`']); } else { shortcutStylePatterns.push([PR_STRING, /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/, null, '"\'']); } fallthroughStylePatterns.push([PR_PLAIN, /^(?:[^\'\"\`\/\#]+)/, null, ' \r\n']); if (options.hashComments) { shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']); } if (options.cStyleComments) { fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]); fallthroughStylePatterns.push([PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]); } if (options.regexLiterals) { var REGEX_LITERAL = ('^/(?=[^/*])' + '(?:[^/\\x5B\\x5C]' + '|\\x5C[\\s\\S]' + '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+' + '(?:/|$)'); fallthroughStylePatterns.push([PR_STRING, new RegExp(REGEX_LITERAL), REGEXP_PRECEDER_PATTERN]); } var keywords = wordSet(options.keywords); options = null; var splitStringAndCommentTokens = createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns); var styleLiteralIdentifierPuncRecognizer = createSimpleLexer([], [[PR_PLAIN, /^\s+/, null, ' \r\n'], [PR_PLAIN, /^[a-z_$@][a-z_$@0-9]*/i, null], [PR_LITERAL, /^0x[a-f0-9]+[a-z]/i, null], [PR_LITERAL, /^(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d+)(?:e[+\-]?\d+)?[a-z]*/i, null, '123456789'], [PR_PUNCTUATION, /^[^\s\w\.$@]+/, null]]); function splitNonStringNonCommentTokens(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_PLAIN) { var start, end, chunk, subDecs; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; chunk = source.substring(start, end); subDecs = styleLiteralIdentifierPuncRecognizer(chunk, start); for (var j = 0, m = subDecs.length; j < m; j += 2) { var subStyle = subDecs[j + 1]; if (subStyle === PR_PLAIN) { var subStart = subDecs[j]; var subEnd = j + 2 < m ? subDecs[j + 2] : chunk.length; var token = source.substring(subStart, subEnd); if (token === '.') { subDecs[j + 1] = PR_PUNCTUATION; } else if (token in keywords) { subDecs[j + 1] = PR_KEYWORD; } else if (/^@?[A-Z][A-Z$]*[a-z][A-Za-z$]*$/.test(token)) { subDecs[j + 1] = token.charAt(0) === '@' ? PR_LITERAL : PR_TYPE; } } } spliceArrayInto(subDecs, decorations, i, 2); i += subDecs.length - 2; } } return decorations; } return function(sourceCode) { var decorations = splitStringAndCommentTokens(sourceCode); decorations = splitNonStringNonCommentTokens(sourceCode, decorations); return decorations; }; } var decorateSource = sourceDecorator({ keywords: ALL_KEYWORDS, hashComments: true, cStyleComments: true, multiLineStrings: true, regexLiterals: true }); function splitSourceNodes(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var subDecorations = decorateSource(source.substring(start, end)); for (var j = 0, m = subDecorations.length; j < m; j += 2) { subDecorations[j] += start; } spliceArrayInto(subDecorations, decorations, i, 2); i += subDecorations.length - 2; } } return decorations; } function splitSourceAttributes(source, decorations) { var nextValueIsSource = false; for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; var start, end; if (style === PR_ATTRIB_NAME) { start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; nextValueIsSource = /^on|^style$/i.test(source.substring(start, end)); } else if (style === PR_ATTRIB_VALUE) { if (nextValueIsSource) { start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var attribValue = source.substring(start, end); var attribLen = attribValue.length; var quoted = (attribLen >= 2 && /^[\"\']/.test(attribValue) && attribValue.charAt(0) === attribValue.charAt(attribLen - 1)); var attribSource; var attribSourceStart; var attribSourceEnd; if (quoted) { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue; } else { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue.substring(1, attribValue.length - 1); } var attribSourceDecorations = decorateSource(attribSource); for (var j = 0, m = attribSourceDecorations.length; j < m; j += 2) { attribSourceDecorations[j] += attribSourceStart; } if (quoted) { attribSourceDecorations.push(attribSourceEnd, PR_ATTRIB_VALUE); spliceArrayInto(attribSourceDecorations, decorations, i + 2, 0); } else { spliceArrayInto(attribSourceDecorations, decorations, i, 2); } } nextValueIsSource = false; } } return decorations; } function decorateMarkup(sourceCode) { var decorations = tokenizeMarkup(sourceCode); decorations = splitTagAttributes(sourceCode, decorations); decorations = splitSourceNodes(sourceCode, decorations); decorations = splitSourceAttributes(sourceCode, decorations); return decorations; } function recombineTagsAndDecorations(sourceText, extractedTags, decorations) { var html = []; var outputIdx = 0; var openDecoration = null; var currentDecoration = null; var tagPos = 0; var decPos = 0; var tabExpander = makeTabExpander(PR_TAB_WIDTH); var adjacentSpaceRe = /([\r\n ]) /g; var startOrSpaceRe = /(^| ) /gm; var newlineRe = /\r\n?|\n/g; var trailingSpaceRe = /[ \r\n]$/; var lastWasSpace = true; function emitTextUpTo(sourceIdx) { if (sourceIdx > outputIdx) { if (openDecoration && openDecoration !== currentDecoration) { html.push('</span>'); openDecoration = null; } if (!openDecoration && currentDecoration) { openDecoration = currentDecoration; html.push('<span class="', openDecoration, '">'); } var htmlChunk = textToHtml(tabExpander(sourceText.substring(outputIdx, sourceIdx))).replace(lastWasSpace ? startOrSpaceRe : adjacentSpaceRe, '$1 '); lastWasSpace = trailingSpaceRe.test(htmlChunk); html.push(htmlChunk.replace(newlineRe, '<br />')); outputIdx = sourceIdx; } } while (true) { var outputTag; if (tagPos < extractedTags.length) { if (decPos < decorations.length) { outputTag = extractedTags[tagPos] <= decorations[decPos]; } else { outputTag = true; } } else { outputTag = false; } if (outputTag) { emitTextUpTo(extractedTags[tagPos]); if (openDecoration) { html.push('</span>'); openDecoration = null; } html.push(extractedTags[tagPos + 1]); tagPos += 2; } else if (decPos < decorations.length) { emitTextUpTo(decorations[decPos]); currentDecoration = decorations[decPos + 1]; decPos += 2; } else { break; } } emitTextUpTo(sourceText.length); if (openDecoration) { html.push('</span>'); } return html.join(''); } var langHandlerRegistry = {}; function registerLangHandler(handler, fileExtensions) { for (var i = fileExtensions.length; --i >= 0; ) { var ext = fileExtensions[i]; if (!langHandlerRegistry.hasOwnProperty(ext)) { langHandlerRegistry[ext] = handler; } else if ('console' in window) { console.log('cannot override language handler %s', ext); } } } registerLangHandler(decorateSource, ['default-code']); registerLangHandler(decorateMarkup, ['default-markup', 'html', 'htm', 'xhtml', 'xml', 'xsl']); registerLangHandler(sourceDecorator({ keywords: CPP_KEYWORDS, hashComments: true, cStyleComments: true }), ['c', 'cc', 'cpp', 'cs', 'cxx', 'cyc']); registerLangHandler(sourceDecorator({ keywords: JAVA_KEYWORDS, cStyleComments: true }), ['java']); registerLangHandler(sourceDecorator({ keywords: SH_KEYWORDS, hashComments: true, multiLineStrings: true }), ['bsh', 'csh', 'sh']); registerLangHandler(sourceDecorator({ keywords: PYTHON_KEYWORDS, hashComments: true, multiLineStrings: true, tripleQuotedStrings: true }), ['cv', 'py']); registerLangHandler(sourceDecorator({ keywords: PERL_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['perl', 'pl', 'pm']); registerLangHandler(sourceDecorator({ keywords: RUBY_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['rb']); registerLangHandler(sourceDecorator({ keywords: JSCRIPT_KEYWORDS, cStyleComments: true, regexLiterals: true }), ['js']); function prettyPrintOne(sourceCodeHtml, opt_langExtension) { try { var sourceAndExtractedTags = extractTags(sourceCodeHtml); var source = sourceAndExtractedTags.source; var extractedTags = sourceAndExtractedTags.tags; if (!langHandlerRegistry.hasOwnProperty(opt_langExtension)) { opt_langExtension = /^\s*</.test(source) ? 'default-markup' : 'default-code'; } var decorations = langHandlerRegistry[opt_langExtension].call({}, source); return recombineTagsAndDecorations(source, extractedTags, decorations); } catch (e) { if ('console' in window) { console.log(e); console.trace(); } return sourceCodeHtml; } } function prettyPrint(opt_whenDone) { var isIE6 = _pr_isIE6(); var codeSegments = [document.getElementsByTagName('pre'), document.getElementsByTagName('code'), document.getElementsByTagName('xmp')]; var elements = []; for (var i = 0; i < codeSegments.length; ++i) { for (var j = 0; j < codeSegments[i].length; ++j) { elements.push(codeSegments[i][j]); } } codeSegments = null; var k = 0; function doWork() { var endTime = (PR_SHOULD_USE_CONTINUATION ? new Date().getTime() + 250 : Infinity); for (; k < elements.length && new Date().getTime() < endTime; k++) { var cs = elements[k]; if (cs.className && cs.className.indexOf('prettyprint') >= 0) { var langExtension = cs.className.match(/\blang-(\w+)\b/); if (langExtension) { langExtension = langExtension[1]; } var nested = false; for (var p = cs.parentNode; p; p = p.parentNode) { if ((p.tagName === 'pre' || p.tagName === 'code' || p.tagName === 'xmp') && p.className && p.className.indexOf('prettyprint') >= 0) { nested = true; break; } } if (!nested) { var content = getInnerHtml(cs); content = content.replace(/(?:\r\n?|\n)$/, ''); var newContent = prettyPrintOne(content, langExtension); if (!isRawContent(cs)) { cs.innerHTML = newContent; } else { var pre = document.createElement('PRE'); for (var i = 0; i < cs.attributes.length; ++i) { var a = cs.attributes[i]; if (a.specified) { var aname = a.name.toLowerCase(); if (aname === 'class') { pre.className = a.value; } else { pre.setAttribute(a.name, a.value); } } } pre.innerHTML = newContent; cs.parentNode.replaceChild(pre, cs); cs = pre; } if (isIE6 && cs.tagName === 'PRE') { var lineBreaks = cs.getElementsByTagName('br'); for (var j = lineBreaks.length; --j >= 0; ) { var lineBreak = lineBreaks[j]; lineBreak.parentNode.replaceChild(document.createTextNode('\r\n'), lineBreak); } } } } } if (k < elements.length) { setTimeout(doWork, 250); } else if (opt_whenDone) { opt_whenDone(); } } doWork(); } window['PR_normalizedHtml'] = normalizedHtml; window['prettyPrintOne'] = prettyPrintOne; window['prettyPrint'] = prettyPrint; window['PR'] = { 'createSimpleLexer': createSimpleLexer, 'registerLangHandler': registerLangHandler, 'sourceDecorator': sourceDecorator, 'PR_ATTRIB_NAME': PR_ATTRIB_NAME, 'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE, 'PR_COMMENT': PR_COMMENT, 'PR_DECLARATION': PR_DECLARATION, 'PR_KEYWORD': PR_KEYWORD, 'PR_LITERAL': PR_LITERAL, 'PR_NOCODE': PR_NOCODE, 'PR_PLAIN': PR_PLAIN, 'PR_PUNCTUATION': PR_PUNCTUATION, 'PR_SOURCE': PR_SOURCE, 'PR_STRING': PR_STRING, 'PR_TAG': PR_TAG, 'PR_TYPE': PR_TYPE }; })();
\ No newline at end of file diff --git a/templates/content/js/jquery.ajaxfileupload.js b/templates/content/js/jquery.ajaxfileupload.js index 106de906..75292776 100644 --- a/templates/content/js/jquery.ajaxfileupload.js +++ b/templates/content/js/jquery.ajaxfileupload.js @@ -1,195 +1,195 @@ -jQuery.extend({
- createUploadIframe: function(id, uri){
- //create frame
- var frameId = 'jUploadFrame' + id;
- if(window.ActiveXObject) {
- var io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '" />');
- if(typeof uri== 'boolean'){
- io.src = 'javascript:false';
- }
- else if(typeof uri== 'string'){
- io.src = uri;
- }
- }
- else {
- var io = document.createElement('iframe');
- io.id = frameId;
- io.name = frameId;
- }
- io.style.position = 'absolute';
- io.style.top = '-1000px';
- io.style.left = '-1000px';
-
- document.body.appendChild(io);
- return io;
- },
- createUploadForm: function(id, fileElementId)
- {
- //create form
- var formId = 'jUploadForm' + id;
- var fileId = 'jUploadFile' + id;
- var form = $('<form action="" method="POST" name="' + formId + '" id="' + formId
- + '" enctype="multipart/form-data"></form>');
- var oldElement = $('#' + fileElementId);
- var newElement = $(oldElement).clone();
- $(oldElement).attr('id', fileId);
- $(oldElement).before(newElement);
- $(oldElement).appendTo(form);
- //set attributes
- $(form).css('position', 'absolute');
- $(form).css('top', '-1200px');
- $(form).css('left', '-1200px');
- $(form).appendTo('body');
- return form;
- },
-
- ajaxFileUpload: function(s) {
- // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout
- s = jQuery.extend({}, jQuery.ajaxSettings, s);
- var id = new Date().getTime()
- var form = jQuery.createUploadForm(id, s.fileElementId);
- var io = jQuery.createUploadIframe(id, s.secureuri);
- var frameId = 'jUploadFrame' + id;
- var formId = 'jUploadForm' + id;
- // Watch for a new set of requests
- if ( s.global && ! jQuery.active++ )
- {
- jQuery.event.trigger( "ajaxStart" );
- }
- var requestDone = false;
- // Create the request object
- var xml = {}
- if ( s.global )
- jQuery.event.trigger("ajaxSend", [xml, s]);
- // Wait for a response to come back
- var uploadCallback = function(isTimeout)
- {
- var io = document.getElementById(frameId);
- try {
- if(io.contentWindow){
- xml.responseText = io.contentWindow.document.body ?
- io.contentWindow.document.body.innerText : null;
- xml.responseXML = io.contentWindow.document.XMLDocument ?
- io.contentWindow.document.XMLDocument : io.contentWindow.document;
-
- }
- else if(io.contentDocument)
- {
- xml.responseText = io.contentDocument.document.body ?
- io.contentDocument.document.body.textContent || document.body.innerText : null;
- xml.responseXML = io.contentDocument.document.XMLDocument ?
- io.contentDocument.document.XMLDocument : io.contentDocument.document;
- }
- }
- catch(e)
- {
- jQuery.handleError(s, xml, null, e);
- }
- if ( xml || isTimeout == "timeout")
- {
- requestDone = true;
- var status;
- try {
- status = isTimeout != "timeout" ? "success" : "error";
- // Make sure that the request was successful or notmodified
- if ( status != "error" )
- {
- // process the data (runs the xml through httpData regardless of callback)
- var data = jQuery.uploadHttpData( xml, s.dataType );
- // If a local callback was specified, fire it and pass it the data
- if ( s.success )
- s.success( data, status );
-
- // Fire the global callback
- if( s.global )
- jQuery.event.trigger( "ajaxSuccess", [xml, s] );
- } else
- jQuery.handleError(s, xml, status);
- } catch(e)
- {
- status = "error";
- jQuery.handleError(s, xml, status, e);
- }
-
- // The request was completed
- if( s.global )
- jQuery.event.trigger( "ajaxComplete", [xml, s] );
-
- // Handle the global AJAX counter
- if ( s.global && ! --jQuery.active )
- jQuery.event.trigger( "ajaxStop" );
-
- // Process result
- if ( s.complete )
- s.complete(xml, status);
-
- jQuery(io).unbind();
-
- setTimeout(function()
- { try
- {
- $(io).remove();
- $(form).remove();
-
- } catch(e) {
- jQuery.handleError(s, xml, null, e);
- }
- }, 100)
- xml = null;
- }
- }
- // Timeout checker
- if ( s.timeout > 0 ) {
- setTimeout(function(){
- // Check to see if the request is still happening
- if( !requestDone ) uploadCallback( "timeout" );
- }, s.timeout);
- }
- try
- {
- // var io = $('#' + frameId);
- var form = $('#' + formId);
- $(form).attr('action', s.url);
- $(form).attr('method', 'POST');
- $(form).attr('target', frameId);
- if(form.encoding)
- {
- form.encoding = 'multipart/form-data';
- }
- else
- {
- form.enctype = 'multipart/form-data';
- }
- $(form).submit();
-
- } catch(e)
- {
- jQuery.handleError(s, xml, null, e);
- }
- if(window.attachEvent){
- document.getElementById(frameId).attachEvent('onload', uploadCallback);
- }
- else{
- document.getElementById(frameId).addEventListener('load', uploadCallback, false);
- }
- return {abort: function () {}};
-
- },
-
- uploadHttpData: function( r, type ) {
- var data = !type;
- data = type == "xml" || data ? r.responseXML : r.responseText;
- // If the type is "script", eval it in global context
- if ( type == "script" )
- jQuery.globalEval( data );
- // Get the JavaScript object, if JSON is used.
- if ( type == "json" )
- eval( "data = " + data );
- // evaluate scripts within html
- if ( type == "html" )
- jQuery("<div>").html(data).evalScripts();
- //alert($('param', data).each(function(){alert($(this).attr('value'));}));
- return data;
- }
-})
-
+jQuery.extend({ + createUploadIframe: function(id, uri){ + //create frame + var frameId = 'jUploadFrame' + id; + if(window.ActiveXObject) { + var io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '" />'); + if(typeof uri== 'boolean'){ + io.src = 'javascript:false'; + } + else if(typeof uri== 'string'){ + io.src = uri; + } + } + else { + var io = document.createElement('iframe'); + io.id = frameId; + io.name = frameId; + } + io.style.position = 'absolute'; + io.style.top = '-1000px'; + io.style.left = '-1000px'; + + document.body.appendChild(io); + return io; + }, + createUploadForm: function(id, fileElementId) + { + //create form + var formId = 'jUploadForm' + id; + var fileId = 'jUploadFile' + id; + var form = $('<form action="" method="POST" name="' + formId + '" id="' + formId + + '" enctype="multipart/form-data"></form>'); + var oldElement = $('#' + fileElementId); + var newElement = $(oldElement).clone(); + $(oldElement).attr('id', fileId); + $(oldElement).before(newElement); + $(oldElement).appendTo(form); + //set attributes + $(form).css('position', 'absolute'); + $(form).css('top', '-1200px'); + $(form).css('left', '-1200px'); + $(form).appendTo('body'); + return form; + }, + + ajaxFileUpload: function(s) { + // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout + s = jQuery.extend({}, jQuery.ajaxSettings, s); + var id = new Date().getTime() + var form = jQuery.createUploadForm(id, s.fileElementId); + var io = jQuery.createUploadIframe(id, s.secureuri); + var frameId = 'jUploadFrame' + id; + var formId = 'jUploadForm' + id; + // Watch for a new set of requests + if ( s.global && ! jQuery.active++ ) + { + jQuery.event.trigger( "ajaxStart" ); + } + var requestDone = false; + // Create the request object + var xml = {} + if ( s.global ) + jQuery.event.trigger("ajaxSend", [xml, s]); + // Wait for a response to come back + var uploadCallback = function(isTimeout) + { + var io = document.getElementById(frameId); + try { + if(io.contentWindow){ + xml.responseText = io.contentWindow.document.body ? + io.contentWindow.document.body.innerText : null; + xml.responseXML = io.contentWindow.document.XMLDocument ? + io.contentWindow.document.XMLDocument : io.contentWindow.document; + + } + else if(io.contentDocument) + { + xml.responseText = io.contentDocument.document.body ? + io.contentDocument.document.body.textContent || document.body.innerText : null; + xml.responseXML = io.contentDocument.document.XMLDocument ? + io.contentDocument.document.XMLDocument : io.contentDocument.document; + } + } + catch(e) + { + jQuery.handleError(s, xml, null, e); + } + if ( xml || isTimeout == "timeout") + { + requestDone = true; + var status; + try { + status = isTimeout != "timeout" ? "success" : "error"; + // Make sure that the request was successful or notmodified + if ( status != "error" ) + { + // process the data (runs the xml through httpData regardless of callback) + var data = jQuery.uploadHttpData( xml, s.dataType ); + // If a local callback was specified, fire it and pass it the data + if ( s.success ) + s.success( data, status ); + + // Fire the global callback + if( s.global ) + jQuery.event.trigger( "ajaxSuccess", [xml, s] ); + } else + jQuery.handleError(s, xml, status); + } catch(e) + { + status = "error"; + jQuery.handleError(s, xml, status, e); + } + + // The request was completed + if( s.global ) + jQuery.event.trigger( "ajaxComplete", [xml, s] ); + + // Handle the global AJAX counter + if ( s.global && ! --jQuery.active ) + jQuery.event.trigger( "ajaxStop" ); + + // Process result + if ( s.complete ) + s.complete(xml, status); + + jQuery(io).unbind(); + + setTimeout(function() + { try + { + $(io).remove(); + $(form).remove(); + + } catch(e) { + jQuery.handleError(s, xml, null, e); + } + }, 100) + xml = null; + } + } + // Timeout checker + if ( s.timeout > 0 ) { + setTimeout(function(){ + // Check to see if the request is still happening + if( !requestDone ) uploadCallback( "timeout" ); + }, s.timeout); + } + try + { + // var io = $('#' + frameId); + var form = $('#' + formId); + $(form).attr('action', s.url); + $(form).attr('method', 'POST'); + $(form).attr('target', frameId); + if(form.encoding) + { + form.encoding = 'multipart/form-data'; + } + else + { + form.enctype = 'multipart/form-data'; + } + $(form).submit(); + + } catch(e) + { + jQuery.handleError(s, xml, null, e); + } + if(window.attachEvent){ + document.getElementById(frameId).attachEvent('onload', uploadCallback); + } + else{ + document.getElementById(frameId).addEventListener('load', uploadCallback, false); + } + return {abort: function () {}}; + + }, + + uploadHttpData: function( r, type ) { + var data = !type; + data = type == "xml" || data ? r.responseXML : r.responseText; + // If the type is "script", eval it in global context + if ( type == "script" ) + jQuery.globalEval( data ); + // Get the JavaScript object, if JSON is used. + if ( type == "json" ) + eval( "data = " + data ); + // evaluate scripts within html + if ( type == "html" ) + jQuery("<div>").html(data).evalScripts(); + //alert($('param', data).each(function(){alert($(this).attr('value'));})); + return data; + } +}) + diff --git a/templates/content/style/style.css b/templates/content/style/style.css index 165903ba..1cd5d438 100644 --- a/templates/content/style/style.css +++ b/templates/content/style/style.css @@ -1,1044 +1,1044 @@ -@import url(/content/style/jquery.autocomplete.css);
-@import url(/content/style/openid.css);
-@import url(/content/style/prettify.css);
-/* 公用 */
-body{background:#FFF; font-size:12px; line-height:150%; margin:0; padding:0; color:#000; font-family: "segoe ui",Helvetica,微软雅黑,宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;
-}
-div{margin:0 auto; padding:0;}
-h1,h2,h3,h4,h5,h6,ul,li,dl,dt,dd,form,img,p{margin:0; padding:0; border:none; }
-input, select {font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;}
-p{margin-bottom:4px; font-size:13px; line-height:160%;}
-a {color:#333333; text-decoration:none;}
-a:hover {text-decoration:underline;}
-.block{width:960px; height:auto;}
-.fleft{float:left;}
-.fright{float:right;}
-.tleft{text-align:left;}
-.tcenter{text-align:center;}
-.tright{text-align:right;}
-.dis{display:block;}
-.inline{display:inline;}
-.none{display:none;}
-.red{color:#CC0000;}
-.b{font-weight:bold;}
-.f10{font-size:10px;}
-.f11{font-size:11px;}
-.f12{font-size:12px;}
-.f13{font-size:13px;}
-.f14{font-size:14px;}
-.white{color:#FFFFFF;}
-.u{text-decoration:underline;}
-.spacer1{height:6px; line-height:6px; clear:both; visibility:hidden;}
-.spacer3{height:30px; line-height:30px; clear:both; visibility:hidden;}
-h1{font-size:160%;padding:5px 0 5px 0;}
-h2{font-size:140%;padding:3px 0 3px 0;}
-h3{font-size:120%;padding:3px 0 3px 0;}
-ul
-{
- list-style: disc;
- margin-left: 20px;
- padding-left:0px;
- margin-bottom: 1em;
-}
-ol
-{
- list-style: decimal;
- margin-left: 30px;
- margin-bottom: 1em;
- padding-left:0px;
-}
-pre
-{
- font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
- font-size:100%;
- margin-bottom: 10px;
- overflow: auto;
- width: 580px;
- background-color: #F5F5F5;
- padding-left:5px;
- padding-top:5px;
- padding-bottom: 20px !ie7;
-}
-
-code{
- font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
- font-size:100%;
-
-}
-
-blockquote
-{
- margin-bottom: 10px;
- margin-right: 15px;
- padding: 10px 0px 1px 10px;
- background-color: #F5F5F5;
-}
-
-/*页面布局*/
-#wrapper {width:960px;margin:auto;padding:0;}
-#roof {margin-top:0px;background:#FFF;}
-#room {padding-top:10px;background-color:#FFF;border-bottom:1px solid #777;}
-#CALeft{width:700px; float:left; position:relative;padding-left:5px}
-#CARight{width:240px; float:right; padding-right:5px}
-#CAFull{float:left;padding:0 5px 0 5px;width:950px;}
-#ground {width:100%;border-top:1px solid #000; padding-top:6px; padding-bottom:10px; text-align:center;background:#777;}
-/*#licenseLogo {top:10px;right:10px;}*/
-
-/*顶部及导航栏*/
-#top {height:20px; text-align:right; padding:3px;background-color:#ffffff;}
-#header {width:960px;}
-#top a {height:35px; text-align:right;
- /*letter-spacing:1px; */
- margin-left:20px;text-decoration:underline; font-size:12px; color:#333333;}
-#logo {padding:5px;}
-#navBar {float:clear;position:relative;display:block;width:960px;}
-#navBar .nav {margin:20px 0px 0px 16px;
- /*letter-spacing:1px; */
- }
-#navBar .nav a {color:#333; background-color:#F9F7ED;
- border-bottom: none;
- border-left: 1px solid #aaaaaa;
- border-right: 1px solid #aaaaaa;
- border-top: 1px solid #aaaaaa;
- padding:0px 12px 3px 12px; height:25px; line-height:30px;margin-left:10px; font-size:15px; font-weight:400; text-decoration:none;display: block;float: left;}
-#navBar .nav a:hover {text-decoration:underline}
-#navBar .nav a.on {height:24px;line-height:28px;
- border-top:1px solid #e66222;
- border-bottom: 1px solid #d64000;
- border-right:1px solid #e66222;
- border-left:1px solid #e66222;
- /*background:#A31E39; */
- background:#d64000;
- color:#FFF; font-weight:600; text-decoration:none}
-#navBar .nav a.special {font-size:15px; color:#B02B2C; font-weight:bold; text-decoration:none; }
-#navBar .nav a.special:hover {text-decoration:underline;}
-#navBar .nav div.focus {float:right; padding-right:0px;}
-/*搜索栏*/
-#searchBar {background-color:#9db2b1;padding:5px;} /* #B02B2C */
-#searchBar .content { }
-#searchBar .searchInput {font-size:13px; height:18px; width:400px;}
-#searchBar .searchBtn {font-size:14px; height:26px; width:80px;}
-#searchBar .options {padding-top:5px; font-size:100%;color:#EEE;
- /*letter-spacing:1px;*/
- }
-#searchBar .options INPUT {margin-left:15px;}
-#searchBar .options INPUT:hover {cursor:pointer}
-
-/*问题列表*/
-#listA {float:left; background-color:#FFF;padding:0 0px 0 0px; width:100%;}
-#listA .qstA {padding:3px 5px 0 5px; margin:0 0px 10px 0px; background:url(/content/images/quest-bg.gif) repeat-x top;}
-#listA .qstA thumb {float:left; }
-#listA .qstA H2 {font-size:15px; font-weight:800; margin:8px auto;padding:0px;}
-#listA .qstA H2 a {color:#663333; }
-#listA .qstA .stat {font-size:13px;
- /*letter-spacing:1px;*/
- float:right;}
-#listA .qstA .stat span {margin-right:5px;}
-#listA .qstA .stat td {min-width:40px;text-align:center;}
-#listA .qstA .stat .num {font-family:arial;color:#905213; font-size:20px; font-weight:800;}
-#listA .qstA .stat .unit {color:#777;}
-#listA .qstA .from {margin-top:5px; font-size:13px;}
-#listA .qstA .from .score {font-family:arial;}
-#listA .qstA .date {margin-left:20px; color:#777;}
-#listA .qstA .wiki {color:#663333;font-size:12px;}
-#listA .qstA .from a {}
-#listA .qstA .from IMG {vertical-align:middle;}
-#listA .qstA .author {font-weight:400; }
-#listA .qstA .author a{ }
-#listA .qstA .summary{margin-right:5px;}
-.evenMore {font-size:14px; font-weight:800;}
-.questions-count{font-size:32px;font-family:arial;font-weight:600;padding:5px;color:#777;}
-
-/*内容块*/
-.boxA {background:#777; padding:6px; margin-bottom:8px;}
-.boxA H3 {font-size:13px; font-weight:800; color:#FFF; margin:0; padding:0; margin-bottom:4px;}
-.boxA .body {border:1px solid #999; padding:8px; background:#FFF; font-size:13px;}
-.boxA .more {padding:2px; text-align:right; font-weight:800;}
-.boxB {background:#F9F7CD; padding:6px; margin-bottom:8px;}
-.boxB H3 {font-size:13px; font-weight:800; color:#000; margin:0; padding:0 0 0 15px; margin-bottom:4px; background:url(/content/images/dot-g.gif) no-repeat left center;}
-.boxB .body {border:1px solid #FFFF88; padding:8px; background:#FFF; font-size:13px; line-height:160%;}
-.boxB .more {padding:1px; text-align:right; font-weight:800;}
-.boxC {background:#F5F5F5; padding:6px; margin-bottom:8px;}
-/*分页*/
-.pager {margin-top:10px; margin-bottom:16px; float:left;}
-.pagesize {margin-top:10px; margin-bottom:16px; float:right;}
-
-/** PAGINATOR **/
-.paginator {
- padding:5px 0 10px 0;
- font:normal 12px arial;
-}
-
-.paginator .prev-na,
-.paginator .next-na {
- padding:.3em;
- font:bold .875em arial;
-}
-
-.paginator .prev-na,
-.paginator .next-na {
- border:1px solid #ccc;
- background-color:#f9f9f9;
- color:#aaa;
- font-weight:normal;
-}
-
-.paginator .prev a, .paginator .prev a:visited,
-.paginator .next a, .paginator .next a:visited {
- border:1px solid #fff;
- background-color:#fff;
- color:#777;
- padding:.3em;
- font:bold 100% arial;
-}
-
-.paginator .prev, .paginator .prev-na { margin-right:.5em; }
-.paginator .next, .paginator .next-na { margin-left:.5em; }
-
-.paginator .page a, .paginator .page a:visited, .paginator .curr {
- padding:.25em;
- font:normal .875em verdana;
- border:1px solid #ccc;
- background-color:#fff;
- margin:0em .25em;
- color:#777;
-}
-
-.paginator .curr {
- background-color:#777;
- color:#fff;
- border:1px solid #777;
- font-weight:bold;
-}
-
-.paginator .page a:hover,
-.paginator .curr a:hover,
-.paginator .prev a:hover,
-.paginator .next a:hover {
- color:#fff;
- background-color:#777;
- border:1px solid #777;
- text-decoration:none;
-}
-
-.paginator .text{
- color:#777;
- padding:.3em;
- font:bold 100% arial;
-}
-
-.paginator-container{
- float:right;
- padding:10px 0 10px 0;
-}
-
-.paginator-container-left{
- padding:5px 0 10px 0;
-}
-
-/*标签*/
-.tag {font-size:100%; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;}
-.tags {font-family:arial; line-height:200%; display:block; margin-top:5px;}
-.tags a {font-size:100%; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;}
-.tags a:hover {background-color:#fFF;color:#333;}
-.tag-number {font-weight:700;font-family:arial;}
-
-/*奖牌*/
-a.medal { font-size:14px; line-height:250%; font-weight:800; color:#333; text-decoration:none; background:url(/content/images/medala.gif) no-repeat; border-left:1px solid #EEE; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:4px 12px 4px 6px;}
-a:hover.medal {color:#333; text-decoration:none; background:url(/content/images/medala_on.gif) no-repeat; border-left:1px solid #E7E296; border-top:1px solid #E7E296; border-bottom:1px solid #D1CA3D; border-right:1px solid #D1CA3D;}
-
-/*Tab栏*/
-.tabBar{background-color:#FFF;border-bottom: 1px solid #666;height: 30px; width: 100%;clear:both; margin-bottom:3px;}
-.tabsA {background-color:#FFF;float:right;position:relative;display:block;font-weight:bold;height:20px;}
-.tabsB {background-color:#FFF;float:left;position:relative;display:block;font-weight:bold;height:20px;}
-.tabsA a.on, .tabsA a.on:hover,.tabsB a.on, .tabsB a.on:hover {background: #fff;
- color:#333;
- border: 1px solid #777;
- border-bottom:2px solid #FFF;
- height: 25px;
- line-height: 26px;
- margin-top: 3px;
- padding: 0px 11px 0px 11px;}
-
-.tabsA a {background: #eee;
- border: 1px solid #eee;
- color: #777;
- display: block;
- float: left;
- height: 22px;
- line-height: 28px;
- margin: 5px 4px 0 0;
- padding: 0 11px 0 11px;
- text-decoration: none;
-}
-.tabsB a {background: #eee;
- border: 1px solid #eee;
- color: #777;
- display: block;
- float: left;
- height: 22px;
- line-height: 28px;
- margin: 5px 0px 0 4px;
- padding: 0 11px 0 11px;
- text-decoration: none;
-}
-.tabsA a:hover, .tabsB a:hover {background: #fff;border: 1px solid #777;border-bottom:3px solid #FFF;}
-.headlineA {font-size:13px; border-bottom:1px solid #777; padding-bottom:2px; font-weight:800; margin-bottom:12px; text-align:right; height:30px;}
-.headQuestions {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;}
-.headAnswers {float:left; padding:3px; font-size:18px; font-weight:800; background:url(/content/images/ico_answers.gif) left 2px no-repeat; padding-left:24px;}
-.headTags {float:left; padding:3px; font-size:18px; font-weight:800; background:url(/content/images/ico_tags.gif) no-repeat; padding-left:24px;}
-.headUsers {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;}
-.headMedals {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;}
-.headLogin {float:left; padding:3px; font-size:15px; font-weight:800; background:url(/content/images/ico_login.gif) no-repeat; padding-left:24px;}
-.headNormal {text-align:left;padding:3px; font-size:15px; margin-bottom:12px; font-weight:800;border-bottom:1px solid #777;}
-.headUser {text-align:left;padding:5px; font-size:20px;
- /*letter-spacing:1px;*/
- margin-bottom:12px; font-weight:800;border-bottom:1px solid #777;}
-/*RSS订阅*/
-#feeds {margin:10px 0; }
-#feeds a {background:url(/content/images/feed-icon-small.png) no-repeat 0; padding-left:18px; font-weight:700; font-size:13px; }
-
-/*问题*/
-#question {margin-bottom:30px;}
-#question h1{font-size:15px;background:#CCC; padding:6px 8px;;}
-#question .body{background:#F7F7F7; padding:20px 10px;}
-.starter {padding:10px; background:#E0EAF1;}
-.vote {font-size:20px; color:#666; font-weight:800;}
-.questions-related{font-weight:700;word-wrap:break-word;}
-.questions-related p{line-height:20px; margin-bottom:10px;font-size:100%;}
-.question-status{
- margin-top:10px;
- padding: 20px;
- background-color:#F5F5F5;
- text-align:center;
-}
-.question-status h3{font-size:125%;}
-.question-body{
- min-height:100px;
- font-size:13px;
- line-height:20px;
-}
-.question-body IMG{
- max-width:600px;
-}
-.question-mark{
- background-color:#E9E9FF;
- text-align:left;
- padding:5px;
- overflow:hidden;
-}
-.question-edit{
- text-align:left;
- overflow:hidden;
-}
-.vote-buttons {float:left;text-align:center;}
-.vote-buttons IMG{cursor:pointer;}
-.vote-number{
- font-family:Arial;
- padding:0px 0 3px 0;
- font-size:140%;
- font-weight:bold;
- color:#777;
-}
-.question-img-upvote:hover{background:url(/content/images/vote-arrow-up-on.png)}
-.question-img-downvote:hover{background:url(/content/images/vote-arrow-down-on.png)}
-.question-img-favorite:hover{background:url(/content/images/vote-favorite-on.png)}
-.favorite-number{padding:0px;font-size:100%; font-family:Arial;font-weight:bold;color:#777;}
-.vote-notification
-{
- z-index: 1;
- cursor: pointer;
- display: none;
- position: absolute;
- padding: 15px;
- color: White;
- background-color: darkred;
- text-align: center;
-}
-.vote-notification a
-{
- color: White;
- text-decoration:underline;
-}
-.offensive-flag a{
- color:#777;
- padding:3px;
- cursor:pointer;
-}
-
-.offensive-flag a:hover{
- background-color:#777;
- text-decoration:none;
- color:#fff;
-}
-
-.linksopt a{
- color:#777;
- padding:3px;
- cursor:pointer;
-}
-
-.linksopt a:hover{
- background-color:#777;
- text-decoration:none;
- color:#fff;
-}
-
-.action-link a{
- color:#777;
- padding:3px;
- cursor:pointer;
-}
-
-.action-link a:hover{
- background-color:#777;
- text-decoration:none;
- color:#fff;
-}
-.action-link-separator{
- color:#ccc;
-}
-.wiki-category{
- margin-left:5px;
- color:#999;
- font-size:90%;
-}
-
-div.comments {
- line-height:150%;
- padding:10px 0;
-}
-
-div.post-comments{
- width:100%;
-}
-
-form.post-comments textarea {
- height:6em;
- margin-bottom:4px;
-}
-
-form.post-comments input {
- margin-left:10px;
- margin-top:1px;
- vertical-align:top;
- width:100px;
-}
-span.text-counter {
- margin-right:20px;
-}
-
-span.form-error {
- color:#990000;
- font-weight:normal;
- margin-left:5px;
-}
-
-div.comments-container, div.comments-container-accepted, div.comments-container-owner, div.comments-container-deleted {
- display:none;
- margin-top:-1px;
- padding:0 5px 5px;
-}
-
-div.comments-container, a.comments-link {
- background-color:#EEEEEE;
-}
-
-.post-comments a {
- color:#888888;
- padding:0 3px 2px;
-}
-
-a.comments-link, a.comments-link-accepted, a.comments-link-owner, a.comments-link-deleted {
- color:black;
- padding:2px;
- cursor:pointer;
-}
-
-.post-comments a:hover {
- background-color:#777777;
- color:white;
- text-decoration:none;
-}
-
-a.comment-user, a.comment-user:hover {
- background-color:inherit;
- color:#0077CC;
- padding:0;
-}
-
-a.comment-user:hover {
- text-decoration:underline;
-}
-/*回答*/
-#answers {}
-.answer{
- border-bottom:1px dotted #666666;
- padding-bottom:20px;
- padding-top:20px;
- width: 100%;
- margin-bottom:10px;
-}
-.answer-body{
- min-height:80px;
- font-size:13px;
- line-height:20px;
-}
-
-.answer-body IMG{
- max-width:600px;
-}
-
-.accepted-answer{
- background-color:#EBFFE6;
- border-bottom-color:#9BD59B;
-}
-
-.accepted-answer .comments-link{
- background-color:#CCFFBF;
-}
-
-.accepted-answer .comments-container{
- background-color:#CCFFBF;
-}
-
-.answered
-{
- background: #CCC;
- color: #999;
-}
-
-.answered-accepted
-{
- background: #CCC;
- color: #663333;
-}
-
-.unanswered
-{
- background: #777;
- color: white;
-}
-
-.answered-by-owner
-{
- background: #E9E9FF;
-}
-
-.answered-by-owner .comments-link
-{
- background-color:#E6ECFF;
-}
-
-.answered-by-owner .comments-container
-{
- background-color:#E6ECFF;
-}
-
-.answered-accepted strong
-{
- color: #E1E818;
-}
-
-.answer-img-accept:hover{background:url(/content/images/vote-accepted-on.png)}
-
-.deleted{
- background:#F4E7E7 none repeat scroll 0 0;
-}
-
-/*标签列表*/
-.tagsbox {}
-.tagsbox a {color:#000;line-height:30px;margin-right:10px;font-size:100%;background-color:#F9F7ED;padding:3px;}
-.tagsbox a:hover {text-decoration:none;background-color:#F9F7CD;color:#B02B2C;}
-.tagsList {margin:0; list-style-type:none;padding:0px;min-height:360px;}
-.tagsList li {width:235px; float:left;}
-.badge-list{margin:0; list-style-type:none;}
-/*登录*/
-.list-item{margin-left:15px;}
-.list-item LI{list-style-type:disc; font-size:13px; line-height:20px; margin-bottom:10px;}
-/* openid styles */
-.form-row{line-height:25px;}
-.submit-row{line-height:30px;padding-top:10px;}
-.errors{line-height:20px;color:red;}
-.error{color:red;}
-.error-list li{padding:5px;}
-.login{margin-bottom:10px;}
-.fieldset{border:solid 1px #777;margin-top:10px;padding:10px;}
-.openid-input{background:url(/content/images/openid.gif) no-repeat;padding-left:15px;cursor:pointer;}
-.openid-login-input{
- background-position:center left;
- background:url(/content/images/openid.gif) no-repeat 0% 50%;
- padding:5px 5px 5px 15px;
- cursor:pointer;
- font-family:Trebuchet MS;
- font-weight:300;
- font-size:150%;
- width:500px;
-}
-
-.openid-login-submit{
- height:40px;
- width:80px;
- line-height:40px;
- cursor:pointer;
- border:1px solid #777;
- font-weight:bold;
- font-size:120%;
-}
-
-.openid-samples{
-
-}
-
-.openid-samples .list, .list li{
- font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;
- list-style:none !important;
- margin-left:-30px !important;
- line-height:20px !important;
-}
-
-/*表单相关*/
-span.form-error {
- color:#990000;
- font-size:90%;
- font-weight:normal;
- margin-left:5px;
-}
-.title-desc{
- color:#999;
- font-size:90%;
-}
-
-/*adjustment for editor preview*/
-#editor{
- font-size:100%;
- min-height:200px;
- line-height: 18px;
- width:100%;
-}
-
-.wmd-preview{
- margin-top:10px;
- padding:6px;
- width:100%;
- background-color:#F5F5F5;
- min-height:20px;
-}
-.wmd-preview pre{
- background-color:#E7F1F8;
-
-}
-
-.wmd-preview blockquote
-{
- background-color: #eee;
-}
-
-.wmd-preview IMG{
- max-width:600px;
-}
-.preview-toggle{
- font-weight:600;
- width:100%;
- color:#aaa;
- /*letter-spacing:1px;*/
- text-align:left;
-}
-
-.preview-toggle span:hover{
- cursor:pointer;
-}
-
-.edit-content-html{
- border-top:1px dotted #D8D2A9;
- border-bottom:1px dotted #D8D2A9;
- margin:5px 0 5px 0;
-}
-
-/*修订记录*/
-
-#revisions{
- width:950px;
-}
-
-.revision{
- margin:10px 0 10px 0;
- width:100%;
- font-size:13px;
-}
-
-.revision .header{
- background-color:#eee;
- padding:5px;
- cursor:pointer;
-}
-
-.revision .author{
- background-color:#E9E9FF;
-}
-
-.revision .summary{
- padding: 5px 0 10px 0;
-}
-
-.revision .summary span{
- background-color:yellow;
- display:inline;
-}
-.revision h1{
- font-size:130%;
- font-weight:600;
- padding:15px 0 15px 0;
-}
-
-.revision-mark{
- width:200px;
- text-align:left;
- font-size:90%;
- overflow:hidden;
-}
-
-.revision-number{
- font-size:300%;
- font-weight:bold;
- font-family:arial;
-}
-
-.revision .body{
- padding-left:10px;
- margin-bottom:50px;
-}
-.revision .answerbody{
- padding:10px 0 5px 10px;
-}
-
-/* Revision pages */
-del { color: #FF5F5F; }
-del .post-tag{
-color: #FF5F5F;
-}
-ins { background-color: #97ff97;}
-ins .post-tag{
-background-color: #97ff97;
-}
-
-/*用户资料页面*/
-.count {font-family:Arial;font-size:200%;font-weight:700;color:#777}
-.scoreNumber{font-family:Arial;font-size:35px;font-weight:800;color:#777;line-height:40px;
- /*letter-spacing:0px*/
- }
-.user-details{font-size:13px;}
-.user-about{background-color:#EEEEEE;height:200px;line-height:20px; overflow:auto;padding:10px;width:90%;}
-.user-edit-link {background:url(/content/images/edit.png) no-repeat; padding-left:20px; font-weight:600;}
-.favorites-count-off {
- color:#919191;
- float:left;
- padding:3px;
- margin:10px 0 0 0 ;
- text-align:center;
-}
-
-.favorites-count {
- color:#D4A849;
- float:left;
- padding:3px;
- margin:10px 0 0 0 ;
- text-align:center;
-}
-.favorites-empty{
- width: 32px; height: 45px; float: left;
-}
-.question-summary {
- border-bottom:1px dotted #999999;
- float:left;
- overflow:hidden;
- padding:11px 0;
- width:670px;
-}
-
-.user-info-table{
-width:950;margin-bottom:10px;
-}
-
-.user-stats-table .question-summary {
- width:800px;
-}
-
-.narrow .stats {
- background:transparent none repeat scroll 0 0;
- float:left;
- height:48px;
- margin:0 0 0 7px;
- padding:0;
- width:auto;
- font-family:Arial;
-}
-
-.stats div {
- font-size:11px;
- text-align:center;
-}
-
-.narrow .votes {
- background:#EEEEEE none repeat scroll 0 0;
- float:left;
- height:42px;
- margin:0 3px 0 0;
- padding:5px;
- width:46px;
- text-align:center;
- -moz-border-radius: 5px;
- -khtml-border-radius: 5px;
- -webkit-border-radius: 5px;
-}
-
-.narrow .summary {
- width:620px;
-}
-
-.narrow .summary h3 {
- padding:0px;
- margin:0px;
-}
-
-.narrow .views {
- float:left;
- height:42px;
- margin:0 7px 0 0;
- /*padding:5px 0 5px 4px;*/
- padding: 5px;
- width:46px;
- text-align:center;
- -moz-border-radius: 5px;
- -khtml-border-radius: 5px;
- -webkit-border-radius: 5px;
- color:#777;
-}
-
-.narrow .status {
- float:left;
- height:42px;
- margin:0 3px 0 0;
- padding:5px;
- width:46px;
- text-align:center;
- -moz-border-radius: 5px;
- -khtml-border-radius: 5px;
- -webkit-border-radius: 5px;
-}
-
-.narrow .vote-count-post {
- font-weight:800;
- margin:0;
- font-size: 190%; color:#555; line-height:20px;
-}
-.narrow .answer-count-post{font-weight:800;margin:0; font-size: 190%; }
-.narrow .views-count-post{font-weight:800;margin:0; font-size: 190%;}
-div.started {
- color:#999999;
- float:right;
- line-height:18px;
-
-}
-
-.narrow div.started {
- line-height:inherit;
- padding-top:4px;
- white-space:nowrap;
- width:auto;
-}
-
-.relativetime {
- font-weight:bold;
- text-decoration:none;
-}
-
-div.started a {
- font-weight:bold;
-}
-
-div.started .reputation-score {
- margin-left:1px;
-}
-
-.narrow .tags{float:left;}
-
-.answer-summary {
- clear:both;
- padding:3px;
-}
-
-.answer-votes {
- background-color:#EEEEEE;
- color:#555555;
- float:left;
- font-family:Arial;
- font-size:110%;
- font-weight:bold;
- height:15px;
- padding:4px 4px 5px;
- text-align:center;
- text-decoration:none;
- width:20px;
- margin-right:10px;
-}
-.vote-count{font-family:Arial; font-size:160%; font-weight:700; color:#777;}
-.user-action{
-
-}
-.user-action-1{
- font-weight:bold;
- color:#333;
-}
-.user-action-2{
- font-weight:bold;
- color:#CCC;
-}
-.user-action-3{
- color:#333;
-}
-.user-action-4{
- color:#333;
-}
-.user-action-5{
- color:darkred;
-}
-.user-action-6{
- color:darkred;
-}
-.user-action-7{
- color:#333;
-}
-.user-action-8{
- padding:3px;
- font-weight:bold;
- background-color:#CCC;
- color:#663333;
-}
-
-.revision-summary{
- background-color:#FFFE9B;
- padding:2px;
-}
-.question-title-link a{
- font-weight:bold;
- color:#0077CC;
-}
-.answer-title-link a{
- color:#333;
-}
-
-.post-type-1 a {
- font-weight:bold;
-
-}
-.post-type-3 a {
- font-weight:bold;
-
-}
-.post-type-5 a {
- font-weight:bold;
- }
-.post-type-2 a{
- color:#333;
-}
-.post-type-4 a{
- color:#333;
-}
-.post-type-6 a{
- color:#333;
-}
-.post-type-8 a{
- color:#333;
-}
-
-
-/*读书频道*/
-.bookInfo {float:left; width:940px;padding:5px;}
-.bookCover {float:left; width:200px;}
-.bookCover img{border:1px solid #ccc;max-width:200px;}
-.bookSummary {float:left; font-size:13px;}
-.blogRss {float:right;margin:0 10px 0 0;width:460px;height:240px;background-color:#EEE; padding:5px;}
-.bookQuestions {margin-bottom:10px;}
-.bookFeed {float:right;}
-.bookAsk{
- /*letter-spacing:1px; */
- float:right;margin:-30px 10px 0 0; padding:3px 5px 3px 5px;}
-.bookAsk a {font-size:15px; color:#FFF; font-weight:bold; text-decoration:none;background-color:#EC7000;padding:3px 6px 3px 6px; }
-.bookAsk a:hover {text-decoration:underline;}
-
-
-/*其他全局样式*/
-.hilite { background-color: #ff0; }
-.hilite1 { background-color: #ff0; }
-.hilite2 { background-color: #f0f; }
-.hilite3 { background-color: #0ff; }
-.userStatus {margin-left:12px; color:#FFF; float:right;}
-.userStatus a {color:#FFF;}
-.gold, .badge1 {color:#FFCC00;}
-.silver, .badge2 {color:#CCCCCC;}
-.bronze, .badge3 {color:#CC9933;}
-.score {font-weight:800; color:#333;}
-.footerLinks {color:#EEE; font-size:13px;
- /* letter-spacing:1px;*/
- }
-.footerLinks a {color:#FFF; font-size:13px;}
-.subSearch {margin-bottom:12px; padding:4px;}
-a.comment {background:#EEE; color:#993300; padding:4px;}
-a.permLink {padding:2px;}
-a.offensive {color:#999;}
-ul.bulleta li {background:url(/content/images/bullet_green.gif) no-repeat 0px 2px; padding-left:16px; margin-bottom:4px;}
-.user {padding:5px; line-height:140%; width:170px;}
-.user ul {margin:0; list-style-type:none;}
-.user .thumb{clear:both;float:left; margin-right:4px; display:inline;}
-.yellowbg{background:yellow;}
-.subtitle{line-height:30px;font-size:15px; font-weight:700;}
-.message{padding:5px;font-weight:bold;background-color:#eee;margin:10px 0 10px 0;}
-.warning{color:red;}
-.darkred{color:darkred;}
-.submit{
- cursor:pointer;
- /*letter-spacing:1px;*/
- background-color:#D4D0C8;
- height:40px;
- border:1px solid #777;
-/* width:100px; */
- font-weight:bold;
- font-size:120%;}
-.submit:hover{text-decoration:underline;}
-.ask-body{padding-right:10px;}
-.thousand{color:orange;}
-.notify
-{
- position: fixed;
- top: 0px;
- left: 0px;
- width: 100%;
- z-index: 100;
- padding: 7px 0 5px 0;
- text-align: center;
- font-size: 130%;
- font-weight: Bold;
- color: #444;
- background-color: #F4A83D;
-}
-
-.notify span
-{
- float: left;
- width: 95%;
- text-align: center;
-}
-
-.close-notify
-{
- float: right;
- margin-right: 20px;
- color: #735005;
- text-decoration: none;
- background-color: #FAD163;
- border: 2px #735005 solid;
- padding-left: 3px;
- padding-right: 3px;
- cursor:pointer;
-}
+@import url(/content/style/jquery.autocomplete.css); +@import url(/content/style/openid.css); +@import url(/content/style/prettify.css); +/* 公用 */ +body{background:#FFF; font-size:12px; line-height:150%; margin:0; padding:0; color:#000; font-family: "segoe ui",Helvetica,微软雅黑,宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif; +} +div{margin:0 auto; padding:0;} +h1,h2,h3,h4,h5,h6,ul,li,dl,dt,dd,form,img,p{margin:0; padding:0; border:none; } +input, select {font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;} +p{margin-bottom:4px; font-size:13px; line-height:160%;} +a {color:#333333; text-decoration:none;} +a:hover {text-decoration:underline;} +.block{width:960px; height:auto;} +.fleft{float:left;} +.fright{float:right;} +.tleft{text-align:left;} +.tcenter{text-align:center;} +.tright{text-align:right;} +.dis{display:block;} +.inline{display:inline;} +.none{display:none;} +.red{color:#CC0000;} +.b{font-weight:bold;} +.f10{font-size:10px;} +.f11{font-size:11px;} +.f12{font-size:12px;} +.f13{font-size:13px;} +.f14{font-size:14px;} +.white{color:#FFFFFF;} +.u{text-decoration:underline;} +.spacer1{height:6px; line-height:6px; clear:both; visibility:hidden;} +.spacer3{height:30px; line-height:30px; clear:both; visibility:hidden;} +h1{font-size:160%;padding:5px 0 5px 0;} +h2{font-size:140%;padding:3px 0 3px 0;} +h3{font-size:120%;padding:3px 0 3px 0;} +ul +{ + list-style: disc; + margin-left: 20px; + padding-left:0px; + margin-bottom: 1em; +} +ol +{ + list-style: decimal; + margin-left: 30px; + margin-bottom: 1em; + padding-left:0px; +} +pre +{ + font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace; + font-size:100%; + margin-bottom: 10px; + overflow: auto; + width: 580px; + background-color: #F5F5F5; + padding-left:5px; + padding-top:5px; + padding-bottom: 20px !ie7; +} + +code{ + font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace; + font-size:100%; + +} + +blockquote +{ + margin-bottom: 10px; + margin-right: 15px; + padding: 10px 0px 1px 10px; + background-color: #F5F5F5; +} + +/*页面布局*/ +#wrapper {width:960px;margin:auto;padding:0;} +#roof {margin-top:0px;background:#FFF;} +#room {padding-top:10px;background-color:#FFF;border-bottom:1px solid #777;} +#CALeft{width:700px; float:left; position:relative;padding-left:5px} +#CARight{width:240px; float:right; padding-right:5px} +#CAFull{float:left;padding:0 5px 0 5px;width:950px;} +#ground {width:100%;border-top:1px solid #000; padding-top:6px; padding-bottom:10px; text-align:center;background:#777;} +/*#licenseLogo {top:10px;right:10px;}*/ + +/*顶部及导航栏*/ +#top {height:20px; text-align:right; padding:3px;background-color:#ffffff;} +#header {width:960px;} +#top a {height:35px; text-align:right; + /*letter-spacing:1px; */ + margin-left:20px;text-decoration:underline; font-size:12px; color:#333333;} +#logo {padding:5px;} +#navBar {float:clear;position:relative;display:block;width:960px;} +#navBar .nav {margin:20px 0px 0px 16px; + /*letter-spacing:1px; */ + } +#navBar .nav a {color:#333; background-color:#F9F7ED; + border-bottom: none; + border-left: 1px solid #aaaaaa; + border-right: 1px solid #aaaaaa; + border-top: 1px solid #aaaaaa; + padding:0px 12px 3px 12px; height:25px; line-height:30px;margin-left:10px; font-size:15px; font-weight:400; text-decoration:none;display: block;float: left;} +#navBar .nav a:hover {text-decoration:underline} +#navBar .nav a.on {height:24px;line-height:28px; + border-top:1px solid #e66222; + border-bottom: 1px solid #d64000; + border-right:1px solid #e66222; + border-left:1px solid #e66222; + /*background:#A31E39; */ + background:#d64000; + color:#FFF; font-weight:600; text-decoration:none} +#navBar .nav a.special {font-size:15px; color:#B02B2C; font-weight:bold; text-decoration:none; } +#navBar .nav a.special:hover {text-decoration:underline;} +#navBar .nav div.focus {float:right; padding-right:0px;} +/*搜索栏*/ +#searchBar {background-color:#9db2b1;padding:5px;} /* #B02B2C */ +#searchBar .content { } +#searchBar .searchInput {font-size:13px; height:18px; width:400px;} +#searchBar .searchBtn {font-size:14px; height:26px; width:80px;} +#searchBar .options {padding-top:5px; font-size:100%;color:#EEE; + /*letter-spacing:1px;*/ + } +#searchBar .options INPUT {margin-left:15px;} +#searchBar .options INPUT:hover {cursor:pointer} + +/*问题列表*/ +#listA {float:left; background-color:#FFF;padding:0 0px 0 0px; width:100%;} +#listA .qstA {padding:3px 5px 0 5px; margin:0 0px 10px 0px; background:url(/content/images/quest-bg.gif) repeat-x top;} +#listA .qstA thumb {float:left; } +#listA .qstA H2 {font-size:15px; font-weight:800; margin:8px auto;padding:0px;} +#listA .qstA H2 a {color:#663333; } +#listA .qstA .stat {font-size:13px; + /*letter-spacing:1px;*/ + float:right;} +#listA .qstA .stat span {margin-right:5px;} +#listA .qstA .stat td {min-width:40px;text-align:center;} +#listA .qstA .stat .num {font-family:arial;color:#905213; font-size:20px; font-weight:800;} +#listA .qstA .stat .unit {color:#777;} +#listA .qstA .from {margin-top:5px; font-size:13px;} +#listA .qstA .from .score {font-family:arial;} +#listA .qstA .date {margin-left:20px; color:#777;} +#listA .qstA .wiki {color:#663333;font-size:12px;} +#listA .qstA .from a {} +#listA .qstA .from IMG {vertical-align:middle;} +#listA .qstA .author {font-weight:400; } +#listA .qstA .author a{ } +#listA .qstA .summary{margin-right:5px;} +.evenMore {font-size:14px; font-weight:800;} +.questions-count{font-size:32px;font-family:arial;font-weight:600;padding:5px;color:#777;} + +/*内容块*/ +.boxA {background:#777; padding:6px; margin-bottom:8px;} +.boxA H3 {font-size:13px; font-weight:800; color:#FFF; margin:0; padding:0; margin-bottom:4px;} +.boxA .body {border:1px solid #999; padding:8px; background:#FFF; font-size:13px;} +.boxA .more {padding:2px; text-align:right; font-weight:800;} +.boxB {background:#F9F7CD; padding:6px; margin-bottom:8px;} +.boxB H3 {font-size:13px; font-weight:800; color:#000; margin:0; padding:0 0 0 15px; margin-bottom:4px; background:url(/content/images/dot-g.gif) no-repeat left center;} +.boxB .body {border:1px solid #FFFF88; padding:8px; background:#FFF; font-size:13px; line-height:160%;} +.boxB .more {padding:1px; text-align:right; font-weight:800;} +.boxC {background:#F5F5F5; padding:6px; margin-bottom:8px;} +/*分页*/ +.pager {margin-top:10px; margin-bottom:16px; float:left;} +.pagesize {margin-top:10px; margin-bottom:16px; float:right;} + +/** PAGINATOR **/ +.paginator { + padding:5px 0 10px 0; + font:normal 12px arial; +} + +.paginator .prev-na, +.paginator .next-na { + padding:.3em; + font:bold .875em arial; +} + +.paginator .prev-na, +.paginator .next-na { + border:1px solid #ccc; + background-color:#f9f9f9; + color:#aaa; + font-weight:normal; +} + +.paginator .prev a, .paginator .prev a:visited, +.paginator .next a, .paginator .next a:visited { + border:1px solid #fff; + background-color:#fff; + color:#777; + padding:.3em; + font:bold 100% arial; +} + +.paginator .prev, .paginator .prev-na { margin-right:.5em; } +.paginator .next, .paginator .next-na { margin-left:.5em; } + +.paginator .page a, .paginator .page a:visited, .paginator .curr { + padding:.25em; + font:normal .875em verdana; + border:1px solid #ccc; + background-color:#fff; + margin:0em .25em; + color:#777; +} + +.paginator .curr { + background-color:#777; + color:#fff; + border:1px solid #777; + font-weight:bold; +} + +.paginator .page a:hover, +.paginator .curr a:hover, +.paginator .prev a:hover, +.paginator .next a:hover { + color:#fff; + background-color:#777; + border:1px solid #777; + text-decoration:none; +} + +.paginator .text{ + color:#777; + padding:.3em; + font:bold 100% arial; +} + +.paginator-container{ + float:right; + padding:10px 0 10px 0; +} + +.paginator-container-left{ + padding:5px 0 10px 0; +} + +/*标签*/ +.tag {font-size:100%; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;} +.tags {font-family:arial; line-height:200%; display:block; margin-top:5px;} +.tags a {font-size:100%; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;} +.tags a:hover {background-color:#fFF;color:#333;} +.tag-number {font-weight:700;font-family:arial;} + +/*奖牌*/ +a.medal { font-size:14px; line-height:250%; font-weight:800; color:#333; text-decoration:none; background:url(/content/images/medala.gif) no-repeat; border-left:1px solid #EEE; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:4px 12px 4px 6px;} +a:hover.medal {color:#333; text-decoration:none; background:url(/content/images/medala_on.gif) no-repeat; border-left:1px solid #E7E296; border-top:1px solid #E7E296; border-bottom:1px solid #D1CA3D; border-right:1px solid #D1CA3D;} + +/*Tab栏*/ +.tabBar{background-color:#FFF;border-bottom: 1px solid #666;height: 30px; width: 100%;clear:both; margin-bottom:3px;} +.tabsA {background-color:#FFF;float:right;position:relative;display:block;font-weight:bold;height:20px;} +.tabsB {background-color:#FFF;float:left;position:relative;display:block;font-weight:bold;height:20px;} +.tabsA a.on, .tabsA a.on:hover,.tabsB a.on, .tabsB a.on:hover {background: #fff; + color:#333; + border: 1px solid #777; + border-bottom:2px solid #FFF; + height: 25px; + line-height: 26px; + margin-top: 3px; + padding: 0px 11px 0px 11px;} + +.tabsA a {background: #eee; + border: 1px solid #eee; + color: #777; + display: block; + float: left; + height: 22px; + line-height: 28px; + margin: 5px 4px 0 0; + padding: 0 11px 0 11px; + text-decoration: none; +} +.tabsB a {background: #eee; + border: 1px solid #eee; + color: #777; + display: block; + float: left; + height: 22px; + line-height: 28px; + margin: 5px 0px 0 4px; + padding: 0 11px 0 11px; + text-decoration: none; +} +.tabsA a:hover, .tabsB a:hover {background: #fff;border: 1px solid #777;border-bottom:3px solid #FFF;} +.headlineA {font-size:13px; border-bottom:1px solid #777; padding-bottom:2px; font-weight:800; margin-bottom:12px; text-align:right; height:30px;} +.headQuestions {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;} +.headAnswers {float:left; padding:3px; font-size:18px; font-weight:800; background:url(/content/images/ico_answers.gif) left 2px no-repeat; padding-left:24px;} +.headTags {float:left; padding:3px; font-size:18px; font-weight:800; background:url(/content/images/ico_tags.gif) no-repeat; padding-left:24px;} +.headUsers {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;} +.headMedals {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;} +.headLogin {float:left; padding:3px; font-size:15px; font-weight:800; background:url(/content/images/ico_login.gif) no-repeat; padding-left:24px;} +.headNormal {text-align:left;padding:3px; font-size:15px; margin-bottom:12px; font-weight:800;border-bottom:1px solid #777;} +.headUser {text-align:left;padding:5px; font-size:20px; + /*letter-spacing:1px;*/ + margin-bottom:12px; font-weight:800;border-bottom:1px solid #777;} +/*RSS订阅*/ +#feeds {margin:10px 0; } +#feeds a {background:url(/content/images/feed-icon-small.png) no-repeat 0; padding-left:18px; font-weight:700; font-size:13px; } + +/*问题*/ +#question {margin-bottom:30px;} +#question h1{font-size:15px;background:#CCC; padding:6px 8px;;} +#question .body{background:#F7F7F7; padding:20px 10px;} +.starter {padding:10px; background:#E0EAF1;} +.vote {font-size:20px; color:#666; font-weight:800;} +.questions-related{font-weight:700;word-wrap:break-word;} +.questions-related p{line-height:20px; margin-bottom:10px;font-size:100%;} +.question-status{ + margin-top:10px; + padding: 20px; + background-color:#F5F5F5; + text-align:center; +} +.question-status h3{font-size:125%;} +.question-body{ + min-height:100px; + font-size:13px; + line-height:20px; +} +.question-body IMG{ + max-width:600px; +} +.question-mark{ + background-color:#E9E9FF; + text-align:left; + padding:5px; + overflow:hidden; +} +.question-edit{ + text-align:left; + overflow:hidden; +} +.vote-buttons {float:left;text-align:center;} +.vote-buttons IMG{cursor:pointer;} +.vote-number{ + font-family:Arial; + padding:0px 0 3px 0; + font-size:140%; + font-weight:bold; + color:#777; +} +.question-img-upvote:hover{background:url(/content/images/vote-arrow-up-on.png)} +.question-img-downvote:hover{background:url(/content/images/vote-arrow-down-on.png)} +.question-img-favorite:hover{background:url(/content/images/vote-favorite-on.png)} +.favorite-number{padding:0px;font-size:100%; font-family:Arial;font-weight:bold;color:#777;} +.vote-notification +{ + z-index: 1; + cursor: pointer; + display: none; + position: absolute; + padding: 15px; + color: White; + background-color: darkred; + text-align: center; +} +.vote-notification a +{ + color: White; + text-decoration:underline; +} +.offensive-flag a{ + color:#777; + padding:3px; + cursor:pointer; +} + +.offensive-flag a:hover{ + background-color:#777; + text-decoration:none; + color:#fff; +} + +.linksopt a{ + color:#777; + padding:3px; + cursor:pointer; +} + +.linksopt a:hover{ + background-color:#777; + text-decoration:none; + color:#fff; +} + +.action-link a{ + color:#777; + padding:3px; + cursor:pointer; +} + +.action-link a:hover{ + background-color:#777; + text-decoration:none; + color:#fff; +} +.action-link-separator{ + color:#ccc; +} +.wiki-category{ + margin-left:5px; + color:#999; + font-size:90%; +} + +div.comments { + line-height:150%; + padding:10px 0; +} + +div.post-comments{ + width:100%; +} + +form.post-comments textarea { + height:6em; + margin-bottom:4px; +} + +form.post-comments input { + margin-left:10px; + margin-top:1px; + vertical-align:top; + width:100px; +} +span.text-counter { + margin-right:20px; +} + +span.form-error { + color:#990000; + font-weight:normal; + margin-left:5px; +} + +div.comments-container, div.comments-container-accepted, div.comments-container-owner, div.comments-container-deleted { + display:none; + margin-top:-1px; + padding:0 5px 5px; +} + +div.comments-container, a.comments-link { + background-color:#EEEEEE; +} + +.post-comments a { + color:#888888; + padding:0 3px 2px; +} + +a.comments-link, a.comments-link-accepted, a.comments-link-owner, a.comments-link-deleted { + color:black; + padding:2px; + cursor:pointer; +} + +.post-comments a:hover { + background-color:#777777; + color:white; + text-decoration:none; +} + +a.comment-user, a.comment-user:hover { + background-color:inherit; + color:#0077CC; + padding:0; +} + +a.comment-user:hover { + text-decoration:underline; +} +/*回答*/ +#answers {} +.answer{ + border-bottom:1px dotted #666666; + padding-bottom:20px; + padding-top:20px; + width: 100%; + margin-bottom:10px; +} +.answer-body{ + min-height:80px; + font-size:13px; + line-height:20px; +} + +.answer-body IMG{ + max-width:600px; +} + +.accepted-answer{ + background-color:#EBFFE6; + border-bottom-color:#9BD59B; +} + +.accepted-answer .comments-link{ + background-color:#CCFFBF; +} + +.accepted-answer .comments-container{ + background-color:#CCFFBF; +} + +.answered +{ + background: #CCC; + color: #999; +} + +.answered-accepted +{ + background: #CCC; + color: #663333; +} + +.unanswered +{ + background: #777; + color: white; +} + +.answered-by-owner +{ + background: #E9E9FF; +} + +.answered-by-owner .comments-link +{ + background-color:#E6ECFF; +} + +.answered-by-owner .comments-container +{ + background-color:#E6ECFF; +} + +.answered-accepted strong +{ + color: #E1E818; +} + +.answer-img-accept:hover{background:url(/content/images/vote-accepted-on.png)} + +.deleted{ + background:#F4E7E7 none repeat scroll 0 0; +} + +/*标签列表*/ +.tagsbox {} +.tagsbox a {color:#000;line-height:30px;margin-right:10px;font-size:100%;background-color:#F9F7ED;padding:3px;} +.tagsbox a:hover {text-decoration:none;background-color:#F9F7CD;color:#B02B2C;} +.tagsList {margin:0; list-style-type:none;padding:0px;min-height:360px;} +.tagsList li {width:235px; float:left;} +.badge-list{margin:0; list-style-type:none;} +/*登录*/ +.list-item{margin-left:15px;} +.list-item LI{list-style-type:disc; font-size:13px; line-height:20px; margin-bottom:10px;} +/* openid styles */ +.form-row{line-height:25px;} +.submit-row{line-height:30px;padding-top:10px;} +.errors{line-height:20px;color:red;} +.error{color:red;} +.error-list li{padding:5px;} +.login{margin-bottom:10px;} +.fieldset{border:solid 1px #777;margin-top:10px;padding:10px;} +.openid-input{background:url(/content/images/openid.gif) no-repeat;padding-left:15px;cursor:pointer;} +.openid-login-input{ + background-position:center left; + background:url(/content/images/openid.gif) no-repeat 0% 50%; + padding:5px 5px 5px 15px; + cursor:pointer; + font-family:Trebuchet MS; + font-weight:300; + font-size:150%; + width:500px; +} + +.openid-login-submit{ + height:40px; + width:80px; + line-height:40px; + cursor:pointer; + border:1px solid #777; + font-weight:bold; + font-size:120%; +} + +.openid-samples{ + +} + +.openid-samples .list, .list li{ + font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif; + list-style:none !important; + margin-left:-30px !important; + line-height:20px !important; +} + +/*表单相关*/ +span.form-error { + color:#990000; + font-size:90%; + font-weight:normal; + margin-left:5px; +} +.title-desc{ + color:#999; + font-size:90%; +} + +/*adjustment for editor preview*/ +#editor{ + font-size:100%; + min-height:200px; + line-height: 18px; + width:100%; +} + +.wmd-preview{ + margin-top:10px; + padding:6px; + width:100%; + background-color:#F5F5F5; + min-height:20px; +} +.wmd-preview pre{ + background-color:#E7F1F8; + +} + +.wmd-preview blockquote +{ + background-color: #eee; +} + +.wmd-preview IMG{ + max-width:600px; +} +.preview-toggle{ + font-weight:600; + width:100%; + color:#aaa; + /*letter-spacing:1px;*/ + text-align:left; +} + +.preview-toggle span:hover{ + cursor:pointer; +} + +.edit-content-html{ + border-top:1px dotted #D8D2A9; + border-bottom:1px dotted #D8D2A9; + margin:5px 0 5px 0; +} + +/*修订记录*/ + +#revisions{ + width:950px; +} + +.revision{ + margin:10px 0 10px 0; + width:100%; + font-size:13px; +} + +.revision .header{ + background-color:#eee; + padding:5px; + cursor:pointer; +} + +.revision .author{ + background-color:#E9E9FF; +} + +.revision .summary{ + padding: 5px 0 10px 0; +} + +.revision .summary span{ + background-color:yellow; + display:inline; +} +.revision h1{ + font-size:130%; + font-weight:600; + padding:15px 0 15px 0; +} + +.revision-mark{ + width:200px; + text-align:left; + font-size:90%; + overflow:hidden; +} + +.revision-number{ + font-size:300%; + font-weight:bold; + font-family:arial; +} + +.revision .body{ + padding-left:10px; + margin-bottom:50px; +} +.revision .answerbody{ + padding:10px 0 5px 10px; +} + +/* Revision pages */ +del { color: #FF5F5F; } +del .post-tag{ +color: #FF5F5F; +} +ins { background-color: #97ff97;} +ins .post-tag{ +background-color: #97ff97; +} + +/*用户资料页面*/ +.count {font-family:Arial;font-size:200%;font-weight:700;color:#777} +.scoreNumber{font-family:Arial;font-size:35px;font-weight:800;color:#777;line-height:40px; + /*letter-spacing:0px*/ + } +.user-details{font-size:13px;} +.user-about{background-color:#EEEEEE;height:200px;line-height:20px; overflow:auto;padding:10px;width:90%;} +.user-edit-link {background:url(/content/images/edit.png) no-repeat; padding-left:20px; font-weight:600;} +.favorites-count-off { + color:#919191; + float:left; + padding:3px; + margin:10px 0 0 0 ; + text-align:center; +} + +.favorites-count { + color:#D4A849; + float:left; + padding:3px; + margin:10px 0 0 0 ; + text-align:center; +} +.favorites-empty{ + width: 32px; height: 45px; float: left; +} +.question-summary { + border-bottom:1px dotted #999999; + float:left; + overflow:hidden; + padding:11px 0; + width:670px; +} + +.user-info-table{ +width:950;margin-bottom:10px; +} + +.user-stats-table .question-summary { + width:800px; +} + +.narrow .stats { + background:transparent none repeat scroll 0 0; + float:left; + height:48px; + margin:0 0 0 7px; + padding:0; + width:auto; + font-family:Arial; +} + +.stats div { + font-size:11px; + text-align:center; +} + +.narrow .votes { + background:#EEEEEE none repeat scroll 0 0; + float:left; + height:42px; + margin:0 3px 0 0; + padding:5px; + width:46px; + text-align:center; + -moz-border-radius: 5px; + -khtml-border-radius: 5px; + -webkit-border-radius: 5px; +} + +.narrow .summary { + width:620px; +} + +.narrow .summary h3 { + padding:0px; + margin:0px; +} + +.narrow .views { + float:left; + height:42px; + margin:0 7px 0 0; + /*padding:5px 0 5px 4px;*/ + padding: 5px; + width:46px; + text-align:center; + -moz-border-radius: 5px; + -khtml-border-radius: 5px; + -webkit-border-radius: 5px; + color:#777; +} + +.narrow .status { + float:left; + height:42px; + margin:0 3px 0 0; + padding:5px; + width:46px; + text-align:center; + -moz-border-radius: 5px; + -khtml-border-radius: 5px; + -webkit-border-radius: 5px; +} + +.narrow .vote-count-post { + font-weight:800; + margin:0; + font-size: 190%; color:#555; line-height:20px; +} +.narrow .answer-count-post{font-weight:800;margin:0; font-size: 190%; } +.narrow .views-count-post{font-weight:800;margin:0; font-size: 190%;} +div.started { + color:#999999; + float:right; + line-height:18px; + +} + +.narrow div.started { + line-height:inherit; + padding-top:4px; + white-space:nowrap; + width:auto; +} + +.relativetime { + font-weight:bold; + text-decoration:none; +} + +div.started a { + font-weight:bold; +} + +div.started .reputation-score { + margin-left:1px; +} + +.narrow .tags{float:left;} + +.answer-summary { + clear:both; + padding:3px; +} + +.answer-votes { + background-color:#EEEEEE; + color:#555555; + float:left; + font-family:Arial; + font-size:110%; + font-weight:bold; + height:15px; + padding:4px 4px 5px; + text-align:center; + text-decoration:none; + width:20px; + margin-right:10px; +} +.vote-count{font-family:Arial; font-size:160%; font-weight:700; color:#777;} +.user-action{ + +} +.user-action-1{ + font-weight:bold; + color:#333; +} +.user-action-2{ + font-weight:bold; + color:#CCC; +} +.user-action-3{ + color:#333; +} +.user-action-4{ + color:#333; +} +.user-action-5{ + color:darkred; +} +.user-action-6{ + color:darkred; +} +.user-action-7{ + color:#333; +} +.user-action-8{ + padding:3px; + font-weight:bold; + background-color:#CCC; + color:#663333; +} + +.revision-summary{ + background-color:#FFFE9B; + padding:2px; +} +.question-title-link a{ + font-weight:bold; + color:#0077CC; +} +.answer-title-link a{ + color:#333; +} + +.post-type-1 a { + font-weight:bold; + +} +.post-type-3 a { + font-weight:bold; + +} +.post-type-5 a { + font-weight:bold; + } +.post-type-2 a{ + color:#333; +} +.post-type-4 a{ + color:#333; +} +.post-type-6 a{ + color:#333; +} +.post-type-8 a{ + color:#333; +} + + +/*读书频道*/ +.bookInfo {float:left; width:940px;padding:5px;} +.bookCover {float:left; width:200px;} +.bookCover img{border:1px solid #ccc;max-width:200px;} +.bookSummary {float:left; font-size:13px;} +.blogRss {float:right;margin:0 10px 0 0;width:460px;height:240px;background-color:#EEE; padding:5px;} +.bookQuestions {margin-bottom:10px;} +.bookFeed {float:right;} +.bookAsk{ + /*letter-spacing:1px; */ + float:right;margin:-30px 10px 0 0; padding:3px 5px 3px 5px;} +.bookAsk a {font-size:15px; color:#FFF; font-weight:bold; text-decoration:none;background-color:#EC7000;padding:3px 6px 3px 6px; } +.bookAsk a:hover {text-decoration:underline;} + + +/*其他全局样式*/ +.hilite { background-color: #ff0; } +.hilite1 { background-color: #ff0; } +.hilite2 { background-color: #f0f; } +.hilite3 { background-color: #0ff; } +.userStatus {margin-left:12px; color:#FFF; float:right;} +.userStatus a {color:#FFF;} +.gold, .badge1 {color:#FFCC00;} +.silver, .badge2 {color:#CCCCCC;} +.bronze, .badge3 {color:#CC9933;} +.score {font-weight:800; color:#333;} +.footerLinks {color:#EEE; font-size:13px; + /* letter-spacing:1px;*/ + } +.footerLinks a {color:#FFF; font-size:13px;} +.subSearch {margin-bottom:12px; padding:4px;} +a.comment {background:#EEE; color:#993300; padding:4px;} +a.permLink {padding:2px;} +a.offensive {color:#999;} +ul.bulleta li {background:url(/content/images/bullet_green.gif) no-repeat 0px 2px; padding-left:16px; margin-bottom:4px;} +.user {padding:5px; line-height:140%; width:170px;} +.user ul {margin:0; list-style-type:none;} +.user .thumb{clear:both;float:left; margin-right:4px; display:inline;} +.yellowbg{background:yellow;} +.subtitle{line-height:30px;font-size:15px; font-weight:700;} +.message{padding:5px;font-weight:bold;background-color:#eee;margin:10px 0 10px 0;} +.warning{color:red;} +.darkred{color:darkred;} +.submit{ + cursor:pointer; + /*letter-spacing:1px;*/ + background-color:#D4D0C8; + height:40px; + border:1px solid #777; +/* width:100px; */ + font-weight:bold; + font-size:120%;} +.submit:hover{text-decoration:underline;} +.ask-body{padding-right:10px;} +.thousand{color:orange;} +.notify +{ + position: fixed; + top: 0px; + left: 0px; + width: 100%; + z-index: 100; + padding: 7px 0 5px 0; + text-align: center; + font-size: 130%; + font-weight: Bold; + color: #444; + background-color: #F4A83D; +} + +.notify span +{ + float: left; + width: 95%; + text-align: center; +} + +.close-notify +{ + float: right; + margin-right: 20px; + color: #735005; + text-decoration: none; + background-color: #FAD163; + border: 2px #735005 solid; + padding-left: 3px; + padding-right: 3px; + cursor:pointer; +} diff --git a/templates/faq.html b/templates/faq.html index 31ee444a..253d3590 100644 --- a/templates/faq.html +++ b/templates/faq.html @@ -1,5 +1,5 @@ -<!-- template faq.html --> {% extends "base_content.html" %} +<!-- template faq.html --> {% load extra_tags %} {% load humanize %} {% load i18n %} diff --git a/templates/footer.html b/templates/footer.html index 61e6e6bb..207104ce 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -1,33 +1,33 @@ -<!-- template footer.html -->
-{% load extra_tags %}
-{% load i18n %}
-<!-- 页面底部开始: -->
- <div id="ground">
- <div class="footerLinks" >
- <a href="/about">{% trans "about" %}</a><span class="link-separator"> |</span>
- <a href="/faq">{% trans "faq" %}</a><span class="link-separator"> |</span>
- <a href="{{ blog_url }}">{% trans "blog" %}</a><span class="link-separator"> |</span>
- <a href="{{ webmaster_email }}">{% trans "contact us" %}</a><span class="link-separator"> |</span>
- <a href="/privacy">{% trans "privacy policy" %}</a><span class="link-separator"> |</span>
- <a href="{{ feedback_url }}" target="_blank">{% trans "give feedback" %}</a>
- </div>
- <p style="margin-top:10px;">
- <a href="http://code.google.com/p/cnprog/" target="_blank">
- <img src="/content/images/djangomade124x25_grey.gif" border="0" alt="Made with Django." title="Made with Django." >
- </a>
- <!--<div style="font-size:90%;color:#333">{% trans "current revision" %}: R-0120-20090406</div>-->
- </p>
- <p id="licenseLogo"><img src="/content/images/cc-wiki.png" title="Creative Commons: Attribution - Share Alike" alt="cc-wiki" width="50" height="68" /></p>
- </div>
- <!-- 页面底部结束: -->
- <script type="text/javascript">
- var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
- document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
- </script>
- <script type="text/javascript">
- try {
- var pageTracker = _gat._getTracker({{ google_analytics_key }});
- pageTracker._trackPageview();
- } catch(err) {}
- </script>
-<!-- end template footer.html -->
+<!-- template footer.html --> +{% load extra_tags %} +{% load i18n %} +<!-- 页面底部开始: --> + <div id="ground"> + <div class="footerLinks" > + <a href="/about">{% trans "about" %}</a><span class="link-separator"> |</span> + <a href="/faq">{% trans "faq" %}</a><span class="link-separator"> |</span> + <a href="{{ blog_url }}">{% trans "blog" %}</a><span class="link-separator"> |</span> + <a href="{{ webmaster_email }}">{% trans "contact us" %}</a><span class="link-separator"> |</span> + <a href="/privacy">{% trans "privacy policy" %}</a><span class="link-separator"> |</span> + <a href="{{ feedback_url }}" target="_blank">{% trans "give feedback" %}</a> + </div> + <p style="margin-top:10px;"> + <a href="http://code.google.com/p/cnprog/" target="_blank"> + <img src="/content/images/djangomade124x25_grey.gif" border="0" alt="Made with Django." title="Made with Django." > + </a> + <!--<div style="font-size:90%;color:#333">{% trans "current revision" %}: R-0120-20090406</div>--> + </p> + <p id="licenseLogo"><img src="/content/images/cc-wiki.png" title="Creative Commons: Attribution - Share Alike" alt="cc-wiki" width="50" height="68" /></p> + </div> + <!-- 页面底部结束: --> + <script type="text/javascript"> + var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); + document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); + </script> + <script type="text/javascript"> + try { + var pageTracker = _gat._getTracker({{ google_analytics_key }}); + pageTracker._trackPageview(); + } catch(err) {} + </script> +<!-- end template footer.html --> diff --git a/templates/header.html b/templates/header.html index fb07a4c0..64cf2bf0 100644 --- a/templates/header.html +++ b/templates/header.html @@ -1,70 +1,71 @@ -<!-- template header.html -->
-{% load extra_tags %}
-{% load i18n %}
- <div id="top">
- <div id="header">
- {% if request.user.is_authenticated %}
- <a href="/users/{{ request.user.id }}/{{ request.user.username }}/">{{ request.user.username }}</a> {% get_score_badge request.user %}
- <a href="{% url user_signout %}">{% trans "logout" %}</a>
- {% else %}
- <a href="{% url user_signin %}">{% trans "login" %}</a>
- {% endif %}
- <a href="/about">{% trans "about" %}</a>
- <a href="/faq">{% trans "faq" %}</a>
- </div>
- </div>
- <div id="roof">
- <div id="navBar">
- <table width="100%" border="0" cellspacing="0" cellpadding="0">
- <tr>
- <td width="23%">
- <div id="logo">
- <a href="/">
- <img src="/content/images/logo.png" title="{% trans "back to home page" %}" />
- </a>
- </div>
- </td>
- <td width="77%" valign="bottom">
- <div class="nav">
- <a id="nav_questions" href="/questions/" >{% trans "questions" %}</a>
- <a id="nav_tags" href="/tags/">{% trans "tags" %}</a>
- <a id="nav_users" href="/users/">{% trans "users" %}</a>
- <!--<a id="nav_books" href="/books/">{% trans "books" %}</a>-->
- <a id="nav_badges" href="/badges/">{% trans "badges" %}</a>
- <a id="nav_unanswered" href="/questions/unanswered/">{% trans "unanswered questions" %}</a>
-
- {% comment %}<!-- i think this needs to be removed -e.f. -->
- {% if request.user.is_authenticated %}
- <a id="nav_profile" href="/users/{{ request.user.id }}/{{ request.user.username }}/">{% trans "my profile" %}</a>
- {% endif %}
- {% endcomment %}
- <div class="focus">
- <a id="nav_ask" href="/questions/ask/" class="special">{% trans "ask a question" %}</a>
- </div>
- </div>
-
- </td>
- </tr>
- </table>
- </div>
- <div id="searchBar">
- <table width="100%" height="60" border="0" cellpadding="0" cellspacing="0" class="content">
- <tr>
- <td align="center" valign="middle">
- <form action="/search/" method="GET">
- <div>
- <input type="text" class="searchInput" value="{{ keywords }}" name="q" id="keywords" >
- <input type="submit" name="Submit" value="{% trans "search" %}" class="searchBtn" >
- </div>
- <div class="options">
- <input id="type-question" type="radio" class="" value="question" name="t" checked >{% trans "questions" %}
- <input id="type-tag" type="radio" class="" value="tag" name="t" >{% trans "tags" %}
- <input id="type-user" type="radio" class="" value="user" name="t" >{% trans "users" %}
- </div>
- </form>
- </td>
- </tr>
- </table>
- </div>
- </div>
-<!-- end template header.html -->
+<!-- template header.html --> +{% load extra_tags %} +{% load i18n %} + <div id="top"> + <div id="header"> + {% if request.user.is_authenticated %} + <a href="/users/{{ request.user.id }}/{{ request.user.username }}/">{{ request.user.username }}</a> {% get_score_badge request.user %} + <a href="{% url user_signout %}">{% trans "logout" %}</a> + {% else %} + <a href="{% url user_signin %}">{% trans "login" %}</a> + <a href="{% url user_signup %}">{% trans "signup" %}</a> + {% endif %} + <a href="/about">{% trans "about" %}</a> + <a href="/faq">{% trans "faq" %}</a> + </div> + </div> + <div id="roof"> + <div id="navBar"> + <table width="100%" border="0" cellspacing="0" cellpadding="0"> + <tr> + <td width="23%"> + <div id="logo"> + <a href="/"> + <img src="/content/images/logo.png" title="{% trans "back to home page" %}" /> + </a> + </div> + </td> + <td width="77%" valign="bottom"> + <div class="nav"> + <a id="nav_questions" href="/questions/" >{% trans "questions" %}</a> + <a id="nav_tags" href="/tags/">{% trans "tags" %}</a> + <a id="nav_users" href="/users/">{% trans "users" %}</a> + <!--<a id="nav_books" href="/books/">{% trans "books" %}</a>--> + <a id="nav_badges" href="/badges/">{% trans "badges" %}</a> + <a id="nav_unanswered" href="/questions/unanswered/">{% trans "unanswered questions" %}</a> + + {% comment %}<!-- i think this needs to be removed -e.f. --> + {% if request.user.is_authenticated %} + <a id="nav_profile" href="/users/{{ request.user.id }}/{{ request.user.username }}/">{% trans "my profile" %}</a> + {% endif %} + {% endcomment %} + <div class="focus"> + <a id="nav_ask" href="/questions/ask/" class="special">{% trans "ask a question" %}</a> + </div> + </div> + + </td> + </tr> + </table> + </div> + <div id="searchBar"> + <table width="100%" height="60" border="0" cellpadding="0" cellspacing="0" class="content"> + <tr> + <td align="center" valign="middle"> + <form action="/search/" method="GET"> + <div> + <input type="text" class="searchInput" value="{{ keywords }}" name="q" id="keywords" > + <input type="submit" name="Submit" value="{% trans "search" %}" class="searchBtn" > + </div> + <div class="options"> + <input id="type-question" type="radio" class="" value="question" name="t" checked >{% trans "questions" %} + <input id="type-tag" type="radio" class="" value="tag" name="t" >{% trans "tags" %} + <input id="type-user" type="radio" class="" value="user" name="t" >{% trans "users" %} + </div> + </form> + </td> + </tr> + </table> + </div> + </div> +<!-- end template header.html --> diff --git a/templates/privacy.html b/templates/privacy.html index 335aba92..d5d7dd43 100644 --- a/templates/privacy.html +++ b/templates/privacy.html @@ -1,5 +1,5 @@ -<!-- privacy.html --> {% extends "base_content.html" %} +<!-- privacy.html --> {% load extra_tags %} {% load i18n %} {% load humanize %} diff --git a/templates/tags.html b/templates/tags.html index cb499eed..6f9b3aed 100644 --- a/templates/tags.html +++ b/templates/tags.html @@ -1,5 +1,5 @@ -<!-- tags.html --> {% extends "base_content.html" %} +<!-- tags.html --> {% load i18n %} {% load extra_tags %} {% load humanize %} diff --git a/templates/user.html b/templates/user.html index 53a30dc0..8db15902 100644 --- a/templates/user.html +++ b/templates/user.html @@ -1,35 +1,35 @@ <!-- user.html --> -{% extends "base_content.html" %}
-{% load extra_tags %}
-{% load humanize %}
-{% block title %}{% spaceless %}{{ page_title }}{% endspaceless %}{% endblock %}
-{% block forestyle%}
- <style type="text/css">
- .history-table td { padding: 5px; }
- .user-stats-table { margin-left:50px; }
- </style>
-{% endblock %}
-{% block forejs %}
- <script type="text/javascript">
- $().ready(function(){
- {% ifequal view_user request.user%}
- $("#nav_profile").attr('className',"on");
- {% else %}
- $("#nav_users").attr('className',"on");
-
- {% endifequal %}
- });
- </script>
- {% block userjs %}
- {% endblock %}
-{% endblock %}
-
-{% block content %}
- <div id="mainbar-full">
- {% include "user_info.html" %}
- {% include "user_tabs.html" %}
- {% block usercontent %}
- {% endblock %}
- {% include "user_footer.html" %}
- </div>
+{% extends "base_content.html" %} +{% load extra_tags %} +{% load humanize %} +{% block title %}{% spaceless %}{{ page_title }}{% endspaceless %}{% endblock %} +{% block forestyle%} + <style type="text/css"> + .history-table td { padding: 5px; } + .user-stats-table { margin-left:50px; } + </style> +{% endblock %} +{% block forejs %} + <script type="text/javascript"> + $().ready(function(){ + {% ifequal view_user request.user%} + $("#nav_profile").attr('className',"on"); + {% else %} + $("#nav_users").attr('className',"on"); + + {% endifequal %} + }); + </script> + {% block userjs %} + {% endblock %} +{% endblock %} + +{% block content %} + <div id="mainbar-full"> + {% include "user_info.html" %} + {% include "user_tabs.html" %} + {% block usercontent %} + {% endblock %} + {% include "user_footer.html" %} + </div> {% endblock %}<!-- end user.html --> diff --git a/templates/users.html b/templates/users.html index 701dbaa2..966596fc 100644 --- a/templates/users.html +++ b/templates/users.html @@ -1,5 +1,5 @@ -<!-- users.html --> {% extends "base_content.html" %} +<!-- users.html --> {% load extra_tags %} {% load humanize %} {% load i18n %} @@ -1,64 +1,64 @@ -import os.path
-from django.conf.urls.defaults import *
-from django.contrib import admin
-from forum.views import index
-from forum import views as app
-from forum.feed import RssLastestQuestionsFeed
-
-admin.autodiscover()
-feeds = {
- 'rss': RssLastestQuestionsFeed
-}
-
-APP_PATH = os.path.dirname(__file__)
-urlpatterns = patterns('',
- (r'^$', index),
- (r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/content/images/favicon.ico'}),
- (r'^favicon\.gif$', 'django.views.generic.simple.redirect_to', {'url': '/content/images/favicon.gif'}),
- (r'^content/(?P<path>.*)$', 'django.views.static.serve',
- {'document_root': os.path.join(APP_PATH, 'templates/content').replace('\\','/')}
- ),
- (r'^upfiles/(?P<path>.*)$', 'django.views.static.serve',
- {'document_root': os.path.join(APP_PATH, 'templates/upfiles').replace('\\','/')}
- ),
- (r'^account/', include('django_authopenid.urls')),
- (r'^signin/$', 'django_authopenid.views.signin'),
- url(r'^about/$', app.about, name='about'),
- url(r'^faq/$', app.faq, name='faq'),
- url(r'^privacy/$', app.privacy, name='privacy'),
- url(r'^logout/$', app.logout, name='logout'),
- url(r'^answers/(?P<id>\d+)/comments/$', app.answer_comments, name='answer_comments'),
- url(r'^answers/(?P<id>\d+)/edit/$', app.edit_answer, name='edit_answer'),
- url(r'^answers/(?P<id>\d+)/revisions/$', app.answer_revisions, name='answer_revisions'),
- url(r'^questions/$', app.questions, name='questions'),
- url(r'^questions/ask/$', app.ask, name='ask'),
- url(r'^questions/unanswered/$', app.unanswered, name='unanswered'),
- url(r'^questions/(?P<id>\d+)/edit/$', app.edit_question, name='edit_question'),
- url(r'^questions/(?P<id>\d+)/close/$', app.close, name='close'),
- url(r'^questions/(?P<id>\d+)/reopen/$', app.reopen, name='reopen'),
- url(r'^questions/(?P<id>\d+)/answer/$', app.answer, name='answer'),
- url(r'^questions/(?P<id>\d+)/vote/$', app.vote, name='vote'),
- url(r'^questions/(?P<id>\d+)/revisions/$', app.question_revisions, name='question_revisions'),
- url(r'^questions/(?P<id>\d+)/comments/$', app.question_comments, name='question_comments'),
- url(r'^questions/(?P<question_id>\d+)/comments/(?P<comment_id>\d+)/delete/$', app.delete_question_comment, name='delete_question_comment'),
- url(r'^answers/(?P<answer_id>\d+)/comments/(?P<comment_id>\d+)/delete/$', app.delete_answer_comment, name='delete_answer_comment'),
- #place general question item in the end of other operations
- url(r'^questions/(?P<id>\d+)//*', app.question, name='question'),
- (r'^tags/$', app.tags),
- (r'^tags/(?P<tag>[^/]+)/$', app.tag),
- (r'^users/$',app.users),
- url(r'^users/(?P<id>\d+)/edit/$', app.edit_user, name='edit_user'),
- url(r'^users/(?P<id>\d+)//*', app.user, name='user'),
- url(r'^badges/$',app.badges, name='badges'),
- url(r'^badges/(?P<id>\d+)//*', app.badge, name='badge'),
- url(r'^messages/markread/$',app.read_message, name='read_message'),
- # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
- (r'^nimda/(.*)', admin.site.root),
- (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}),
- (r'^upload/$', app.upload),
- url(r'^books/$', app.books, name='books'),
- url(r'^books/ask/(?P<short_name>[^/]+)/$', app.ask_book, name='ask_book'),
- url(r'^books/(?P<short_name>[^/]+)/$', app.book, name='book'),
- url(r'^search/$', app.search, name='search'),
- (r'^i18n/', include('django.conf.urls.i18n')),
-)
+import os.path +from django.conf.urls.defaults import * +from django.contrib import admin +from forum.views import index +from forum import views as app +from forum.feed import RssLastestQuestionsFeed + +admin.autodiscover() +feeds = { + 'rss': RssLastestQuestionsFeed +} + +APP_PATH = os.path.dirname(__file__) +urlpatterns = patterns('', + (r'^$', index), + (r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/content/images/favicon.ico'}), + (r'^favicon\.gif$', 'django.views.generic.simple.redirect_to', {'url': '/content/images/favicon.gif'}), + (r'^content/(?P<path>.*)$', 'django.views.static.serve', + {'document_root': os.path.join(APP_PATH, 'templates/content').replace('\\','/')} + ), + (r'^upfiles/(?P<path>.*)$', 'django.views.static.serve', + {'document_root': os.path.join(APP_PATH, 'templates/upfiles').replace('\\','/')} + ), + (r'^account/', include('django_authopenid.urls')), + (r'^signin/$', 'django_authopenid.views.signin'), + url(r'^about/$', app.about, name='about'), + url(r'^faq/$', app.faq, name='faq'), + url(r'^privacy/$', app.privacy, name='privacy'), + url(r'^logout/$', app.logout, name='logout'), + url(r'^answers/(?P<id>\d+)/comments/$', app.answer_comments, name='answer_comments'), + url(r'^answers/(?P<id>\d+)/edit/$', app.edit_answer, name='edit_answer'), + url(r'^answers/(?P<id>\d+)/revisions/$', app.answer_revisions, name='answer_revisions'), + url(r'^questions/$', app.questions, name='questions'), + url(r'^questions/ask/$', app.ask, name='ask'), + url(r'^questions/unanswered/$', app.unanswered, name='unanswered'), + url(r'^questions/(?P<id>\d+)/edit/$', app.edit_question, name='edit_question'), + url(r'^questions/(?P<id>\d+)/close/$', app.close, name='close'), + url(r'^questions/(?P<id>\d+)/reopen/$', app.reopen, name='reopen'), + url(r'^questions/(?P<id>\d+)/answer/$', app.answer, name='answer'), + url(r'^questions/(?P<id>\d+)/vote/$', app.vote, name='vote'), + url(r'^questions/(?P<id>\d+)/revisions/$', app.question_revisions, name='question_revisions'), + url(r'^questions/(?P<id>\d+)/comments/$', app.question_comments, name='question_comments'), + url(r'^questions/(?P<question_id>\d+)/comments/(?P<comment_id>\d+)/delete/$', app.delete_question_comment, name='delete_question_comment'), + url(r'^answers/(?P<answer_id>\d+)/comments/(?P<comment_id>\d+)/delete/$', app.delete_answer_comment, name='delete_answer_comment'), + #place general question item in the end of other operations + url(r'^questions/(?P<id>\d+)//*', app.question, name='question'), + (r'^tags/$', app.tags), + (r'^tags/(?P<tag>[^/]+)/$', app.tag), + (r'^users/$',app.users), + url(r'^users/(?P<id>\d+)/edit/$', app.edit_user, name='edit_user'), + url(r'^users/(?P<id>\d+)//*', app.user, name='user'), + url(r'^badges/$',app.badges, name='badges'), + url(r'^badges/(?P<id>\d+)//*', app.badge, name='badge'), + url(r'^messages/markread/$',app.read_message, name='read_message'), + # (r'^admin/doc/', include('django.contrib.admindocs.urls')), + (r'^nimda/(.*)', admin.site.root), + (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}), + (r'^upload/$', app.upload), + url(r'^books/$', app.books, name='books'), + url(r'^books/ask/(?P<short_name>[^/]+)/$', app.ask_book, name='ask_book'), + url(r'^books/(?P<short_name>[^/]+)/$', app.book, name='book'), + url(r'^search/$', app.search, name='search'), + (r'^i18n/', include('django.conf.urls.i18n')), +) diff --git a/utils/cache.py b/utils/cache.py index bc1cb1af..410c0662 100644 --- a/utils/cache.py +++ b/utils/cache.py @@ -1,92 +1,92 @@ -"""Utilities for working with Django Models."""
-import itertools
-
-from django.contrib.contenttypes.models import ContentType
-
-from lanai.utils.lists import flatten
-
-def fetch_model_dict(model, ids, fields=None):
- """
- Fetches a dict of model details for model instances with the given
- ids, keyed by their id.
-
- If a fields list is given, a dict of details will be retrieved for
- each model, otherwise complete model instances will be retrieved.
-
- Any fields list given shouldn't contain the primary key attribute for
- the model, as this can be determined from its Options.
- """
- if fields is None:
- return model._default_manager.in_bulk(ids)
- else:
- id_attr = model._meta.pk.attname
- return dict((obj[id_attr], obj) for obj
- in model._default_manager.filter(id__in=ids).values(
- *itertools.chain((id_attr,), fields)))
-
-def populate_foreign_key_caches(model, objects_to_populate, fields=None):
- """
- Populates caches for the given related Model in instances of objects
- which have a ForeignKey relationship to it, specified as a list of
- (object list, related attribute name list) two-tuples.
-
- If a list of field names is given, only the given fields will be
- looked up and related object caches will be populated with a dict of
- the specified fields. Otherwise, complete model instances will be
- retrieved.
- """
- # Get all related object ids for the appropriate fields
- related_object_ids = []
- for objects, attrs in objects_to_populate:
- related_object_ids.append(tuple(tuple(getattr(obj, '%s_id' % attr)
- for attr in attrs)
- for obj in objects))
- unique_ids = tuple(set(pk for pk in flatten(related_object_ids) if pk))
- related_objects = fetch_model_dict(model, unique_ids, fields)
-
- # Fill related object caches
- for (objects, attrs), related_ids in itertools.izip(objects_to_populate,
- related_object_ids):
- for obj, related_ids_for_obj in itertools.izip(objects,
- related_ids):
- for attr, related_object in itertools.izip(attrs, (related_objects.get(pk, None)
- for pk in related_ids_for_obj)):
- setattr(obj, '_%s_cache' % attr, related_object)
-
-def populate_content_object_caches(generic_related_objects, model_fields=None):
- """
- Retrieves ``ContentType`` and content objects for the given list of
- items which use a generic relation, grouping the retrieval of content
- objects by model to reduce the number of queries executed.
-
- This results in ``number_of_content_types + 1`` queries rather than
- the ``number_of_generic_reL_objects * 2`` queries you'd get by
- iterating over the list and accessing each item's object attribute.
-
- If a dict mapping model classes to field names is given, only the
- given fields will be looked up for each model specified and the
- object cache will be populated with a dict of the specified fields.
- Otherwise, complete model instances will be retrieved.
- """
- if model_fields is None:
- model_fields = {}
-
- # Group content object ids by their content type ids
- ids_by_content_type = {}
- for obj in generic_related_objects:
- ids_by_content_type.setdefault(obj.content_type_id,
- []).append(obj.object_id)
-
- # Retrieve content types and content objects in bulk
- content_types = ContentType.objects.in_bulk(ids_by_content_type.keys())
- for content_type_id, ids in ids_by_content_type.iteritems():
- model = content_types[content_type_id].model_class()
- objects[content_type_id] = fetch_model_dict(
- model, tuple(set(ids)), model_fields.get(model, None))
-
- # Set content types and content objects in the appropriate cache
- # attributes, so accessing the 'content_type' and 'object' attributes
- # on each object won't result in further database hits.
- for obj in generic_related_objects:
- obj._object_cache = objects[obj.content_type_id][obj.object_id]
- obj._content_type_cache = content_types[obj.content_type_id]
+"""Utilities for working with Django Models.""" +import itertools + +from django.contrib.contenttypes.models import ContentType + +from lanai.utils.lists import flatten + +def fetch_model_dict(model, ids, fields=None): + """ + Fetches a dict of model details for model instances with the given + ids, keyed by their id. + + If a fields list is given, a dict of details will be retrieved for + each model, otherwise complete model instances will be retrieved. + + Any fields list given shouldn't contain the primary key attribute for + the model, as this can be determined from its Options. + """ + if fields is None: + return model._default_manager.in_bulk(ids) + else: + id_attr = model._meta.pk.attname + return dict((obj[id_attr], obj) for obj + in model._default_manager.filter(id__in=ids).values( + *itertools.chain((id_attr,), fields))) + +def populate_foreign_key_caches(model, objects_to_populate, fields=None): + """ + Populates caches for the given related Model in instances of objects + which have a ForeignKey relationship to it, specified as a list of + (object list, related attribute name list) two-tuples. + + If a list of field names is given, only the given fields will be + looked up and related object caches will be populated with a dict of + the specified fields. Otherwise, complete model instances will be + retrieved. + """ + # Get all related object ids for the appropriate fields + related_object_ids = [] + for objects, attrs in objects_to_populate: + related_object_ids.append(tuple(tuple(getattr(obj, '%s_id' % attr) + for attr in attrs) + for obj in objects)) + unique_ids = tuple(set(pk for pk in flatten(related_object_ids) if pk)) + related_objects = fetch_model_dict(model, unique_ids, fields) + + # Fill related object caches + for (objects, attrs), related_ids in itertools.izip(objects_to_populate, + related_object_ids): + for obj, related_ids_for_obj in itertools.izip(objects, + related_ids): + for attr, related_object in itertools.izip(attrs, (related_objects.get(pk, None) + for pk in related_ids_for_obj)): + setattr(obj, '_%s_cache' % attr, related_object) + +def populate_content_object_caches(generic_related_objects, model_fields=None): + """ + Retrieves ``ContentType`` and content objects for the given list of + items which use a generic relation, grouping the retrieval of content + objects by model to reduce the number of queries executed. + + This results in ``number_of_content_types + 1`` queries rather than + the ``number_of_generic_reL_objects * 2`` queries you'd get by + iterating over the list and accessing each item's object attribute. + + If a dict mapping model classes to field names is given, only the + given fields will be looked up for each model specified and the + object cache will be populated with a dict of the specified fields. + Otherwise, complete model instances will be retrieved. + """ + if model_fields is None: + model_fields = {} + + # Group content object ids by their content type ids + ids_by_content_type = {} + for obj in generic_related_objects: + ids_by_content_type.setdefault(obj.content_type_id, + []).append(obj.object_id) + + # Retrieve content types and content objects in bulk + content_types = ContentType.objects.in_bulk(ids_by_content_type.keys()) + for content_type_id, ids in ids_by_content_type.iteritems(): + model = content_types[content_type_id].model_class() + objects[content_type_id] = fetch_model_dict( + model, tuple(set(ids)), model_fields.get(model, None)) + + # Set content types and content objects in the appropriate cache + # attributes, so accessing the 'content_type' and 'object' attributes + # on each object won't result in further database hits. + for obj in generic_related_objects: + obj._object_cache = objects[obj.content_type_id][obj.object_id] + obj._content_type_cache = content_types[obj.content_type_id] diff --git a/utils/html.py b/utils/html.py index 602e1a76..25a74a4a 100644 --- a/utils/html.py +++ b/utils/html.py @@ -1,51 +1,51 @@ -"""Utilities for working with HTML."""
-import html5lib
-from html5lib import sanitizer, serializer, tokenizer, treebuilders, treewalkers
-
-class HTMLSanitizerMixin(sanitizer.HTMLSanitizerMixin):
- acceptable_elements = ('a', 'abbr', 'acronym', 'address', 'b', 'big',
- 'blockquote', 'br', 'caption', 'center', 'cite', 'code', 'col',
- 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'font',
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd',
- 'li', 'ol', 'p', 'pre', 'q', 's', 'samp', 'small', 'span', 'strike',
- 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead',
- 'tr', 'tt', 'u', 'ul', 'var')
-
- acceptable_attributes = ('abbr', 'align', 'alt', 'axis', 'border',
- 'cellpadding', 'cellspacing', 'char', 'charoff', 'charset', 'cite',
- 'cols', 'colspan', 'datetime', 'dir', 'frame', 'headers', 'height',
- 'href', 'hreflang', 'hspace', 'lang', 'longdesc', 'name', 'nohref',
- 'noshade', 'nowrap', 'rel', 'rev', 'rows', 'rowspan', 'rules', 'scope',
- 'span', 'src', 'start', 'summary', 'title', 'type', 'valign', 'vspace',
- 'width')
-
- allowed_elements = acceptable_elements
- allowed_attributes = acceptable_attributes
- allowed_css_properties = ()
- allowed_css_keywords = ()
- allowed_svg_properties = ()
-
-class HTMLSanitizer(tokenizer.HTMLTokenizer, HTMLSanitizerMixin):
- def __init__(self, stream, encoding=None, parseMeta=True, useChardet=True,
- lowercaseElementName=True, lowercaseAttrName=True):
- tokenizer.HTMLTokenizer.__init__(self, stream, encoding, parseMeta,
- useChardet, lowercaseElementName,
- lowercaseAttrName)
-
- def __iter__(self):
- for token in tokenizer.HTMLTokenizer.__iter__(self):
- token = self.sanitize_token(token)
- if token:
- yield token
-
-def sanitize_html(html):
- """Sanitizes an HTML fragment."""
- p = html5lib.HTMLParser(tokenizer=HTMLSanitizer,
- tree=treebuilders.getTreeBuilder("dom"))
- dom_tree = p.parseFragment(html)
- walker = treewalkers.getTreeWalker("dom")
- stream = walker(dom_tree)
- s = serializer.HTMLSerializer(omit_optional_tags=False,
- quote_attr_values=True)
- output_generator = s.serialize(stream)
- return u''.join(output_generator)
+"""Utilities for working with HTML.""" +import html5lib +from html5lib import sanitizer, serializer, tokenizer, treebuilders, treewalkers + +class HTMLSanitizerMixin(sanitizer.HTMLSanitizerMixin): + acceptable_elements = ('a', 'abbr', 'acronym', 'address', 'b', 'big', + 'blockquote', 'br', 'caption', 'center', 'cite', 'code', 'col', + 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'font', + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', + 'li', 'ol', 'p', 'pre', 'q', 's', 'samp', 'small', 'span', 'strike', + 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', + 'tr', 'tt', 'u', 'ul', 'var') + + acceptable_attributes = ('abbr', 'align', 'alt', 'axis', 'border', + 'cellpadding', 'cellspacing', 'char', 'charoff', 'charset', 'cite', + 'cols', 'colspan', 'datetime', 'dir', 'frame', 'headers', 'height', + 'href', 'hreflang', 'hspace', 'lang', 'longdesc', 'name', 'nohref', + 'noshade', 'nowrap', 'rel', 'rev', 'rows', 'rowspan', 'rules', 'scope', + 'span', 'src', 'start', 'summary', 'title', 'type', 'valign', 'vspace', + 'width') + + allowed_elements = acceptable_elements + allowed_attributes = acceptable_attributes + allowed_css_properties = () + allowed_css_keywords = () + allowed_svg_properties = () + +class HTMLSanitizer(tokenizer.HTMLTokenizer, HTMLSanitizerMixin): + def __init__(self, stream, encoding=None, parseMeta=True, useChardet=True, + lowercaseElementName=True, lowercaseAttrName=True): + tokenizer.HTMLTokenizer.__init__(self, stream, encoding, parseMeta, + useChardet, lowercaseElementName, + lowercaseAttrName) + + def __iter__(self): + for token in tokenizer.HTMLTokenizer.__iter__(self): + token = self.sanitize_token(token) + if token: + yield token + +def sanitize_html(html): + """Sanitizes an HTML fragment.""" + p = html5lib.HTMLParser(tokenizer=HTMLSanitizer, + tree=treebuilders.getTreeBuilder("dom")) + dom_tree = p.parseFragment(html) + walker = treewalkers.getTreeWalker("dom") + stream = walker(dom_tree) + s = serializer.HTMLSerializer(omit_optional_tags=False, + quote_attr_values=True) + output_generator = s.serialize(stream) + return u''.join(output_generator) diff --git a/utils/lists.py b/utils/lists.py index 426d9cd3..bbcfae98 100644 --- a/utils/lists.py +++ b/utils/lists.py @@ -1,86 +1,86 @@ -"""Utilities for working with lists and sequences."""
-
-def flatten(x):
- """
- Returns a single, flat list which contains all elements retrieved
- from the sequence and all recursively contained sub-sequences
- (iterables).
-
- Examples:
- >>> [1, 2, [3, 4], (5, 6)]
- [1, 2, [3, 4], (5, 6)]
-
- From http://kogs-www.informatik.uni-hamburg.de/~meine/python_tricks
- """
- result = []
- for el in x:
- if hasattr(el, '__iter__') and not isinstance(el, basestring):
- result.extend(flatten(el))
- else:
- result.append(el)
- return result
-
-def batch_size(items, size):
- """
- Retrieves items in batches of the given size.
-
- >>> l = range(1, 11)
- >>> batch_size(l, 3)
- [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
- >>> batch_size(l, 5)
- [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
- """
- return [items[i:i+size] for i in xrange(0, len(items), size)]
-
-def batches(items, number):
- """
- Retrieves items in the given number of batches.
-
- >>> l = range(1, 11)
- >>> batches(l, 1)
- [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]
- >>> batches(l, 2)
- [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
- >>> batches(l, 3)
- [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
- >>> batches(l, 4)
- [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
- >>> batches(l, 5)
- [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
-
- Initial batches will contain as many items as possible in cases where
- there are not enough items to be distributed evenly.
-
- >>> batches(l, 6)
- [[1, 2], [3, 4], [5, 6], [7, 8], [9], [10]]
- >>> batches(l, 7)
- [[1, 2], [3, 4], [5, 6], [7], [8], [9], [10]]
- >>> batches(l, 8)
- [[1, 2], [3, 4], [5], [6], [7], [8], [9], [10]]
- >>> batches(l, 9)
- [[1, 2], [3], [4], [5], [6], [7], [8], [9], [10]]
- >>> batches(l, 10)
- [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]]
-
- If there are more batches than items, empty batches will be appended
- to the batch list.
-
- >>> batches(l, 11)
- [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], []]
- >>> batches(l, 12)
- [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [], []]
- """
- div, mod= divmod(len(items), number)
- if div > 1:
- if mod:
- div += 1
- return batch_size(items, div)
- else:
- if not div:
- return [[item] for item in items] + [[]] * (number - mod)
- elif div == 1 and not mod:
- return [[item] for item in items]
- else:
- # mod now tells you how many lists of 2 you can fit in
- return ([items[i*2:(i*2)+2] for i in xrange(0, mod)] +
- [[item] for item in items[mod*2:]])
+"""Utilities for working with lists and sequences.""" + +def flatten(x): + """ + Returns a single, flat list which contains all elements retrieved + from the sequence and all recursively contained sub-sequences + (iterables). + + Examples: + >>> [1, 2, [3, 4], (5, 6)] + [1, 2, [3, 4], (5, 6)] + + From http://kogs-www.informatik.uni-hamburg.de/~meine/python_tricks + """ + result = [] + for el in x: + if hasattr(el, '__iter__') and not isinstance(el, basestring): + result.extend(flatten(el)) + else: + result.append(el) + return result + +def batch_size(items, size): + """ + Retrieves items in batches of the given size. + + >>> l = range(1, 11) + >>> batch_size(l, 3) + [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]] + >>> batch_size(l, 5) + [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]] + """ + return [items[i:i+size] for i in xrange(0, len(items), size)] + +def batches(items, number): + """ + Retrieves items in the given number of batches. + + >>> l = range(1, 11) + >>> batches(l, 1) + [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]] + >>> batches(l, 2) + [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]] + >>> batches(l, 3) + [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]] + >>> batches(l, 4) + [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]] + >>> batches(l, 5) + [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]] + + Initial batches will contain as many items as possible in cases where + there are not enough items to be distributed evenly. + + >>> batches(l, 6) + [[1, 2], [3, 4], [5, 6], [7, 8], [9], [10]] + >>> batches(l, 7) + [[1, 2], [3, 4], [5, 6], [7], [8], [9], [10]] + >>> batches(l, 8) + [[1, 2], [3, 4], [5], [6], [7], [8], [9], [10]] + >>> batches(l, 9) + [[1, 2], [3], [4], [5], [6], [7], [8], [9], [10]] + >>> batches(l, 10) + [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]] + + If there are more batches than items, empty batches will be appended + to the batch list. + + >>> batches(l, 11) + [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], []] + >>> batches(l, 12) + [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [], []] + """ + div, mod= divmod(len(items), number) + if div > 1: + if mod: + div += 1 + return batch_size(items, div) + else: + if not div: + return [[item] for item in items] + [[]] * (number - mod) + elif div == 1 and not mod: + return [[item] for item in items] + else: + # mod now tells you how many lists of 2 you can fit in + return ([items[i*2:(i*2)+2] for i in xrange(0, mod)] + + [[item] for item in items[mod*2:]]) |