From: hernani Date: Thu, 8 Apr 2010 15:20:37 +0000 (+0000) Subject: cleaning the repo X-Git-Tag: live~1063 X-Git-Url: https://git.openstreetmap.org./osqa.git/commitdiff_plain/8d6f7bc7837a4d0c1a912cdc9cf66177b04b1740 cleaning the repo git-svn-id: http://svn.osqa.net/svnroot/osqa/trunk@11 0cfe37f9-358a-4d5e-be75-b63607b5c754 --- diff --git a/.project b/.project deleted file mode 100644 index 8e56b00..0000000 --- a/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - osqa - - - - - - org.eclipse.wst.jsdt.core.javascriptValidator - - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - org.eclipse.wst.jsdt.core.jsNature - - diff --git a/.pydevproject b/.pydevproject deleted file mode 100644 index f7f3fd1..0000000 --- a/.pydevproject +++ /dev/null @@ -1,10 +0,0 @@ - - - - -Default -python 2.6 - -/osqa - - diff --git a/HOW_TO_DEBUG b/HOW_TO_DEBUG deleted file mode 100644 index ba36198..0000000 --- a/HOW_TO_DEBUG +++ /dev/null @@ -1,39 +0,0 @@ -1) LOGGING -Please remember that log files may contain plaintext passwords, etc. - -Please do not add print statements - at least do not commit them to git -because in some environments printing to stdout causes errors - -Instead use python logging this way: --------------------------------- -#somewere on top of file -import logging - -#anywhere below -logging.debug('this maybe works') -logging.error('have big error!') -#or even -logging.debug('') #this will add time, line number, function and file record -#sometimes useful record for call tracing on its own -#etc - take a look at http://docs.python.org/library/logging.html -------------------------------- - -in OSQA logging is currently set up in settings_local.py.dist -please update it if you need - in older revs logging strings have less info - -messages of interest can be grepped out of the log file by module/file/function name -e.g. to take out all django_authopenid logs run: ->grep 'osqa\/django_authopenid' log/django.osqa.log | sed 's/^.*MSG: //' -in the example above 'sed' call truncates out a long prefix -and makes output look more meaningful - -2) DJANGO DEBUG TOOLBAR -osqa works with django debug toolbar -if debugging under apache server, check -that debug toolbar media is loaded correctly -if toolbar is enabled but you do not see it, possibly some Alias statement -in apache config is wrong in your VirtualHost or elsewhere - -3) If you discover new debugging techniques, please add here. -Possible areas to improve - at this point there is no SQL query logging, -as well as request data and http header. diff --git a/INSTALL b/INSTALL deleted file mode 100644 index f70b3ec..0000000 --- a/INSTALL +++ /dev/null @@ -1,314 +0,0 @@ -CONTENTS ------------------- -A. PREREQUISITES -B. INSTALLATION - 1. Settings file - 2. Database - 3. Running OSQA in the development server - 4. Installation under Apache/WSGI - 5. Full text search - 6. Email subscriptions - 7. Sitemap - 8. Miscellaneous -C. CONFIGURATION PARAMETERS (settings_local.py) -D. CUSTOMIZATION - - -A. PREREQUISITES ------------------------------------------------ -0. We recommend you to use python-setuptools to install pre-requirement libraries. -If you haven't installed it, please try to install it first. -e.g, sudo apt-get install python-setuptools - -1. Python2.5/2.6, MySQL, Django v1.0/1.1 -Note: email subscription sender job requires Django 1.1, everything else works with 1.0 -Make sure mysql for python provider has been installed. -sudo easy_install mysql-python - -2. Python-openid v2.2 -http://openidenabled.com/python-openid/ -sudo easy_install python-openid - -4. html5lib -http://code.google.com/p/html5lib/ -Used for HTML sanitizer -sudo easy_install html5lib - -5. Markdown2 -http://code.google.com/p/python-markdown2/ -sudo easy_install markdown2 - -6. Django Debug Toolbar -http://github.com/robhudson/django-debug-toolbar/tree/master - -7. djangosphinx (optional - for full text questions+answer+tag) -http://github.com/dcramer/django-sphinx/tree/master/djangosphinx - -8. sphinx search engine (optional, works together with djangosphinx) -http://sphinxsearch.com/downloads.html - -9. recaptcha_django -http://code.google.com/p/recaptcha-django/ - -10. python recaptcha module -http://code.google.com/p/recaptcha/ -Notice that you will need to register with recaptcha.net and receive -recaptcha public and private keys that need to be saved in your -settings_local.py file - -NOTES: django_authopenid is included into OSQA code -and is significantly modified. http://code.google.com/p/django-authopenid/ -no need to install this library - -B. INSTALLATION ------------------------------------------------ -0. Make sure you have all above python libraries installed. - - make osqa installation server-readable on Linux command might be: - chown -R yourlogin:apache /path/to/OSQA - - directories templates/upfiles and log must be server writable - - on Linux type chmod - chmod -R g+w /path/to/OSQA/upfiles - chmod -R g+w /path/to/log - - above it is assumed that webserver runs under group named "apache" - -1. Settings file - -Copy settings_local.py.dist to settings_local.py and -update all your settings. Check settings.py and update -it as well if necessory. -Section C explains configuration paramaters. - -2. Database - -Prepare your database by using the same database/account -configuration from above. -e.g, -create database osqa DEFAULT CHARACTER SET UTF8 COLLATE utf8_general_ci; -grant all on osqa.* to 'osqa'@'localhost'; -And then run "python manage.py syncdb" to synchronize your database. - -3. Running OSQA on the development server - -Run "python manage.py runserver" to startup django -development environment. -(Under Linux you can use command "python manage.py runserver `hostname -i`:8000", -where you can use any other available number for the port) - -you might want to have DEBUG=True in the beginning of settings.py -when using the test server - -4. Installation under Apache/WSGI - -4.1 Prepare wsgi script - -Make a file readable by your webserver with the following content: - ---------- -import os -import sys - -sys.path.insert(0,'/one/level/above') #insert to make sure that forum will be found -sys.path.append('/one/level/above/OSQA') #maybe this is not necessary -os.environ['DJANGO_SETTINGS_MODULE'] = 'OSQA.settings' -import django.core.handlers.wsgi -application = django.core.handlers.wsgi.WSGIHandler() ------------ - -insert method is used for path because if the forum directory name -is by accident the same as some other python module -you wull see strange errors - forum won't be found even though -it's in the python path. for example using name "test" is -not a good idea - as there is a module with such name - - -4.2 Configure webserver -Settings below are not perfect but may be a good starting point - ---------- -WSGISocketPrefix /path/to/socket/sock #must be readable and writable by apache -WSGIPythonHome /usr/local #must be readable by apache -WSGIPythonEggs /var/python/eggs #must be readable and writable by apache - -#NOTE: all urs below will need to be adjusted if -#settings.FORUM_SCRIPT_ALIAS !='' (e.g. = 'forum/') -#this allows "rooting" forum at http://example.com/forum, if you like - - ServerAdmin forum@example.com - DocumentRoot /path/to/osqa-site - ServerName example.com - - #run mod_wsgi process for django in daemon mode - #this allows avoiding confused timezone settings when - #another application runs in the same virtual host - WSGIDaemonProcess OSQA - WSGIProcessGroup OSQA - - #force all content to be served as static files - #otherwise django will be crunching images through itself wasting time - Alias /m/ /path/to/osqa-site/forum/skins/ - Alias /upfiles/ /path/to/osqa-site/forum/upfiles/ - - Order deny,allow - Allow from all - - - #this is your wsgi script described in the prev section - WSGIScriptAlias / /path/to/osqa-site/osqa.wsgi - - #this will force admin interface to work only - #through https (optional) - #"nimda" is the secret spelling of "admin" ;) - - RewriteEngine on - RewriteRule /nimda(.*)$ https://example.com/nimda$1 [L,R=301] - - CustomLog /var/log/httpd/OSQA/access_log common - ErrorLog /var/log/httpd/OSQA/error_log - -#(optional) run admin interface under https - - ServerAdmin forum@example.com - DocumentRoot /path/to/osqa-site - ServerName example.com - SSLEngine on - SSLCertificateFile /path/to/ssl-certificate/server.crt - SSLCertificateKeyFile /path/to/ssl-certificate/server.key - WSGIScriptAlias / /path/to/osqa-site/osqa.wsgi - CustomLog /var/log/httpd/OSQA/access_log common - ErrorLog /var/log/httpd/OSQA/error_log - DirectoryIndex index.html - -------------- - -5. Full text search (using sphinx search) - - Currently full text search works only with sphinx search engine - And builtin PostgreSQL (postgres only >= 8.3???) - - 5.1 Instructions for Sphinx search setup - Sphinx at this time supports only MySQL and PostgreSQL databases - to enable this, install sphinx search engine and djangosphinx - - configure sphinx, sample configuration can be found in - sphinx/sphinx.conf file usually goes somewhere in /etc tree - - build osqa index first time manually - - % indexer --config /path/to/sphinx.conf --index osqa - - setup cron job to rebuild index periodically with command - your crontab entry may be something like - - 0 9,15,21 * * * /usr/local/bin/indexer --config /etc/sphinx/sphinx.conf --all --rotate >/dev/null 2>&1 - adjust it as necessary this one will reindex three times a day at 9am 3pm and 9pm - - if your forum grows very big ( good luck with that :) you'll - need to two search indices one diff index and one main - please refer to online sphinx search documentation for the information - on the subject http://sphinxsearch.com/docs/ - - in settings_local.py set - USE_SPHINX_SEARCH=True - adjust other settings that have SPHINX_* prefix accordingly - remember that there must be trailing comma in parentheses for - SHPINX_SEARCH_INDICES tuple - particlarly with just one item! - - in settings.py look for INSTALLED_APPS - and uncomment #'djangosphinx', - - -6. Email subscriptions - - This function at the moment requires Django 1.1 - - edit paths in the file cron/send_email_alerts - set up a cron job to call cron/send_email_alerts once or twice a day - subscription sender may be tested manually in shell - by calling cron/send_email_alerts - -7. Sitemap -Sitemap will be available at /sitemap.xml -e.g yoursite.com/forum/sitemap.xml - -google will be pinged each time question, answer or -comment is saved or a question deleted - -for this to be useful - do register you sitemap with Google at -https://www.google.com/webmasters/tools/ - -8. Miscellaneous - -There are some demo scripts under sql_scripts folder, -including badges and test accounts for CNProg.com. You -don't need them to run your sample. - -C. CONFIGURATION PARAMETERS - -#the only parameter that needs to be touched in settings.py is -DEBUG=False #set to True to enable debug mode - -#all forum parameters are set in file settings_local.py - -LOG_FILENAME = 'osqa.log' #where logging messages should go -DATABASE_NAME = 'osqa' # Or path to database file if using sqlite3. -DATABASE_USER = '' # Not used with sqlite3. -DATABASE_PASSWORD = '' # Not used with sqlite3. -DATABASE_ENGINE = 'mysql' #mysql, etc -SERVER_EMAIL = '' -DEFAULT_FROM_EMAIL = '' -EMAIL_HOST_USER = '' -EMAIL_HOST_PASSWORD = '' #not necessary if mailserver is run on local machine -EMAIL_SUBJECT_PREFIX = '[OSQA] ' -EMAIL_HOST='osqa.com' -EMAIL_PORT='25' -EMAIL_USE_TLS=False -TIME_ZONE = 'America/Tijuana' -APP_TITLE = u'OSQA Q&A Forum' #title of your forum -APP_KEYWORDS = u'OSQA,forum,community' #keywords for search engines -APP_DESCRIPTION = u'Ask and answer questions.' #site description for searche engines -APP_INTRO = u'

Ask and answer questions, make the world better!

' #slogan that goes to front page in logged out mode -APP_COPYRIGHT = '' #copyright message - -#if you set FORUM_SCRIPT_ALIAS= 'forum/' -#then OSQA will run at url http://example.com/forum -#FORUM_SCRIPT_ALIAS cannot have leading slash, otherwise it can be set to anything -FORUM_SCRIPT_ALIAS = '' #no leading slash, default = '' empty string - -LANGUAGE_CODE = 'en' #forum language (see language instructions on the wiki) -EMAIL_VALIDATION = 'off' #string - on|off -MIN_USERNAME_LENGTH = 1 -EMAIL_UNIQUE = False #if True, email addresses must be unique in all accounts -APP_URL = 'http://osqa.com' #used by email notif system and RSS -GOOGLE_SITEMAP_CODE = '' #code for google site crawler (look up google webmaster tools) -GOOGLE_ANALYTICS_KEY = '' #key to enable google analytics on this site -BOOKS_ON = False #if True - books tab will be on -WIKI_ON = True #if False - community wiki feature is disabled - -#experimental - allow password login through external site -#must implement django_authopenid/external_login.py -#included prototype external_login works with Mediawiki -USE_EXTERNAL_LEGACY_LOGIN = True #if false OSQA uses it's own login/password -EXTERNAL_LEGACY_LOGIN_HOST = 'login.osqa.com' -EXTERNAL_LEGACY_LOGIN_PORT = 80 -EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME = 'OSQA' - -FEEDBACK_SITE_URL = None #None or url -LOGIN_URL = '/%s%s%s' % (FORUM_SCRIPT_ALIAS,'account/','signin/') - -DJANGO_VERSION = 1.1 #must be either 1.0 or 1.1 -RESOURCE_REVISION=4 #increment when you update media files - clients will be forced to load new version - -D. Customization - -Other than settings_local.py the following will most likely need customization: -* locale/*/django.po - language files that may also contain your site-specific messages - if you want to start with english messages file - look for words like "forum" and - "OSQA" in the msgstr lines -* templates/header.html and templates/footer.html may contain extra links -* templates/about.html - a place to explain for is your forum for -* templates/faq.html - put answers to users frequent questions -* templates/content/style/style.css - modify style sheet to add disctinctive look to your forum diff --git a/PENDING b/PENDING deleted file mode 100644 index 2931303..0000000 --- a/PENDING +++ /dev/null @@ -1,28 +0,0 @@ -There are two kinds of things that can be done: -refactorings (think of jogging in the morning, going to a spa, well make the code better :) -new features (go to law school, get a job, do something real) -Just a joke - pick yourself a task and work on it. - -==Refactoring== -* validate HTML -* set up loading of default settings from inside the /forum dir -* automatic dependency checking for modules -* propose how to rename directory forum --> osqa - without breaking things and keeping name of the project root - named the same way - osqa - -==New features== -Whoever wants - pick a feature from the WISH_LIST -add it here and start working on it -If you are not starting immediately - leave it on the wishlist :) - -==Notes== -1)after this is done most new suggested features - may be worked on easily since most of them - only require editing view functions and templates - - However, anyone can work on new features anyway - you'll - just have to probably copy-paste your code into - the branch undergoing refactoring which involves - splitting the files. Auto merging across split points - is harder or impossible. diff --git a/README b/README deleted file mode 100644 index 2a209b7..0000000 --- a/README +++ /dev/null @@ -1,6 +0,0 @@ -This is OSQA project - open source Q&A system - -Demo site is http://osqa.net - -OSQA is based on code of CNPROG, originally created by Mike Chen and Sailing Cai. - diff --git a/ROADMAP.rst b/ROADMAP.rst deleted file mode 100644 index 42f2e8c..0000000 --- a/ROADMAP.rst +++ /dev/null @@ -1,32 +0,0 @@ -This document is a map for our activities down the road - therefore ROADMAP. -ROADMAP does not specify deadlines - those belong to the PENDING file - -Intro -========= -ROADMAP aims to streamline activities of the OSQA open source project and -to minimize ad-hoc approaches of "big-picture" level. - -With one exception: under extreme time pressure improvised approaches are perfectly acceptable. - -Items in this document must be discussed in public via dev@osqa.net - -Architecture -============= - -Sub-systems ------------------ -* authentication system -* Q&A system - -Authentication system -------------------------- -* MUST authenticate people visiting the website via web browsers. -* Upon successful authentication must associates the visitor with - his/her Django system user account -* MUST allow multiple methods of authentication to the same account -* MUST support a method to recover lost authentication link by email -* MAY offer an option to "soft-validate" user's email (send a link - with a special key, so that user clicks and we know that email is valid) - "soft" - meaning that lack of validation won't block people - from using the site - diff --git a/WISH_LIST b/WISH_LIST deleted file mode 100644 index 6b10687..0000000 --- a/WISH_LIST +++ /dev/null @@ -1,10 +0,0 @@ -* The wonder bar (integrated the search / ask functionality) -* The authentication system ??? -* allow multiple logins to the same account -* more advanced templating/skinning system -* per-tag email subscriptions -* view for personalized news on the site -* a little flag popping when there are news -* drill-down mode for navigation by tags -* improved admin console -* sort out mess with profile - currently we patch django User diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/context.py b/context.py deleted file mode 100644 index 9e22550..0000000 --- a/context.py +++ /dev/null @@ -1,47 +0,0 @@ -from django.conf import settings -def application_settings(context): - my_settings = { - 'APP_TITLE' : settings.APP_TITLE, - 'APP_SHORT_NAME' : settings.APP_SHORT_NAME, - 'APP_URL' : settings.APP_URL, - 'APP_KEYWORDS' : settings.APP_KEYWORDS, - 'APP_DESCRIPTION' : settings.APP_DESCRIPTION, - 'APP_INTRO' : settings.APP_INTRO, - 'EMAIL_VALIDATION': settings.EMAIL_VALIDATION, - 'LANGUAGE_CODE': settings.LANGUAGE_CODE, - 'GOOGLE_SITEMAP_CODE':settings.GOOGLE_SITEMAP_CODE, - 'GOOGLE_ANALYTICS_KEY':settings.GOOGLE_ANALYTICS_KEY, - 'BOOKS_ON':settings.BOOKS_ON, - 'WIKI_ON':settings.WIKI_ON, - 'USE_EXTERNAL_LEGACY_LOGIN':settings.USE_EXTERNAL_LEGACY_LOGIN, - 'RESOURCE_REVISION':settings.RESOURCE_REVISION, - 'USE_SPHINX_SEARCH':settings.USE_SPHINX_SEARCH, - 'OSQA_SKIN':settings.OSQA_DEFAULT_SKIN, - } - return {'settings':my_settings} - -def auth_processor(request): - """ - Returns context variables required by apps that use Django's authentication - system. - - If there is no 'user' attribute in the request, uses AnonymousUser (from - django.contrib.auth). - """ - if hasattr(request, 'user'): - user = request.user - if user.is_authenticated(): - messages = user.message_set.all() - else: - messages = None - else: - from django.contrib.auth.models import AnonymousUser - user = AnonymousUser() - messages = None - - from django.core.context_processors import PermWrapper - return { - 'user': user, - 'messages': messages, - 'perms': PermWrapper(user), - } diff --git a/cron/send_email_alerts b/cron/send_email_alerts deleted file mode 100644 index 6358b59..0000000 --- a/cron/send_email_alerts +++ /dev/null @@ -1,4 +0,0 @@ -PYTHONPATH=/path/to/dir/above/forum -export PYTHONPATH -APP_ROOT=$PYTHONPATH/nmr-forum2 -/path/to/python $APP_ROOT/manage.py send_email_alerts diff --git a/dos2unix.sh b/dos2unix.sh deleted file mode 100644 index 2864426..0000000 --- a/dos2unix.sh +++ /dev/null @@ -1,12 +0,0 @@ -#please take care not to dos2unix anything in your .git directory -#because that will probably break your repo -dos2unix `find . -name '*.py'` -dos2unix `find . -name '*.po'` -dos2unix `find . -name '*.js'` -dos2unix `find . -name '*.css'` -dos2unix `find . -name '*.txt'` -dos2unix `find ./sphinx -type f` -dos2unix `find ./cron -type f` -dos2unix settings_local.py.dist -dos2unix README -dos2unix INSTALL diff --git a/forum/__init__.py b/forum/__init__.py deleted file mode 100644 index 85cd5d2..0000000 --- a/forum/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__all__ = ['admin','auth','const','feed','forms','managers','models','sitemap','urls','views'] diff --git a/forum/admin.py b/forum/admin.py deleted file mode 100644 index 88643b9..0000000 --- a/forum/admin.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.contrib import admin -from models import * - - -class AnonymousQuestionAdmin(admin.ModelAdmin): - """AnonymousQuestion admin class""" - -class QuestionAdmin(admin.ModelAdmin): - """Question admin class""" - -class TagAdmin(admin.ModelAdmin): - """Tag admin class""" - -class Answerdmin(admin.ModelAdmin): - """Answer admin class""" - -class CommentAdmin(admin.ModelAdmin): - """ admin class""" - -class VoteAdmin(admin.ModelAdmin): - """ admin class""" - -class FlaggedItemAdmin(admin.ModelAdmin): - """ admin class""" - -class FavoriteQuestionAdmin(admin.ModelAdmin): - """ admin class""" - -class QuestionRevisionAdmin(admin.ModelAdmin): - """ admin class""" - -class AnswerRevisionAdmin(admin.ModelAdmin): - """ admin class""" - -class AwardAdmin(admin.ModelAdmin): - """ admin class""" - -class BadgeAdmin(admin.ModelAdmin): - """ admin class""" - -class ReputeAdmin(admin.ModelAdmin): - """ admin class""" - -class ActivityAdmin(admin.ModelAdmin): - """ admin class""" - -#class BookAdmin(admin.ModelAdmin): -# """ admin class""" - -#class BookAuthorInfoAdmin(admin.ModelAdmin): -# """ admin class""" - -#class BookAuthorRssAdmin(admin.ModelAdmin): -# """ admin class""" - - -admin.site.register(Question, QuestionAdmin) -admin.site.register(Tag, TagAdmin) -admin.site.register(Answer, Answerdmin) -admin.site.register(Comment, CommentAdmin) -admin.site.register(Vote, VoteAdmin) -admin.site.register(FlaggedItem, FlaggedItemAdmin) -admin.site.register(FavoriteQuestion, FavoriteQuestionAdmin) -admin.site.register(QuestionRevision, QuestionRevisionAdmin) -admin.site.register(AnswerRevision, AnswerRevisionAdmin) -admin.site.register(Badge, BadgeAdmin) -admin.site.register(Award, AwardAdmin) -admin.site.register(Repute, ReputeAdmin) -admin.site.register(Activity, ActivityAdmin) -#admin.site.register(Book, BookAdmin) -#admin.site.register(BookAuthorInfo, BookAuthorInfoAdmin) -#admin.site.register(BookAuthorRss, BookAuthorRssAdmin) diff --git a/forum/auth.py b/forum/auth.py deleted file mode 100644 index 3533b9c..0000000 --- a/forum/auth.py +++ /dev/null @@ -1,498 +0,0 @@ -""" -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.utils.translation import ugettext as _ -from django.db import transaction -from models import Repute -from models import Question -from models import Answer -from const import TYPE_REPUTATION -import logging -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_moderate_users(user): - return user.is_superuser - -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,subject): - """Determines if a User can add comments to Questions and Answers.""" - if user.is_authenticated(): - if user.id == subject.author.id: - return True - if user.reputation >= LEAVE_COMMENTS: - return True - if user.is_superuser: - return True - if isinstance(subject,Answer) and subject.question.author.id == user.id: - return True - return False - -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): - if user.is_superuser: - return True - elif user.is_authenticated() and user == post.author: - if isinstance(post,Answer): - return True - elif isinstance(post,Question): - answers = post.answers.all() - for answer in answers: - if user != answer.author and answer.deleted == False: - return False - return True - else: - return False - else: - return False - -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) - if (result > 0): - return result - else: - return 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() - logging.debug('now restoring something') - if isinstance(post,Answer): - logging.debug('updated answer count on undelete, have %d' % post.question.answer_count) - Question.objects.update_answer_count(post.question) - elif isinstance(post,Question): - 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() - - if isinstance(post, Question): - 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() - else: - tag.used_count = tag.used_count - 1 - tag.save() - - answers = post.answers.all() - if user == post.author: - if len(answers) > 0: - msg = _('Your question and all of it\'s answers have been deleted') - else: - msg = _('Your question has been deleted') - else: - if len(answers) > 0: - msg = _('The question and all of it\'s answers have been deleted') - else: - msg = _('The question has been deleted') - user.message_set.create(message=msg) - logging.debug('posted a message %s' % msg) - for answer in answers: - onDeleted(answer, user) - elif isinstance(post, Answer): - Question.objects.update_answer_count(post.question) - logging.debug('updated answer count to %d' % post.question.answer_count) diff --git a/forum/authentication/__init__.py b/forum/authentication/__init__.py deleted file mode 100755 index e83ba87..0000000 --- a/forum/authentication/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -import re -from forum.modules import get_modules_script_classes -from forum.authentication.base import AuthenticationConsumer, ConsumerTemplateContext - -class ConsumerAndContext(): - def __init__(self, id, consumer, context): - self.id = id - self.consumer = consumer() - - context.id = id - self.context = context - -consumers = dict([ - (re.sub('AuthConsumer$', '', name).lower(), cls) for name, cls - in get_modules_script_classes('authentication', AuthenticationConsumer).items() - if not re.search('AbstractAuthConsumer$', name) - ]) - -contexts = dict([ - (re.sub('AuthContext$', '', name).lower(), cls) for name, cls - in get_modules_script_classes('authentication', ConsumerTemplateContext).items() - ]) - -AUTH_PROVIDERS = dict([ - (name, ConsumerAndContext(name, consumers[name], contexts[name])) for name in consumers.keys() - if name in contexts - ]) \ No newline at end of file diff --git a/forum/authentication/base.py b/forum/authentication/base.py deleted file mode 100755 index 995f7c9..0000000 --- a/forum/authentication/base.py +++ /dev/null @@ -1,40 +0,0 @@ - -class AuthenticationConsumer(object): - - def prepare_authentication_request(self, request, redirect_to): - raise NotImplementedError() - - def process_authentication_request(self, response): - raise NotImplementedError() - - def get_user_data(self, key): - raise NotImplementedError() - - -class ConsumerTemplateContext(object): - """ - Class that provides information about a certain authentication provider context in the signin page. - - class attributes: - - mode - one of BIGICON, SMALLICON, FORM - - human_name - the human readable name of the provider - - extra_js - some providers require us to load extra javascript on the signin page for them to work, - this is the place to add those files in the form of a list - - extra_css - same as extra_js but for css files - """ - mode = '' - weight = 500 - human_name = '' - extra_js = [] - extra_css = [] - show_to_logged_in_user = True - -class InvalidAuthentication(Exception): - def __init__(self, message): - self.message = message - - \ No newline at end of file diff --git a/forum/authentication/forms.py b/forum/authentication/forms.py deleted file mode 100755 index 0484134..0000000 --- a/forum/authentication/forms.py +++ /dev/null @@ -1,31 +0,0 @@ -from forum.utils.forms import NextUrlField, UserNameField, UserEmailField -from forum.models import EmailFeedSetting, Question -from django.contrib.contenttypes.models import ContentType -from django.utils.translation import ugettext as _ -from django import forms -from forum.forms import EditUserEmailFeedsForm -import logging - -class SimpleRegistrationForm(forms.Form): - next = NextUrlField() - username = UserNameField() - email = UserEmailField() - - -class SimpleEmailSubscribeForm(forms.Form): - SIMPLE_SUBSCRIBE_CHOICES = ( - ('y',_('okay, let\'s try!')), - ('n',_('no OSQA community email please, thanks')) - ) - subscribe = forms.ChoiceField(widget=forms.widgets.RadioSelect(), \ - error_messages={'required':_('please choose one of the options above')}, - choices=SIMPLE_SUBSCRIBE_CHOICES) - - def save(self,user=None): - EFF = EditUserEmailFeedsForm - if self.cleaned_data['subscribe'] == 'y': - email_settings_form = EFF() - logging.debug('%s wants to subscribe' % user.username) - else: - email_settings_form = EFF(initial=EFF.NO_EMAIL_INITIAL) - email_settings_form.save(user,save_unbound=True) diff --git a/forum/const.py b/forum/const.py deleted file mode 100644 index 76fd4a2..0000000 --- a/forum/const.py +++ /dev/null @@ -1,92 +0,0 @@ -# encoding:utf-8 -from django.utils.translation import ugettext as _ -""" -All constants could be used in other modules -For reasons that models, views can't have unicode text in this project, all unicode text go here. -""" -CLOSE_REASONS = ( - (1, _('duplicate question')), - (2, _('question is off-topic or not relevant')), - (3, _('too subjective and argumentative')), - (4, _('is not an answer to the question')), - (5, _('the question is answered, right answer was accepted')), - (6, _('problem is not reproducible or outdated')), - #(7, u'太局部、本地化的问题',) - (7, _('question contains offensive inappropriate, or malicious remarks')), - (8, _('spam or advertising')), -) - -TYPE_REPUTATION = ( - (1, 'gain_by_upvoted'), - (2, 'gain_by_answer_accepted'), - (3, 'gain_by_accepting_answer'), - (4, 'gain_by_downvote_canceled'), - (5, 'gain_by_canceling_downvote'), - (-1, 'lose_by_canceling_accepted_answer'), - (-2, 'lose_by_accepted_answer_cancled'), - (-3, 'lose_by_downvoted'), - (-4, 'lose_by_flagged'), - (-5, 'lose_by_downvoting'), - (-6, 'lose_by_flagged_lastrevision_3_times'), - (-7, 'lose_by_flagged_lastrevision_5_times'), - (-8, 'lose_by_upvote_canceled'), -) - -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 -TYPE_ACTIVITY_QUESTION_EMAIL_UPDATE_SENT = 18 -#TYPE_ACTIVITY_EDIT_QUESTION=17 -#TYPE_ACTIVITY_EDIT_ANSWER=18 - -TYPE_ACTIVITY = ( - (TYPE_ACTIVITY_ASK_QUESTION, _('question')), - (TYPE_ACTIVITY_ANSWER, _('answer')), - (TYPE_ACTIVITY_COMMENT_QUESTION, _('commented question')), - (TYPE_ACTIVITY_COMMENT_ANSWER, _('commented answer')), - (TYPE_ACTIVITY_UPDATE_QUESTION, _('edited question')), - (TYPE_ACTIVITY_UPDATE_ANSWER, _('edited answer')), - (TYPE_ACTIVITY_PRIZE, _('received award')), - (TYPE_ACTIVITY_MARK_ANSWER, _('marked best answer')), - (TYPE_ACTIVITY_VOTE_UP, _('upvoted')), - (TYPE_ACTIVITY_VOTE_DOWN, _('downvoted')), - (TYPE_ACTIVITY_CANCEL_VOTE, _('canceled vote')), - (TYPE_ACTIVITY_DELETE_QUESTION, _('deleted question')), - (TYPE_ACTIVITY_DELETE_ANSWER, _('deleted answer')), - (TYPE_ACTIVITY_MARK_OFFENSIVE, _('marked offensive')), - (TYPE_ACTIVITY_UPDATE_TAGS, _('updated tags')), - (TYPE_ACTIVITY_FAVORITE, _('selected favorite')), - (TYPE_ACTIVITY_USER_FULL_UPDATED, _('completed user profile')), - (TYPE_ACTIVITY_QUESTION_EMAIL_UPDATE_SENT, _('email update sent to user')), -) - -TYPE_RESPONSE = { - 'QUESTION_ANSWERED' : 'question_answered', - 'QUESTION_COMMENTED': 'question_commented', - 'ANSWER_COMMENTED' : 'answer_commented', - 'ANSWER_ACCEPTED' : 'answer_accepted', -} - -CONST = { - 'closed' : _('[closed]'), - 'deleted' : _('[deleted]'), - 'default_version' : _('initial version'), - 'retagged' : _('retagged'), -} - -#how to filter questions by tags in email digests? -TAG_EMAIL_FILTER_CHOICES = (('ignored', _('exclude ignored tags')),('interesting',_('allow only selected tags'))) diff --git a/forum/feed.py b/forum/feed.py deleted file mode 100644 index e4b929e..0000000 --- a/forum/feed.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/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 -from django.conf import settings -class RssLastestQuestionsFeed(Feed): - title = settings.APP_TITLE + _(' - ')+ _('latest questions') - link = settings.APP_URL #+ '/' + _('question/') - description = settings.APP_DESCRIPTION - #ttl = 10 - copyright = settings.APP_COPYRIGHT - - def item_link(self, item): - return self.link + item.get_absolute_url() - - 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('-last_activity_at')[:30] - -def main(): - pass - -if __name__ == '__main__': - main() diff --git a/forum/forms.py b/forum/forms.py deleted file mode 100644 index c157aa4..0000000 --- a/forum/forms.py +++ /dev/null @@ -1,359 +0,0 @@ -import re -from datetime import date -from django import forms -from models import * -from const import * -from django.utils.translation import ugettext as _ -from django.contrib.auth.models import User -from django.contrib.contenttypes.models import ContentType -from django.utils.safestring import mark_safe -from forum.utils.forms import NextUrlField, UserNameField, SetPasswordForm -from recaptcha_django import ReCaptchaField -from django.conf import settings -import logging - -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.help_text = _('Tags are short keywords, with no spaces within. Up to five tags can be used.') - 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')) - - split_re = re.compile(r'[ ,]+') - list = split_re.split(data) - 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') - def clean(self,value): - return value and settings.WIKI_ON - -class EmailNotifyField(forms.BooleanField): - def __init__(self, *args, **kwargs): - super(EmailNotifyField, self).__init__(*args, **kwargs) - self.required = False - self.widget.attrs['class'] = 'nomargin' - -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 ModerateUserForm(forms.ModelForm): - is_approved = forms.BooleanField(label=_("Automatically accept user's contributions for the email updates"), - required=False) - - def clean_is_approved(self): - if 'is_approved' not in self.cleaned_data: - self.cleaned_data['is_approved'] = False - return self.cleaned_data['is_approved'] - - class Meta: - model = User - fields = ('is_approved',) - -class NotARobotForm(forms.Form): - recaptcha = ReCaptchaField() - -class FeedbackForm(forms.Form): - name = forms.CharField(label=_('Your name:'), required=False) - email = forms.EmailField(label=_('Email (not shared with anyone):'), required=False) - message = forms.CharField(label=_('Your message:'), max_length=800,widget=forms.Textarea(attrs={'cols':60})) - next = NextUrlField() - -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})) - email_notify = EmailNotifyField() - def __init__(self, question, user, *args, **kwargs): - super(AnswerForm, self).__init__(*args, **kwargs) - self.fields['email_notify'].widget.attrs['id'] = 'question-subscribe-updates'; - if question.wiki and settings.WIKI_ON: - self.fields['wiki'].initial = True - if user.is_authenticated(): - if user in question.followed_by.all(): - self.fields['email_notify'].initial = True - return - self.fields['email_notify'].initial = False - - -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=True, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) - if settings.EDITABLE_SCREEN_NAME: - username = UserNameField(label=_('Screen name')) - 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) - logging.debug('initializing the form') - if settings.EDITABLE_SCREEN_NAME: - self.fields['username'].initial = user.username - self.fields['username'].user_instance = user - 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']: - #todo dry it, there is a similar thing in openidauth - if settings.EMAIL_UNIQUE == True: - 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')) - return self.cleaned_data['email'] - -class TagFilterSelectionForm(forms.ModelForm): - tag_filter_setting = forms.ChoiceField(choices=TAG_EMAIL_FILTER_CHOICES, #imported from forum/const.py - initial='ignored', - label=_('Choose email tag filter'), - widget=forms.RadioSelect) - class Meta: - model = User - fields = ('tag_filter_setting',) - - def save(self): - before = self.instance.tag_filter_setting - super(TagFilterSelectionForm, self).save() - after = self.instance.tag_filter_setting #User.objects.get(pk=self.instance.id).tag_filter_setting - if before != after: - return True - return False - - -class ChangePasswordForm(SetPasswordForm): - """ change password form """ - oldpw = forms.CharField(widget=forms.PasswordInput(attrs={'class':'required'}), - label=mark_safe(_('Current password'))) - - def __init__(self, data=None, user=None, *args, **kwargs): - if user is None: - raise TypeError("Keyword argument 'user' must be supplied") - super(ChangePasswordForm, self).__init__(data, *args, **kwargs) - self.user = user - - def clean_oldpw(self): - """ test old password """ - if not self.user.check_password(self.cleaned_data['oldpw']): - raise forms.ValidationError(_("Old password is incorrect. \ - Please enter the correct password.")) - return self.cleaned_data['oldpw'] - -class EditUserEmailFeedsForm(forms.Form): - WN = (('w',_('weekly')),('n',_('no email'))) - DWN = (('d',_('daily')),('w',_('weekly')),('n',_('no email'))) - FORM_TO_MODEL_MAP = { - 'all_questions':'q_all', - 'asked_by_me':'q_ask', - 'answered_by_me':'q_ans', - 'individually_selected':'q_sel', - } - NO_EMAIL_INITIAL = { - 'all_questions':'n', - 'asked_by_me':'n', - 'answered_by_me':'n', - 'individually_selected':'n', - } - asked_by_me = forms.ChoiceField(choices=DWN,initial='w', - widget=forms.RadioSelect, - label=_('Asked by me')) - answered_by_me = forms.ChoiceField(choices=DWN,initial='w', - widget=forms.RadioSelect, - label=_('Answered by me')) - individually_selected = forms.ChoiceField(choices=DWN,initial='w', - widget=forms.RadioSelect, - label=_('Individually selected')) - all_questions = forms.ChoiceField(choices=DWN,initial='w', - widget=forms.RadioSelect, - label=_('Entire forum (tag filtered)'),) - - def set_initial_values(self,user=None): - KEY_MAP = dict([(v,k) for k,v in self.FORM_TO_MODEL_MAP.iteritems()]) - if user != None: - settings = EmailFeedSetting.objects.filter(subscriber=user) - initial_values = {} - for setting in settings: - feed_type = setting.feed_type - form_field = KEY_MAP[feed_type] - frequency = setting.frequency - initial_values[form_field] = frequency - self.initial = initial_values - return self - - def reset(self): - self.cleaned_data['all_questions'] = 'n' - self.cleaned_data['asked_by_me'] = 'n' - self.cleaned_data['answered_by_me'] = 'n' - self.cleaned_data['individually_selected'] = 'n' - self.initial = self.NO_EMAIL_INITIAL - return self - - def save(self,user,save_unbound=False): - """ - with save_unbound==True will bypass form validation and save initial values - """ - changed = False - for form_field, feed_type in self.FORM_TO_MODEL_MAP.items(): - s, created = EmailFeedSetting.objects.get_or_create(subscriber=user,\ - feed_type=feed_type) - if save_unbound: - #just save initial values instead - if form_field in self.initial: - new_value = self.initial[form_field] - else: - new_value = self.fields[form_field].initial - else: - new_value = self.cleaned_data[form_field] - if s.frequency != new_value: - s.frequency = new_value - s.save() - changed = True - else: - if created: - s.save() - if form_field == 'individually_selected': - feed_type = ContentType.objects.get_for_model(Question) - user.followed_questions.clear() - return changed - diff --git a/forum/management/__init__.py b/forum/management/__init__.py deleted file mode 100644 index 8266592..0000000 --- a/forum/management/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from forum.modules import get_modules_script - -get_modules_script('management') \ No newline at end of file diff --git a/forum/management/commands/__init__.py b/forum/management/commands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/forum/management/commands/base_command.py b/forum/management/commands/base_command.py deleted file mode 100644 index c073bf7..0000000 --- a/forum/management/commands/base_command.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/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: 22/01/2009 -# Copyright: (c) Mike 2009 -# Licence: GPL V2 -#------------------------------------------------------------------------------- - -from datetime import datetime, date -from django.core.management.base import NoArgsCommand -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 * - -class BaseCommand(NoArgsCommand): - def update_activities_auditted(self, cursor, activity_ids): - # update processed rows to auditted - if len(activity_ids): - query = "UPDATE activity SET is_auditted = 1 WHERE id in (%s)"\ - % ','.join('%s' % item for item in activity_ids) - cursor.execute(query) - - - - - diff --git a/forum/management/commands/clean_award_badges.py b/forum/management/commands/clean_award_badges.py deleted file mode 100644 index 117e3a5..0000000 --- a/forum/management/commands/clean_award_badges.py +++ /dev/null @@ -1,59 +0,0 @@ -#------------------------------------------------------------------------------- -# 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 -# -# Created: 18/01/2009 -# Copyright: (c) Mike 2009 -# Licence: GPL V2 -#------------------------------------------------------------------------------- -#!/usr/bin/env python -#encoding:utf-8 -from django.core.management.base import NoArgsCommand -from django.db import connection -from django.shortcuts import get_object_or_404 -from django.contrib.contenttypes.models import ContentType - -from forum.models import * - -class Command(NoArgsCommand): - def handle_noargs(self, **options): - try: - try: - self.clean_awards() - except Exception, e: - print e - finally: - connection.close() - - def clean_awards(self): - Award.objects.all().delete() - - award_type =ContentType.objects.get_for_model(Award) - Activity.objects.filter(content_type=award_type).delete() - - for user in User.objects.all(): - user.gold = 0 - user.silver = 0 - user.bronze = 0 - user.save() - - for badge in Badge.objects.all(): - badge.awarded_count = 0 - badge.save() - - query = "UPDATE activity SET is_auditted = 0" - cursor = connection.cursor() - try: - cursor.execute(query) - finally: - cursor.close() - connection.close() - -def main(): - pass - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/forum/management/commands/message_to_everyone.py b/forum/management/commands/message_to_everyone.py deleted file mode 100644 index c020c17..0000000 --- a/forum/management/commands/message_to_everyone.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.core.management.base import NoArgsCommand -from django.contrib.auth.models import User -import sys - -class Command(NoArgsCommand): - def handle_noargs(self, **options): - msg = None - if msg == None: - print 'to run this command, please first edit the file %s' % __file__ - sys.exit(1) - for u in User.objects.all(): - u.message_set.create(message = msg % u.username) diff --git a/forum/management/commands/multi_award_badges.py b/forum/management/commands/multi_award_badges.py deleted file mode 100644 index 6b330cf..0000000 --- a/forum/management/commands/multi_award_badges.py +++ /dev/null @@ -1,348 +0,0 @@ -#!/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: 22/01/2009 -# Copyright: (c) Mike 2009 -# Licence: GPL V2 -#------------------------------------------------------------------------------- - -from datetime import datetime, date -from django.core.management.base import NoArgsCommand -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 -""" - -class Command(BaseCommand): - def handle_noargs(self, **options): - try: - try: - self.delete_question_be_voted_up_3() - self.delete_answer_be_voted_up_3() - self.delete_question_be_vote_down_3() - self.delete_answer_be_voted_down_3() - self.answer_be_voted_up_10() - self.question_be_voted_up_10() - self.question_view_1000() - self.answer_self_question_be_voted_up_3() - self.answer_be_voted_up_100() - self.question_be_voted_up_100() - self.question_be_favorited_100() - self.question_view_10000() - self.answer_be_voted_up_25() - self.question_be_voted_up_25() - self.question_be_favorited_25() - self.question_view_2500() - self.answer_be_accepted_and_voted_up_40() - self.question_be_answered_after_60_days_and_be_voted_up_5() - self.created_tag_be_used_in_question_50() - except Exception, e: - print e - finally: - connection.close() - - def delete_question_be_voted_up_3(self): - """ - (1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0), - """ - query = "SELECT act.id, act.user_id, act.object_id FROM activity act, question q WHERE act.object_id = q.id AND\ - act.activity_type = %s AND\ - q.vote_up_count >=3 AND \ - act.is_auditted = 0" % (TYPE_ACTIVITY_DELETE_QUESTION) - self.__process_activities_badge(query, 1, Question) - - def delete_answer_be_voted_up_3(self): - """ - (1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0), - """ - query = "SELECT act.id, act.user_id, act.object_id FROM activity act, answer an WHERE act.object_id = an.id AND\ - act.activity_type = %s AND\ - an.vote_up_count >=3 AND \ - act.is_auditted = 0" % (TYPE_ACTIVITY_DELETE_ANSWER) - self.__process_activities_badge(query, 1, Answer) - - def delete_question_be_vote_down_3(self): - """ - (2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0), - """ - query = "SELECT act.id, act.user_id, act.object_id FROM activity act, question q WHERE act.object_id = q.id AND\ - act.activity_type = %s AND\ - q.vote_down_count >=3 AND \ - act.is_auditted = 0" % (TYPE_ACTIVITY_DELETE_QUESTION) - content_type = ContentType.objects.get_for_model(Question) - self.__process_activities_badge(query, 2, Question) - - def delete_answer_be_voted_down_3(self): - """ - (2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0), - """ - query = "SELECT act.id, act.user_id, act.object_id FROM activity act, answer an WHERE act.object_id = an.id AND\ - act.activity_type = %s AND\ - an.vote_down_count >=3 AND \ - act.is_auditted = 0" % (TYPE_ACTIVITY_DELETE_ANSWER) - self.__process_activities_badge(query, 2, Answer) - - def answer_be_voted_up_10(self): - """ - (3, '优秀回答', 3, '优秀回答', '回答好评10次以上', 1, 0), - """ - query = "SELECT act.id, 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.is_auditted = 0" % (TYPE_ACTIVITY_ANSWER) - self.__process_activities_badge(query, 3, Answer) - - def question_be_voted_up_10(self): - """ - (4, '优秀问题', 3, '优秀问题', '问题好评10次以上', 1, 0), - """ - query = "SELECT act.id, act.user_id, act.object_id FROM \ - activity act, question q WHERE act.object_id = q.id AND\ - act.activity_type = %s AND \ - q.vote_up_count >= 10 AND\ - act.is_auditted = 0" % (TYPE_ACTIVITY_ASK_QUESTION) - self.__process_activities_badge(query, 4, Question) - - def question_view_1000(self): - """ - (6, '流行问题', 3, '流行问题', '问题的浏览量超过1000人次', 1, 0), - """ - query = "SELECT act.id, act.user_id, act.object_id FROM \ - activity act, question q WHERE act.activity_type = %s AND\ - act.object_id = q.id AND \ - q.view_count >= 1000 AND\ - act.object_id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 6) - self.__process_activities_badge(query, 6, Question, False) - - def answer_self_question_be_voted_up_3(self): - """ - (17, '自学成才', 3, '自学成才', '回答自己的问题并且有3个以上赞成票', 1, 0), - """ - query = "SELECT act.id, act.user_id, act.object_id FROM \ - activity act, answer an WHERE act.activity_type = %s AND\ - act.object_id = an.id AND\ - an.vote_up_count >= 3 AND\ - act.user_id = (SELECT user_id FROM question q WHERE q.id = an.question_id) AND\ - act.object_id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 17) - self.__process_activities_badge(query, 17, Question, False) - - def answer_be_voted_up_100(self): - """ - (18, '最有价值回答', 1, '最有价值回答', '回答超过100次赞成票', 1, 0), - """ - query = "SELECT an.id, an.author_id FROM answer an WHERE an.vote_up_count >= 100 AND an.id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (18) - - self.__process_badge(query, 18, Answer) - - def question_be_voted_up_100(self): - """ - (19, '最有价值问题', 1, '最有价值问题', '问题超过100次赞成票', 1, 0), - """ - query = "SELECT q.id, q.author_id FROM question q WHERE q.vote_up_count >= 100 AND q.id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (19) - - self.__process_badge(query, 19, Question) - - def question_be_favorited_100(self): - """ - (20, '万人迷', 1, '万人迷', '问题被100人以上收藏', 1, 0), - """ - query = "SELECT q.id, q.author_id FROM question q WHERE q.favourite_count >= 100 AND q.id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (20) - - self.__process_badge(query, 20, Question) - - def question_view_10000(self): - """ - (21, '著名问题', 1, '著名问题', '问题的浏览量超过10000人次', 1, 0), - """ - query = "SELECT q.id, q.author_id FROM question q WHERE q.view_count >= 10000 AND q.id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (21) - - self.__process_badge(query, 21, Question) - - def answer_be_voted_up_25(self): - """ - (23, '极好回答', 2, '极好回答', '回答超过25次赞成票', 1, 0), - """ - query = "SELECT a.id, a.author_id FROM answer a WHERE a.vote_up_count >= 25 AND a.id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (23) - - self.__process_badge(query, 23, Answer) - - def question_be_voted_up_25(self): - """ - (24, '极好问题', 2, '极好问题', '问题超过25次赞成票', 1, 0), - """ - query = "SELECT q.id, q.author_id FROM question q WHERE q.vote_up_count >= 25 AND q.id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (24) - - self.__process_badge(query, 24, Question) - - def question_be_favorited_25(self): - """ - (25, '受欢迎问题', 2, '受欢迎问题', '问题被25人以上收藏', 1, 0), - """ - query = "SELECT q.id, q.author_id FROM question q WHERE q.favourite_count >= 25 AND q.id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (25) - - self.__process_badge(query, 25, Question) - - def question_view_2500(self): - """ - (31, '最受关注问题', 2, '最受关注问题', '问题的浏览量超过2500人次', 1, 0), - """ - query = "SELECT q.id, q.author_id FROM question q WHERE q.view_count >= 2500 AND q.id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (31) - - self.__process_badge(query, 31, Question) - - def answer_be_accepted_and_voted_up_40(self): - """ - (34, '导师', 2, '导师', '被指定为最佳答案并且赞成票40以上', 1, 0), - """ - query = "SELECT a.id, a.author_id FROM answer a WHERE a.vote_up_count >= 40 AND\ - a.accepted = 1 AND\ - a.id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (34) - - self.__process_badge(query, 34, Answer) - - def question_be_answered_after_60_days_and_be_voted_up_5(self): - """ - (35, '巫师', 2, '巫师', '在提问60天之后回答并且赞成票5次以上', 1, 0), - """ - query = "SELECT a.id, a.author_id FROM question q, answer a WHERE q.id = a.question_id AND\ - DATEDIFF(a.added_at, q.added_at) >= 60 AND\ - a.vote_up_count >= 5 AND \ - a.id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (35) - - self.__process_badge(query, 35, Answer) - - def created_tag_be_used_in_question_50(self): - """ - (36, '分类专家', 2, '分类专家', '创建的标签被50个以上问题使用', 1, 0); - """ - query = "SELECT t.id, t.created_by_id FROM tag t, auth_user u WHERE t.created_by_id = u.id AND \ - t. used_count >= 50 AND \ - t.id NOT IN \ - (SELECT object_id FROM award WHERE award.badge_id = %s)" % (36) - - self.__process_badge(query, 36, Tag) - - def __process_activities_badge(self, query, badge, content_object, update_auditted=True): - content_type = ContentType.objects.get_for_model(content_object) - - cursor = connection.cursor() - try: - cursor.execute(query) - rows = cursor.fetchall() - - if update_auditted: - activity_ids = [] - badge = get_object_or_404(Badge, id=badge) - for row in rows: - activity_id = row[0] - user_id = row[1] - object_id = row[2] - - 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() - - if update_auditted: - activity_ids.append(activity_id) - - if update_auditted: - self.update_activities_auditted(cursor, activity_ids) - finally: - cursor.close() - - def __process_badge(self, query, badge, content_object): - content_type = ContentType.objects.get_for_model(Answer) - cursor = connection.cursor() - try: - cursor.execute(query) - rows = cursor.fetchall() - - badge = get_object_or_404(Badge, id=badge) - for row in rows: - object_id = row[0] - user_id = row[1] - - 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() - finally: - cursor.close() diff --git a/forum/management/commands/once_award_badges.py b/forum/management/commands/once_award_badges.py deleted file mode 100644 index 8c91334..0000000 --- a/forum/management/commands/once_award_badges.py +++ /dev/null @@ -1,350 +0,0 @@ -#!/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 datetime import datetime, date -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: - 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/management/commands/sample_command.py b/forum/management/commands/sample_command.py deleted file mode 100644 index 55e6723..0000000 --- a/forum/management/commands/sample_command.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.core.management.base import NoArgsCommand -from forum.models import Comment - -class Command(NoArgsCommand): - def handle_noargs(self, **options): - objs = Comment.objects.all() - print objs \ No newline at end of file diff --git a/forum/management/commands/send_email_alerts.py b/forum/management/commands/send_email_alerts.py deleted file mode 100644 index 26eb779..0000000 --- a/forum/management/commands/send_email_alerts.py +++ /dev/null @@ -1,192 +0,0 @@ -from django.core.management.base import NoArgsCommand -from django.db import connection -from django.db.models import Q, F -from forum.models import * -from forum import const -from django.core.mail import EmailMessage -from django.utils.translation import ugettext as _ -from django.utils.translation import ungettext -import datetime -from django.conf import settings -import logging -from forum.utils.odict import OrderedDict - -class Command(NoArgsCommand): - def handle_noargs(self,**options): - try: - try: - self.send_email_alerts() - except Exception, e: - print e - finally: - connection.close() - - def get_updated_questions_for_user(self,user): - q_sel = None - q_ask = None - q_ans = None - q_all = None - now = datetime.datetime.now() - Q_set1 = Question.objects.exclude( - last_activity_by=user, - ).exclude( - last_activity_at__lt=user.date_joined - ).filter( - Q(viewed__who=user,viewed__when__lt=F('last_activity_at')) | \ - ~Q(viewed__who=user) - ).exclude( - deleted=True - ).exclude( - closed=True - ) - - user_feeds = EmailFeedSetting.objects.filter(subscriber=user).exclude(frequency='n') - for feed in user_feeds: - cutoff_time = now - EmailFeedSetting.DELTA_TABLE[feed.frequency] - if feed.reported_at == None or feed.reported_at <= cutoff_time: - Q_set = Q_set1.exclude(last_activity_at__gt=cutoff_time)#report these excluded later - feed.reported_at = now - feed.save()#may not actually report anything, depending on filters below - if feed.feed_type == 'q_sel': - q_sel = Q_set.filter(followed_by=user) - q_sel.cutoff_time = cutoff_time #store cutoff time per query set - elif feed.feed_type == 'q_ask': - q_ask = Q_set.filter(author=user) - q_ask.cutoff_time = cutoff_time - elif feed.feed_type == 'q_ans': - q_ans = Q_set.filter(answers__author=user) - q_ans.cutoff_time = cutoff_time - elif feed.feed_type == 'q_all': - if user.tag_filter_setting == 'ignored': - ignored_tags = Tag.objects.filter(user_selections__reason='bad',user_selections__user=user) - q_all = Q_set.exclude( tags__in=ignored_tags ) - else: - selected_tags = Tag.objects.filter(user_selections__reason='good',user_selections__user=user) - q_all = Q_set.filter( tags__in=selected_tags ) - q_all.cutoff_time = cutoff_time - #build list in this order - q_list = OrderedDict() - def extend_question_list(src, dst): - """src is a query set with questions - or an empty list - dst - is an ordered dictionary - """ - if src is None: - return #will not do anything if subscription of this type is not used - cutoff_time = src.cutoff_time - for q in src: - if q in dst: - if cutoff_time < dst[q]['cutoff_time']: - dst[q]['cutoff_time'] = cutoff_time - else: - #initialise a questions metadata dictionary to use for email reporting - dst[q] = {'cutoff_time':cutoff_time} - - extend_question_list(q_sel, q_list) - extend_question_list(q_ask, q_list) - extend_question_list(q_ans, q_list) - extend_question_list(q_all, q_list) - - ctype = ContentType.objects.get_for_model(Question) - EMAIL_UPDATE_ACTIVITY = const.TYPE_ACTIVITY_QUESTION_EMAIL_UPDATE_SENT - for q, meta_data in q_list.items(): - #todo use Activity, but first start keeping more Activity records - #act = Activity.objects.filter(content_type=ctype, object_id=q.id) - #because currently activity is not fully recorded to through - #revision records to see what kind modifications were done on - #the questions and answers - try: - update_info = Activity.objects.get(content_type=ctype, - object_id=q.id, - activity_type=EMAIL_UPDATE_ACTIVITY) - emailed_at = update_info.active_at - except Activity.DoesNotExist: - update_info = Activity(user=user, content_object=q, activity_type=EMAIL_UPDATE_ACTIVITY) - emailed_at = datetime.datetime(1970,1,1)#long time ago - except Activity.MultipleObjectsReturned: - raise Exception('server error - multiple question email activities found per user-question pair') - - q_rev = QuestionRevision.objects.filter(question=q,\ - revised_at__lt=cutoff_time,\ - revised_at__gt=emailed_at) - q_rev = q_rev.exclude(author=user) - meta_data['q_rev'] = len(q_rev) - if len(q_rev) > 0 and q.added_at == q_rev[0].revised_at: - meta_data['q_rev'] = 0 - meta_data['new_q'] = True - else: - meta_data['new_q'] = False - - new_ans = Answer.objects.filter(question=q,\ - added_at__lt=cutoff_time,\ - added_at__gt=emailed_at) - new_ans = new_ans.exclude(author=user) - meta_data['new_ans'] = len(new_ans) - ans_rev = AnswerRevision.objects.filter(answer__question=q,\ - revised_at__lt=cutoff_time,\ - revised_at__gt=emailed_at) - ans_rev = ans_rev.exclude(author=user) - meta_data['ans_rev'] = len(ans_rev) - if len(q_rev) == 0 and len(new_ans) == 0 and len(ans_rev) == 0: - meta_data['nothing_new'] = True - else: - meta_data['nothing_new'] = False - update_info.active_at = now - update_info.save() #save question email update activity - return q_list - - def __action_count(self,string,number,output): - if number > 0: - output.append(_(string) % {'num':number}) - - def send_email_alerts(self): - - #todo: move this to template - for user in User.objects.all(): - q_list = self.get_updated_questions_for_user(user) - num_q = 0 - num_moot = 0 - for meta_data in q_list.values(): - if meta_data['nothing_new'] == False: - num_q += 1 - else: - num_moot += 1 - if num_q > 0: - url_prefix = settings.APP_URL - subject = _('email update message subject') - print 'have %d updated questions for %s' % (num_q, user.username) - text = ungettext('%(name)s, this is an update message header for a question', - '%(name)s, this is an update message header for %(num)d questions',num_q) \ - % {'num':num_q, 'name':user.username} - - text += '' - if num_moot > 0: - text += '

' - text += ungettext('There is also one question which was recently '\ - +'updated but you might not have seen its latest version.', - 'There are also %(num)d more questions which were recently updated '\ - +'but you might not have seen their latest version.',num_moot) \ - % {'num':num_moot,} - text += _('Perhaps you could look up previously sent forum reminders in your mailbox.') - text += '

' - - link = url_prefix + user.get_profile_url() + '?sort=email_subscriptions' - text += _('go to %(link)s to change frequency of email updates or %(email)s administrator') \ - % {'link':link, 'email':settings.ADMINS[0][1]} - msg = EmailMessage(subject, text, settings.DEFAULT_FROM_EMAIL, [user.email]) - msg.content_subtype = 'html' - msg.send() diff --git a/forum/management/commands/subscribe_everyone.py b/forum/management/commands/subscribe_everyone.py deleted file mode 100644 index c79528f..0000000 --- a/forum/management/commands/subscribe_everyone.py +++ /dev/null @@ -1,32 +0,0 @@ -from django.core.management.base import NoArgsCommand -from django.db import connection -from django.db.models import Q, F -from forum.models import * -from django.core.mail import EmailMessage -from django.utils.translation import ugettext as _ -from django.utils.translation import ungettext -import datetime -from django.conf import settings - -class Command(NoArgsCommand): - def handle_noargs(self,**options): - try: - try: - self.subscribe_everyone() - except Exception, e: - print e - finally: - connection.close() - - def subscribe_everyone(self): - - feed_type_info = EmailFeedSetting.FEED_TYPES - for user in User.objects.all(): - for feed_type in feed_type_info: - try: - feed_setting = EmailFeedSetting.objects.get(subscriber=user,feed_type = feed_type[0]) - except EmailFeedSetting.DoesNotExist: - feed_setting = EmailFeedSetting(subscriber=user,feed_type=feed_type[0]) - feed_setting.frequency = 'w' - feed_setting.reported_at = None - feed_setting.save() diff --git a/forum/middleware/__init__.py b/forum/middleware/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/forum/middleware/anon_user.py b/forum/middleware/anon_user.py deleted file mode 100644 index 866734d..0000000 --- a/forum/middleware/anon_user.py +++ /dev/null @@ -1,35 +0,0 @@ -from django.http import HttpResponseRedirect -from forum.utils.forms import get_next_url -from django.utils.translation import ugettext as _ -from forum.user_messages import create_message, get_and_delete_messages -from django.conf import settings -from django.core.urlresolvers import reverse -import logging - -class AnonymousMessageManager(object): - def __init__(self,request): - self.request = request - def create(self,message=''): - create_message(self.request,message) - def get_and_delete(self): - messages = get_and_delete_messages(self.request) - return messages - -def dummy_deepcopy(*arg): - """this is necessary to prevent deepcopy() on anonymous user object - that now contains reference to request, which cannot be deepcopied - """ - return None - -class ConnectToSessionMessagesMiddleware(object): - def process_request(self, request): - if not request.user.is_authenticated(): - request.user.__deepcopy__ = dummy_deepcopy #plug on deepcopy which may be called by django db "driver" - request.user.message_set = AnonymousMessageManager(request) #here request is linked to anon user - request.user.get_and_delete_messages = request.user.message_set.get_and_delete - - #also set the first greeting one time per session only - if 'greeting_set' not in request.session: - request.session['greeting_set'] = True - msg = _('First time here? Check out the FAQ!') % reverse('faq') - request.user.message_set.create(message=msg) diff --git a/forum/middleware/cancel.py b/forum/middleware/cancel.py deleted file mode 100644 index 15a4371..0000000 --- a/forum/middleware/cancel.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.http import HttpResponseRedirect -from forum.utils.forms import get_next_url -import logging -class CancelActionMiddleware(object): - def process_view(self, request, view_func, view_args, view_kwargs): - if 'cancel' in request.REQUEST: - #todo use session messages for the anonymous users - try: - msg = getattr(view_func,'CANCEL_MESSAGE') - except AttributeError: - msg = 'action canceled' - request.user.message_set.create(message=msg) - return HttpResponseRedirect(get_next_url(request)) - else: - return None diff --git a/forum/middleware/pagesize.py b/forum/middleware/pagesize.py deleted file mode 100644 index f6e6fcf..0000000 --- a/forum/middleware/pagesize.py +++ /dev/null @@ -1,33 +0,0 @@ -# used in questions -QUESTIONS_PAGE_SIZE = 10 -class QuestionsPageSizeMiddleware(object): - def process_request(self, request): - # Set flag to False by default. If it is equal to True, then need to be saved. - pagesize_changed = False - # get pagesize from session, if failed then get default value - user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE) - # set pagesize equal to logon user specified value in database - if request.user.is_authenticated() and request.user.questions_per_page > 0: - user_page_size = request.user.questions_per_page - - try: - # get new pagesize from UI selection - pagesize = int(request.GET.get('pagesize', user_page_size)) - if pagesize <> user_page_size: - pagesize_changed = True - - except ValueError: - pagesize = user_page_size - - # save this pagesize to user database - if pagesize_changed: - if request.user.is_authenticated(): - user = request.user - user.questions_per_page = pagesize - user.save() - # put pagesize into session - request.session["pagesize"] = pagesize - - def process_exception(self,request,exception): - import logging - logging.debug('have exception %s' % str(exception)) diff --git a/forum/models/__init__.py b/forum/models/__init__.py deleted file mode 100755 index 12a0239..0000000 --- a/forum/models/__init__.py +++ /dev/null @@ -1,343 +0,0 @@ -from question import Question ,QuestionRevision, QuestionView, AnonymousQuestion, FavoriteQuestion -from answer import Answer, AnonymousAnswer, AnswerRevision -from tag import Tag, MarkedTag -from meta import Vote, Comment, FlaggedItem -from user import Activity, AnonymousEmail, EmailFeedSetting, AuthKeyUserAssociation -from repute import Badge, Award, Repute - -from base import * - -# User extend properties -QUESTIONS_PER_PAGE_CHOICES = ( - (10, u'10'), - (30, u'30'), - (50, u'50'), -) - -def user_is_username_taken(cls,username): - try: - cls.objects.get(username=username) - return True - except cls.MultipleObjectsReturned: - return True - except cls.DoesNotExist: - return False - -def user_get_q_sel_email_feed_frequency(self): - #print 'looking for frequency for user %s' % self - try: - feed_setting = EmailFeedSetting.objects.get(subscriber=self,feed_type='q_sel') - except Exception, e: - #print 'have error %s' % e.message - raise e - #print 'have freq=%s' % feed_setting.frequency - return feed_setting.frequency - -User.add_to_class('is_approved', models.BooleanField(default=False)) -User.add_to_class('email_isvalid', models.BooleanField(default=False)) -User.add_to_class('email_key', models.CharField(max_length=32, null=True)) -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)) -User.add_to_class('is_username_taken',classmethod(user_is_username_taken)) -User.add_to_class('get_q_sel_email_feed_frequency',user_get_q_sel_email_feed_frequency) -User.add_to_class('hide_ignored_questions', models.BooleanField(default=False)) -User.add_to_class('tag_filter_setting', - models.CharField( - max_length=16, - choices=TAG_EMAIL_FILTER_CHOICES, - default='ignored' - ) - ) - -# 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"]) -user_logged_in = django.dispatch.Signal(providing_args=["session"]) - - -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]), slugify(self.username)) - -def get_profile_link(self): - profile_link = u'%s' % (self.get_profile_url(),self.username) - logging.debug('in get profile link %s' % profile_link) - return mark_safe(profile_link) - -User.add_to_class('get_profile_url', get_profile_url) -User.add_to_class('get_profile_link', get_profile_link) -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 - if (instance.content_type_id == question_type_id): - type = TYPE_ACTIVITY_COMMENT_QUESTION - else: - type = 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"Congratulations, you have received a badge '%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() - -def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs): - aq_list = AnonymousQuestion.objects.filter(session_key = session_key) - aa_list = AnonymousAnswer.objects.filter(session_key = session_key) - import settings - if settings.EMAIL_VALIDATION == 'on':#add user to the record - for aq in aq_list: - aq.author = user - aq.save() - for aa in aa_list: - aa.author = user - aa.save() - #maybe add pending posts message? - else: #just publish the questions - for aq in aq_list: - aq.publish(user) - for aa in aa_list: - aa.publish(user) - -#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) -user_logged_in.connect(post_stored_anonymous_content) - -Question = Question -QuestionRevision = QuestionRevision -QuestionView = QuestionView -FavoriteQuestion = FavoriteQuestion -AnonymousQuestion = AnonymousQuestion - -Answer = Answer -AnswerRevision = AnswerRevision -AnonymousAnswer = AnonymousAnswer - -Tag = Tag -Comment = Comment -Vote = Vote -FlaggedItem = FlaggedItem -MarkedTag = MarkedTag - -Badge = Badge -Award = Award -Repute = Repute - -Activity = Activity -EmailFeedSetting = EmailFeedSetting -AnonymousEmail = AnonymousEmail -AuthKeyUserAssociation = AuthKeyUserAssociation - -__all__ = [ - 'Question', - 'QuestionRevision', - 'QuestionView', - 'FavoriteQuestion', - 'AnonymousQuestion', - - 'Answer', - 'AnswerRevision', - 'AnonymousAnswer', - - 'Tag', - 'Comment', - 'Vote', - 'FlaggedItem', - 'MarkedTag', - - 'Badge', - 'Award', - 'Repute', - - 'Activity', - 'EmailFeedSetting', - 'AnonymousEmail', - 'AuthKeyUserAssociation', - - 'User' - ] - - -from forum.modules import get_modules_script_classes - -for k, v in get_modules_script_classes('models', models.Model).items(): - if not k in __all__: - __all__.append(k) - exec "%s = v" % k \ No newline at end of file diff --git a/forum/models/answer.py b/forum/models/answer.py deleted file mode 100755 index 14199de..0000000 --- a/forum/models/answer.py +++ /dev/null @@ -1,134 +0,0 @@ -from base import * - -from question import Question - -class AnswerManager(models.Manager): - @staticmethod - def create_new(cls, question=None, author=None, added_at=None, wiki=False, text='', email_notify=False): - answer = Answer( - question = question, - author = author, - added_at = added_at, - wiki = wiki, - html = text - ) - if answer.wiki: - answer.last_edited_by = answer.author - answer.last_edited_at = added_at - answer.wikified_at = added_at - - answer.save() - - #update question data - question.last_activity_at = added_at - question.last_activity_by = author - question.save() - Question.objects.update_answer_count(question) - - AnswerRevision.objects.create( - answer = answer, - revision = 1, - author = author, - revised_at = added_at, - summary = CONST['default_version'], - text = text - ) - - #set notification/delete - if email_notify: - if author not in question.followed_by.all(): - question.followed_by.add(author) - else: - #not sure if this is necessary. ajax should take care of this... - try: - question.followed_by.remove(author) - except: - pass - - #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(models.Q(question=question), - models.Q(deleted=False) | models.Q(deleted_by=user)) - - #todo: I think this method is not being used anymore, I'll just comment it for now - #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 Answer(Content, DeletableContent): - question = models.ForeignKey('Question', related_name='answers') - accepted = models.BooleanField(default=False) - accepted_at = models.DateTimeField(null=True, blank=True) - - objects = AnswerManager() - - class Meta(Content.Meta): - db_table = u'answer' - - def get_user_vote(self, user): - if user.__class__.__name__ == "AnonymousUser": - return None - - votes = self.votes.filter(user=user) - if votes and 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]), django_urlquote(slugify(self.question.title)), self.id) - - def __unicode__(self): - return self.html - - -class AnswerRevision(ContentRevision): - """A revision of an Answer.""" - answer = models.ForeignKey('Answer', related_name='revisions') - - def get_absolute_url(self): - return reverse('answer_revisions', kwargs={'id':self.answer.id}) - - def get_question_title(self): - return self.answer.question.title - - class Meta(ContentRevision.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 AnonymousAnswer(AnonymousContent): - question = models.ForeignKey('Question', related_name='anonymous_answers') - - def publish(self,user): - added_at = datetime.datetime.now() - #print user.id - AnswerManager.create_new(question=self.question,wiki=self.wiki, - added_at=added_at,text=self.text, - author=user) - self.delete() diff --git a/forum/models/base.py b/forum/models/base.py deleted file mode 100755 index 2c28a47..0000000 --- a/forum/models/base.py +++ /dev/null @@ -1,139 +0,0 @@ -import datetime -import hashlib -from urllib import quote_plus, urlencode -from django.db import models, IntegrityError, connection, transaction -from django.utils.http import urlquote as django_urlquote -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 _ -from django.utils.safestring import mark_safe -from django.contrib.sitemaps import ping_google -import django.dispatch -from django.conf import settings -import logging - -if settings.USE_SPHINX_SEARCH == True: - from djangosphinx.models import SphinxSearch - -from forum.const import * - -class MetaContent(models.Model): - """ - Base class for Vote, Comment and FlaggedItem - """ - content_type = models.ForeignKey(ContentType) - object_id = models.PositiveIntegerField() - content_object = generic.GenericForeignKey('content_type', 'object_id') - user = models.ForeignKey(User, related_name='%(class)ss') - - class Meta: - abstract = True - app_label = 'forum' - - -class DeletableContent(models.Model): - 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_%(class)ss') - - class Meta: - abstract = True - app_label = 'forum' - - -class ContentRevision(models.Model): - """ - Base class for QuestionRevision and AnswerRevision - """ - revision = models.PositiveIntegerField() - author = models.ForeignKey(User, related_name='%(class)ss') - revised_at = models.DateTimeField() - summary = models.CharField(max_length=300, blank=True) - text = models.TextField() - - class Meta: - abstract = True - app_label = 'forum' - - -class AnonymousContent(models.Model): - """ - Base class for AnonymousQuestion and AnonymousAnswer - """ - session_key = models.CharField(max_length=40) #session id for anonymous questions - wiki = models.BooleanField(default=False) - added_at = models.DateTimeField(default=datetime.datetime.now) - ip_addr = models.IPAddressField(max_length=21) #allow high port numbers - author = models.ForeignKey(User,null=True) - text = models.TextField() - summary = models.CharField(max_length=180) - - class Meta: - abstract = True - app_label = 'forum' - - -from meta import Comment, Vote, FlaggedItem - -class Content(models.Model): - """ - Base class for Question and Answer - """ - author = models.ForeignKey(User, related_name='%(class)ss') - added_at = models.DateTimeField(default=datetime.datetime.now) - - wiki = models.BooleanField(default=False) - wikified_at = models.DateTimeField(null=True, blank=True) - - locked = models.BooleanField(default=False) - locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_%(class)ss') - locked_at = models.DateTimeField(null=True, blank=True) - - 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_%(class)ss') - - html = models.TextField() - comments = generic.GenericRelation(Comment) - votes = generic.GenericRelation(Vote) - flagged_items = generic.GenericRelation(FlaggedItem) - - class Meta: - abstract = True - app_label = 'forum' - - def save(self,**kwargs): - super(Content,self).save(**kwargs) - try: - ping_google() - except Exception: - logging.debug('problem pinging google did you register you sitemap with google?') - - def get_object_comments(self): - comments = self.comments.all().order_by('id') - return comments - - def post_get_last_update_info(self): - when = self.added_at - who = self.author - if self.last_edited_at and self.last_edited_at > when: - when = self.last_edited_at - who = self.last_edited_by - comments = self.comments.all() - if len(comments) > 0: - for c in comments: - if c.added_at > when: - when = c.added_at - who = c.user - return when, who \ No newline at end of file diff --git a/forum/models/meta.py b/forum/models/meta.py deleted file mode 100755 index 3dfd3e8..0000000 --- a/forum/models/meta.py +++ /dev/null @@ -1,89 +0,0 @@ -from base import * - -class VoteManager(models.Manager): - def get_up_vote_count_from_user(self, user): - if user is not None: - return self.filter(user=user, vote=1).count() - else: - return 0 - - def get_down_vote_count_from_user(self, user): - if user is not None: - return self.filter(user=user, vote=-1).count() - else: - return 0 - - def get_votes_count_today_from_user(self, user): - if user is not None: - today = datetime.date.today() - return self.filter(user=user, voted_at__range=(today, today + datetime.timedelta(1))).count() - - else: - return 0 - - -class Vote(MetaContent): - VOTE_UP = +1 - VOTE_DOWN = -1 - VOTE_CHOICES = ( - (VOTE_UP, u'Up'), - (VOTE_DOWN, u'Down'), - ) - - vote = models.SmallIntegerField(choices=VOTE_CHOICES) - voted_at = models.DateTimeField(default=datetime.datetime.now) - - objects = VoteManager() - - class Meta(MetaContent.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 FlaggedItemManager(models.Manager): - def get_flagged_items_count_today(self, user): - if user is not None: - today = datetime.date.today() - return self.filter(user=user, flagged_at__range=(today, today + datetime.timedelta(1))).count() - else: - return 0 - -class FlaggedItem(MetaContent): - """A flag on a Question or Answer indicating offensive content.""" - flagged_at = models.DateTimeField(default=datetime.datetime.now) - - objects = FlaggedItemManager() - - class Meta(MetaContent.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 Comment(MetaContent): - comment = models.CharField(max_length=300) - added_at = models.DateTimeField(default=datetime.datetime.now) - - class Meta(MetaContent.Meta): - ordering = ('-added_at',) - db_table = u'comment' - - def save(self,**kwargs): - super(Comment,self).save(**kwargs) - try: - ping_google() - except Exception: - logging.debug('problem pinging google did you register you sitemap with google?') - - def __unicode__(self): - return self.comment \ No newline at end of file diff --git a/forum/models/question.py b/forum/models/question.py deleted file mode 100755 index f916e65..0000000 --- a/forum/models/question.py +++ /dev/null @@ -1,336 +0,0 @@ -from base import * -from tag import Tag - -class QuestionManager(models.Manager): - @staticmethod - def create_new(cls, title=None,author=None,added_at=None, wiki=False,tagnames=None,summary=None, text=None): - question = Question( - title = title, - author = author, - added_at = added_at, - last_activity_at = added_at, - last_activity_by = author, - wiki = wiki, - tagnames = tagnames, - html = text, - summary = summary - ) - if question.wiki: - question.last_edited_by = question.author - question.last_edited_at = added_at - question.wikified_at = added_at - - question.save() - - # create the first revision - QuestionRevision.objects.create( - question = question, - revision = 1, - title = question.title, - author = author, - revised_at = added_at, - tagnames = question.tagnames, - summary = CONST['default_version'], - text = text - ) - return question - - 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. - """ - - 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 answer import Answer - self.filter(id=question.id).update( - answer_count=Answer.objects.get_answers_from_question(question).filter(deleted=False).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 - """ - 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() - questions = list(self.filter(tagnames = question.tagnames, deleted=False).all()) - - tags_list = question.tags.all() - for tag in tags_list: - extend_questions = self.filter(tags__id = tag.id, deleted=False)[: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 Question(Content, DeletableContent): - title = models.CharField(max_length=300) - tags = models.ManyToManyField('Tag', related_name='questions') - 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) - followed_by = models.ManyToManyField(User, related_name='followed_questions') - - # Denormalised data - answer_count = models.PositiveIntegerField(default=0) - view_count = models.PositiveIntegerField(default=0) - favourite_count = models.PositiveIntegerField(default=0) - 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) - - favorited_by = models.ManyToManyField(User, through='FavoriteQuestion', related_name='favorite_questions') - - objects = QuestionManager() - - class Meta(Content.Meta): - db_table = u'question' - - def delete(self): - super(Question, self).delete() - try: - ping_google() - except Exception: - logging.debug('problem pinging google did you register you sitemap with google?') - - 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 tagname_meta_generator(self): - return u','.join([unicode(tag) for tag in self.tagname_list()]) - - def get_absolute_url(self): - return '%s%s' % (reverse('question', args=[self.id]), django_urlquote(slugify(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): - from answer import Answer - 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 - if attr is not None: - return u'%s %s' % (self.title, attr) - else: - return 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 get_last_update_info(self): - when, who = self.post_get_last_update_info() - - answers = self.answers.all() - if len(answers) > 0: - for a in answers: - a_when, a_who = a.post_get_last_update_info() - if a_when > when: - when = a_when - who = a_who - - return when, who - - def get_update_summary(self,last_reported_at=None,recipient_email=''): - edited = False - if self.last_edited_at and self.last_edited_at > last_reported_at: - if self.last_edited_by.email != recipient_email: - edited = True - comments = [] - for comment in self.comments.all(): - if comment.added_at > last_reported_at and comment.user.email != recipient_email: - comments.append(comment) - new_answers = [] - answer_comments = [] - modified_answers = [] - commented_answers = [] - import sets - commented_answers = sets.Set([]) - for answer in self.answers.all(): - if (answer.added_at > last_reported_at and answer.author.email != recipient_email): - new_answers.append(answer) - if (answer.last_edited_at - and answer.last_edited_at > last_reported_at - and answer.last_edited_by.email != recipient_email): - modified_answers.append(answer) - for comment in answer.comments.all(): - if comment.added_at > last_reported_at and comment.user.email != recipient_email: - commented_answers.add(answer) - answer_comments.append(comment) - - #create the report - if edited or new_answers or modified_answers or answer_comments: - out = [] - if edited: - out.append(_('%(author)s modified the question') % {'author':self.last_edited_by.username}) - if new_answers: - names = sets.Set(map(lambda x: x.author.username,new_answers)) - people = ', '.join(names) - out.append(_('%(people)s posted %(new_answer_count)s new answers') \ - % {'new_answer_count':len(new_answers),'people':people}) - if comments: - names = sets.Set(map(lambda x: x.user.username,comments)) - people = ', '.join(names) - out.append(_('%(people)s commented the question') % {'people':people}) - if answer_comments: - names = sets.Set(map(lambda x: x.user.username,answer_comments)) - people = ', '.join(names) - if len(commented_answers) > 1: - out.append(_('%(people)s commented answers') % {'people':people}) - else: - out.append(_('%(people)s commented an answer') % {'people':people}) - url = settings.APP_URL + self.get_absolute_url() - retval = '%s:
\n' % (url,self.title) - out = map(lambda x: '
  • ' + x + '
  • ',out) - retval += '
    \n' - return retval - else: - return None - - def __unicode__(self): - return self.title - - -class QuestionView(models.Model): - question = models.ForeignKey(Question, related_name='viewed') - who = models.ForeignKey(User, related_name='question_views') - when = models.DateTimeField() - - class Meta: - app_label = 'forum' - -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: - app_label = 'forum' - db_table = u'favorite_question' - def __unicode__(self): - return '[%s] favorited at %s' %(self.user, self.added_at) - -class QuestionRevision(ContentRevision): - """A revision of a Question.""" - question = models.ForeignKey(Question, related_name='revisions') - title = models.CharField(max_length=300) - tagnames = models.CharField(max_length=125) - - class Meta(ContentRevision.Meta): - db_table = u'question_revision' - ordering = ('-revision',) - - def get_question_title(self): - return self.question.title - - def get_absolute_url(self): - #print 'in QuestionRevision.get_absolute_url()' - return reverse('question_revisions', args=[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 AnonymousQuestion(AnonymousContent): - title = models.CharField(max_length=300) - tagnames = models.CharField(max_length=125) - - def publish(self,user): - added_at = datetime.datetime.now() - QuestionManager.create_new(title=self.title, author=user, added_at=added_at, - wiki=self.wiki, tagnames=self.tagnames, - summary=self.summary, text=self.text) - self.delete() - -from answer import Answer, AnswerManager diff --git a/forum/models/repute.py b/forum/models/repute.py deleted file mode 100755 index a47ce47..0000000 --- a/forum/models/repute.py +++ /dev/null @@ -1,109 +0,0 @@ -from base import * - -from django.utils.translation import ugettext as _ - -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) - - awarded_to = models.ManyToManyField(User, through='Award', related_name='badges') - - class Meta: - app_label = 'forum' - 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 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 - -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: - app_label = 'forum' - db_table = u'award' - -class ReputeManager(models.Manager): - 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: - today = datetime.date.today() - sums = self.filter(models.Q(reputation_type=1) | models.Q(reputation_type=-8), - user=user, reputed_at__range=(today, today + datetime.timedelta(1))). \ - agregate(models.Sum('positive'), models.Sum('negative')) - - return sums['positive__sum'] + sums['negative__sum'] - else: - return 0 - -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: - app_label = 'forum' - db_table = u'repute' diff --git a/forum/models/tag.py b/forum/models/tag.py deleted file mode 100755 index 28b9e57..0000000 --- a/forum/models/tag.py +++ /dev/null @@ -1,85 +0,0 @@ -from base import * - -from django.utils.translation import ugettext as _ - -class TagManager(models.Manager): - UPDATE_USED_COUNTS_QUERY = ( - 'UPDATE tag ' - 'SET used_count = (' - 'SELECT COUNT(*) FROM question_tags ' - 'INNER JOIN question ON question_id=question.id ' - 'WHERE tag_id = tag.id AND question.deleted=False' - ') ' - 'WHERE id IN (%s)') - - def get_valid_tags(self, page_size): - tags = self.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 Tag(DeletableContent): - name = models.CharField(max_length=255, unique=True) - created_by = models.ForeignKey(User, related_name='created_tags') - # Denormalised data - used_count = models.PositiveIntegerField(default=0) - - objects = TagManager() - - class Meta(DeletableContent.Meta): - db_table = u'tag' - ordering = ('-used_count', 'name') - - def __unicode__(self): - return self.name - -class MarkedTag(models.Model): - TAG_MARK_REASONS = (('good',_('interesting')),('bad',_('ignored'))) - tag = models.ForeignKey('Tag', related_name='user_selections') - user = models.ForeignKey(User, related_name='tag_selections') - reason = models.CharField(max_length=16, choices=TAG_MARK_REASONS) - - class Meta: - app_label = 'forum' \ No newline at end of file diff --git a/forum/models/user.py b/forum/models/user.py deleted file mode 100755 index 9502416..0000000 --- a/forum/models/user.py +++ /dev/null @@ -1,77 +0,0 @@ -from base import * - -from django.utils.translation import ugettext as _ - -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: - app_label = 'forum' - db_table = u'activity' - -class EmailFeedSetting(models.Model): - DELTA_TABLE = { - 'w':datetime.timedelta(7), - 'd':datetime.timedelta(1), - 'n':datetime.timedelta(-1), - } - FEED_TYPES = ( - ('q_all',_('Entire forum')), - ('q_ask',_('Questions that I asked')), - ('q_ans',_('Questions that I answered')), - ('q_sel',_('Individually selected questions')), - ) - UPDATE_FREQUENCY = ( - ('w',_('Weekly')), - ('d',_('Daily')), - ('n',_('No email')), - ) - subscriber = models.ForeignKey(User) - feed_type = models.CharField(max_length=16,choices=FEED_TYPES) - frequency = models.CharField(max_length=8,choices=UPDATE_FREQUENCY,default='n') - added_at = models.DateTimeField(auto_now_add=True) - reported_at = models.DateTimeField(null=True) - - def save(self,*args,**kwargs): - type = self.feed_type - subscriber = self.subscriber - similar = self.__class__.objects.filter(feed_type=type,subscriber=subscriber).exclude(pk=self.id) - if len(similar) > 0: - raise IntegrityError('email feed setting already exists') - super(EmailFeedSetting,self).save(*args,**kwargs) - - class Meta: - app_label = 'forum' - -class AnonymousEmail(models.Model): - #validation key, if used - key = models.CharField(max_length=32) - email = models.EmailField(null=False,unique=True) - isvalid = models.BooleanField(default=False) - - class Meta: - app_label = 'forum' - - -class AuthKeyUserAssociation(models.Model): - key = models.CharField(max_length=256,null=False,unique=True) - provider = models.CharField(max_length=64) - user = models.ForeignKey(User) - added_at = models.DateTimeField(default=datetime.datetime.now) - - class Meta: - app_label = 'forum' - - \ No newline at end of file diff --git a/forum/modules.py b/forum/modules.py deleted file mode 100755 index 9c07233..0000000 --- a/forum/modules.py +++ /dev/null @@ -1,79 +0,0 @@ -import os -import types -import re - -from django.template import Template, TemplateDoesNotExist - -MODULES_PACKAGE = 'forum_modules' - -MODULES_FOLDER = os.path.join(os.path.dirname(__file__), '../' + MODULES_PACKAGE) - -MODULE_LIST = [ - __import__('forum_modules.%s' % f, globals(), locals(), ['forum_modules']) - for f in os.listdir(MODULES_FOLDER) - if os.path.isdir(os.path.join(MODULES_FOLDER, f)) and - os.path.exists(os.path.join(MODULES_FOLDER, "%s/__init__.py" % f)) and - not os.path.exists(os.path.join(MODULES_FOLDER, "%s/DISABLED" % f)) -] - -def get_modules_script(script_name): - all = [] - - for m in MODULE_LIST: - try: - all.append(__import__('%s.%s' % (m.__name__, script_name), globals(), locals(), [m.__name__])) - except Exception, e: - #print script_name + ":" + str(e) - pass - - return all - -def get_modules_script_classes(script_name, base_class): - scripts = get_modules_script(script_name) - all_classes = {} - - for script in scripts: - all_classes.update(dict([ - (n, c) for (n, c) in [(n, getattr(script, n)) for n in dir(script)] - if isinstance(c, (type, types.ClassType)) and issubclass(c, base_class) - ])) - - return all_classes - -def get_all_handlers(name): - handler_files = get_modules_script('handlers') - - return [ - h for h in [ - getattr(f, name) for f in handler_files - if hasattr(f, name) - ] - - if callable(h) - ] - -def get_handler(name, default): - all = get_all_handlers(name) - print(len(all)) - return len(all) and all[0] or default - -module_template_re = re.compile('^modules\/(\w+)\/(.*)$') - -def module_templates_loader(name, dirs=None): - result = module_template_re.search(name) - - if result is not None: - file_name = os.path.join(MODULES_FOLDER, result.group(1), 'templates', result.group(2)) - - if os.path.exists(file_name): - try: - f = open(file_name, 'r') - source = f.read() - f.close() - return (source, file_name) - except: - pass - - raise TemplateDoesNotExist, name - -module_templates_loader.is_usable = True \ No newline at end of file diff --git a/forum/sitemap.py b/forum/sitemap.py deleted file mode 100644 index c0c60b5..0000000 --- a/forum/sitemap.py +++ /dev/null @@ -1,14 +0,0 @@ -from django.contrib.sitemaps import Sitemap -from forum.models import Question - -class QuestionsSitemap(Sitemap): - changefreq = 'daily' - priority = 0.5 - def items(self): - return Question.objects.exclude(deleted=True) - - def lastmod(self, obj): - return obj.last_activity_at - - def location(self, obj): - return obj.get_absolute_url() diff --git a/forum/skins/README b/forum/skins/README deleted file mode 100644 index 5565fa8..0000000 --- a/forum/skins/README +++ /dev/null @@ -1,22 +0,0 @@ -this directory contains available skins - -1) default - default skin with templates -2) common - this directory is to media directory common to all or many templates - -to create a new skin just create another directory under skins/ -and start populating it with the directory structure as in -default/templates - templates must be named the same way - -NO NEED TO CREATE ALL TEMPLATES/MEDIA FILES AT ONCE - -templates are resolved in the following way: -* check in skin named as in settings.OSQA_DEFAULT_SKIN -* then skin named 'default' - -media is resolved with one extra option -* settings.OSQA_DEFAULT_SKIN -* 'default' -* 'common' - -media does not have to be composed of files named the same way as in default skin -whatever media you link to from your templates - will be in operation diff --git a/forum/skins/__init__.py b/forum/skins/__init__.py deleted file mode 100644 index be6bd4f..0000000 --- a/forum/skins/__init__.py +++ /dev/null @@ -1,57 +0,0 @@ -from django.conf import settings -from django.template import loader -from django.template.loaders import filesystem -from django.http import HttpResponse -import os.path -import logging - -#module for skinning osqa -#at this point skin can be changed only in settings file -#via OSQA_DEFAULT_SKIN variable - -#note - Django template loaders use method django.utils._os.safe_join -#to work on unicode file paths -#here it is ignored because it is assumed that we won't use unicode paths - -def load_template_source(name, dirs=None): - try: - tname = os.path.join(settings.OSQA_DEFAULT_SKIN,'templates',name) - return filesystem.load_template_source(tname,dirs) - except: - tname = os.path.join('default','templates',name) - return filesystem.load_template_source(tname,dirs) -load_template_source.is_usable = True - -def find_media_source(url): - """returns url prefixed with the skin name - of the first skin that contains the file - directories are searched in this order: - settings.OSQA_DEFAULT_SKIN, then 'default', then 'commmon' - if file is not found - returns None - and logs an error message - """ - while url[0] == '/': url = url[1:] - d = os.path.dirname - n = os.path.normpath - j = os.path.join - f = os.path.isfile - skins = n(j(d(d(__file__)),'skins')) - try: - media = os.path.join(skins, settings.OSQA_DEFAULT_SKIN, url) - assert(f(media)) - use_skin = settings.OSQA_DEFAULT_SKIN - except: - try: - media = j(skins, 'default', url) - assert(f(media)) - use_skin = 'default' - except: - media = j(skins, 'common', url) - try: - assert(f(media)) - use_skin = 'common' - except: - logging.error('could not find media for %s' % url) - use_skin = '' - return None - return use_skin + '/' + url diff --git a/forum/skins/common/media/README b/forum/skins/common/media/README deleted file mode 100644 index 3376e75..0000000 --- a/forum/skins/common/media/README +++ /dev/null @@ -1 +0,0 @@ -directory for media common to all or many templates diff --git a/forum/skins/default/media/images/blue-up-arrow-h18px.png b/forum/skins/default/media/images/blue-up-arrow-h18px.png deleted file mode 100644 index e1f29e8..0000000 Binary files a/forum/skins/default/media/images/blue-up-arrow-h18px.png and /dev/null differ diff --git a/forum/skins/default/media/images/box-arrow.gif b/forum/skins/default/media/images/box-arrow.gif deleted file mode 100644 index 89dcf5b..0000000 Binary files a/forum/skins/default/media/images/box-arrow.gif and /dev/null differ diff --git a/forum/skins/default/media/images/bullet_green.gif b/forum/skins/default/media/images/bullet_green.gif deleted file mode 100644 index fa53091..0000000 Binary files a/forum/skins/default/media/images/bullet_green.gif and /dev/null differ diff --git a/forum/skins/default/media/images/cc-88x31.png b/forum/skins/default/media/images/cc-88x31.png deleted file mode 100644 index 0f2a0f1..0000000 Binary files a/forum/skins/default/media/images/cc-88x31.png and /dev/null differ diff --git a/forum/skins/default/media/images/cc-wiki.png b/forum/skins/default/media/images/cc-wiki.png deleted file mode 100644 index 3e68053..0000000 Binary files a/forum/skins/default/media/images/cc-wiki.png and /dev/null differ diff --git a/forum/skins/default/media/images/close-small-dark.png b/forum/skins/default/media/images/close-small-dark.png deleted file mode 100644 index 280c1fc..0000000 Binary files a/forum/skins/default/media/images/close-small-dark.png and /dev/null differ diff --git a/forum/skins/default/media/images/close-small-hover.png b/forum/skins/default/media/images/close-small-hover.png deleted file mode 100644 index 7899aec..0000000 Binary files a/forum/skins/default/media/images/close-small-hover.png and /dev/null differ diff --git a/forum/skins/default/media/images/close-small.png b/forum/skins/default/media/images/close-small.png deleted file mode 100644 index 5a99d31..0000000 Binary files a/forum/skins/default/media/images/close-small.png and /dev/null differ diff --git a/forum/skins/default/media/images/dash.gif b/forum/skins/default/media/images/dash.gif deleted file mode 100644 index d1ddc50..0000000 Binary files a/forum/skins/default/media/images/dash.gif and /dev/null differ diff --git a/forum/skins/default/media/images/djangomade124x25_grey.gif b/forum/skins/default/media/images/djangomade124x25_grey.gif deleted file mode 100644 index d34bb31..0000000 Binary files a/forum/skins/default/media/images/djangomade124x25_grey.gif and /dev/null differ diff --git a/forum/skins/default/media/images/dot-g.gif b/forum/skins/default/media/images/dot-g.gif deleted file mode 100644 index 5d6bb28..0000000 Binary files a/forum/skins/default/media/images/dot-g.gif and /dev/null differ diff --git a/forum/skins/default/media/images/dot-list.gif b/forum/skins/default/media/images/dot-list.gif deleted file mode 100644 index f6a6b86..0000000 Binary files a/forum/skins/default/media/images/dot-list.gif and /dev/null differ diff --git a/forum/skins/default/media/images/edit.png b/forum/skins/default/media/images/edit.png deleted file mode 100644 index dcb09be..0000000 Binary files a/forum/skins/default/media/images/edit.png and /dev/null differ diff --git a/forum/skins/default/media/images/expander-arrow-hide.gif b/forum/skins/default/media/images/expander-arrow-hide.gif deleted file mode 100644 index feb6a61..0000000 Binary files a/forum/skins/default/media/images/expander-arrow-hide.gif and /dev/null differ diff --git a/forum/skins/default/media/images/expander-arrow-show.gif b/forum/skins/default/media/images/expander-arrow-show.gif deleted file mode 100644 index 6825c56..0000000 Binary files a/forum/skins/default/media/images/expander-arrow-show.gif and /dev/null differ diff --git a/forum/skins/default/media/images/favicon.gif b/forum/skins/default/media/images/favicon.gif deleted file mode 100644 index 910c266..0000000 Binary files a/forum/skins/default/media/images/favicon.gif and /dev/null differ diff --git a/forum/skins/default/media/images/feed-icon-small.png b/forum/skins/default/media/images/feed-icon-small.png deleted file mode 100644 index b3c949d..0000000 Binary files a/forum/skins/default/media/images/feed-icon-small.png and /dev/null differ diff --git a/forum/skins/default/media/images/gray-up-arrow-h18px.png b/forum/skins/default/media/images/gray-up-arrow-h18px.png deleted file mode 100644 index 7876744..0000000 Binary files a/forum/skins/default/media/images/gray-up-arrow-h18px.png and /dev/null differ diff --git a/forum/skins/default/media/images/grippie.png b/forum/skins/default/media/images/grippie.png deleted file mode 100644 index 6524d41..0000000 Binary files a/forum/skins/default/media/images/grippie.png and /dev/null differ diff --git a/forum/skins/default/media/images/indicator.gif b/forum/skins/default/media/images/indicator.gif deleted file mode 100644 index 1c72ebb..0000000 Binary files a/forum/skins/default/media/images/indicator.gif and /dev/null differ diff --git a/forum/skins/default/media/images/logo.gif b/forum/skins/default/media/images/logo.gif deleted file mode 100644 index ab690de..0000000 Binary files a/forum/skins/default/media/images/logo.gif and /dev/null differ diff --git a/forum/skins/default/media/images/logo.png b/forum/skins/default/media/images/logo.png deleted file mode 100644 index 6a250e3..0000000 Binary files a/forum/skins/default/media/images/logo.png and /dev/null differ diff --git a/forum/skins/default/media/images/logo1.png b/forum/skins/default/media/images/logo1.png deleted file mode 100644 index d79a627..0000000 Binary files a/forum/skins/default/media/images/logo1.png and /dev/null differ diff --git a/forum/skins/default/media/images/logo2.png b/forum/skins/default/media/images/logo2.png deleted file mode 100644 index bd3cccd..0000000 Binary files a/forum/skins/default/media/images/logo2.png and /dev/null differ diff --git a/forum/skins/default/media/images/medala.gif b/forum/skins/default/media/images/medala.gif deleted file mode 100644 index 93dd1a3..0000000 Binary files a/forum/skins/default/media/images/medala.gif and /dev/null differ diff --git a/forum/skins/default/media/images/medala_on.gif b/forum/skins/default/media/images/medala_on.gif deleted file mode 100644 index a18f9e8..0000000 Binary files a/forum/skins/default/media/images/medala_on.gif and /dev/null differ diff --git a/forum/skins/default/media/images/new.gif b/forum/skins/default/media/images/new.gif deleted file mode 100644 index 8a220b5..0000000 Binary files a/forum/skins/default/media/images/new.gif and /dev/null differ diff --git a/forum/skins/default/media/images/nophoto.png b/forum/skins/default/media/images/nophoto.png deleted file mode 100644 index 2daf0ff..0000000 Binary files a/forum/skins/default/media/images/nophoto.png and /dev/null differ diff --git a/forum/skins/default/media/images/openid.gif b/forum/skins/default/media/images/openid.gif deleted file mode 100644 index 8540e12..0000000 Binary files a/forum/skins/default/media/images/openid.gif and /dev/null differ diff --git a/forum/skins/default/media/images/openid/aol.gif b/forum/skins/default/media/images/openid/aol.gif deleted file mode 100644 index decc4f1..0000000 Binary files a/forum/skins/default/media/images/openid/aol.gif and /dev/null differ diff --git a/forum/skins/default/media/images/openid/blogger.ico b/forum/skins/default/media/images/openid/blogger.ico deleted file mode 100644 index 1b9730b..0000000 Binary files a/forum/skins/default/media/images/openid/blogger.ico and /dev/null differ diff --git a/forum/skins/default/media/images/openid/claimid.ico b/forum/skins/default/media/images/openid/claimid.ico deleted file mode 100644 index 2b80f49..0000000 Binary files a/forum/skins/default/media/images/openid/claimid.ico and /dev/null differ diff --git a/forum/skins/default/media/images/openid/facebook.gif b/forum/skins/default/media/images/openid/facebook.gif deleted file mode 100644 index b997b35..0000000 Binary files a/forum/skins/default/media/images/openid/facebook.gif and /dev/null differ diff --git a/forum/skins/default/media/images/openid/flickr.ico b/forum/skins/default/media/images/openid/flickr.ico deleted file mode 100644 index 11f6e07..0000000 Binary files a/forum/skins/default/media/images/openid/flickr.ico and /dev/null differ diff --git a/forum/skins/default/media/images/openid/google.gif b/forum/skins/default/media/images/openid/google.gif deleted file mode 100644 index 1b6cd07..0000000 Binary files a/forum/skins/default/media/images/openid/google.gif and /dev/null differ diff --git a/forum/skins/default/media/images/openid/livejournal.ico b/forum/skins/default/media/images/openid/livejournal.ico deleted file mode 100644 index f3d21ec..0000000 Binary files a/forum/skins/default/media/images/openid/livejournal.ico and /dev/null differ diff --git a/forum/skins/default/media/images/openid/myopenid.ico b/forum/skins/default/media/images/openid/myopenid.ico deleted file mode 100644 index ceb06e6..0000000 Binary files a/forum/skins/default/media/images/openid/myopenid.ico and /dev/null differ diff --git a/forum/skins/default/media/images/openid/openid-inputicon.gif b/forum/skins/default/media/images/openid/openid-inputicon.gif deleted file mode 100644 index cde836c..0000000 Binary files a/forum/skins/default/media/images/openid/openid-inputicon.gif and /dev/null differ diff --git a/forum/skins/default/media/images/openid/openid.gif b/forum/skins/default/media/images/openid/openid.gif deleted file mode 100644 index c718b0e..0000000 Binary files a/forum/skins/default/media/images/openid/openid.gif and /dev/null differ diff --git a/forum/skins/default/media/images/openid/technorati.ico b/forum/skins/default/media/images/openid/technorati.ico deleted file mode 100644 index fa1083c..0000000 Binary files a/forum/skins/default/media/images/openid/technorati.ico and /dev/null differ diff --git a/forum/skins/default/media/images/openid/twitter.png b/forum/skins/default/media/images/openid/twitter.png deleted file mode 100755 index 9a6552d..0000000 Binary files a/forum/skins/default/media/images/openid/twitter.png and /dev/null differ diff --git a/forum/skins/default/media/images/openid/verisign.ico b/forum/skins/default/media/images/openid/verisign.ico deleted file mode 100644 index 3953af9..0000000 Binary files a/forum/skins/default/media/images/openid/verisign.ico and /dev/null differ diff --git a/forum/skins/default/media/images/openid/vidoop.ico b/forum/skins/default/media/images/openid/vidoop.ico deleted file mode 100644 index bbd9a0d..0000000 Binary files a/forum/skins/default/media/images/openid/vidoop.ico and /dev/null differ diff --git a/forum/skins/default/media/images/openid/wordpress.ico b/forum/skins/default/media/images/openid/wordpress.ico deleted file mode 100644 index 31b7d2c..0000000 Binary files a/forum/skins/default/media/images/openid/wordpress.ico and /dev/null differ diff --git a/forum/skins/default/media/images/openid/yahoo.gif b/forum/skins/default/media/images/openid/yahoo.gif deleted file mode 100644 index 0f0eb8e..0000000 Binary files a/forum/skins/default/media/images/openid/yahoo.gif and /dev/null differ diff --git a/forum/skins/default/media/images/quest-bg.gif b/forum/skins/default/media/images/quest-bg.gif deleted file mode 100644 index b754023..0000000 Binary files a/forum/skins/default/media/images/quest-bg.gif and /dev/null differ diff --git a/forum/skins/default/media/images/vote-accepted-on.png b/forum/skins/default/media/images/vote-accepted-on.png deleted file mode 100644 index 2026f3b..0000000 Binary files a/forum/skins/default/media/images/vote-accepted-on.png and /dev/null differ diff --git a/forum/skins/default/media/images/vote-accepted.png b/forum/skins/default/media/images/vote-accepted.png deleted file mode 100644 index ecd1855..0000000 Binary files a/forum/skins/default/media/images/vote-accepted.png and /dev/null differ diff --git a/forum/skins/default/media/images/vote-arrow-down-on.png b/forum/skins/default/media/images/vote-arrow-down-on.png deleted file mode 100644 index 048dbb4..0000000 Binary files a/forum/skins/default/media/images/vote-arrow-down-on.png and /dev/null differ diff --git a/forum/skins/default/media/images/vote-arrow-down.png b/forum/skins/default/media/images/vote-arrow-down.png deleted file mode 100644 index e4fdec0..0000000 Binary files a/forum/skins/default/media/images/vote-arrow-down.png and /dev/null differ diff --git a/forum/skins/default/media/images/vote-arrow-up-on.png b/forum/skins/default/media/images/vote-arrow-up-on.png deleted file mode 100644 index 56ad0c2..0000000 Binary files a/forum/skins/default/media/images/vote-arrow-up-on.png and /dev/null differ diff --git a/forum/skins/default/media/images/vote-arrow-up.png b/forum/skins/default/media/images/vote-arrow-up.png deleted file mode 100644 index 6e9a51c..0000000 Binary files a/forum/skins/default/media/images/vote-arrow-up.png and /dev/null differ diff --git a/forum/skins/default/media/images/vote-favorite-off.png b/forum/skins/default/media/images/vote-favorite-off.png deleted file mode 100644 index c1bef07..0000000 Binary files a/forum/skins/default/media/images/vote-favorite-off.png and /dev/null differ diff --git a/forum/skins/default/media/images/vote-favorite-on.png b/forum/skins/default/media/images/vote-favorite-on.png deleted file mode 100644 index 1f9c14a..0000000 Binary files a/forum/skins/default/media/images/vote-favorite-on.png and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/aol.gif b/forum/skins/default/media/jquery-openid/images/aol.gif deleted file mode 100644 index decc4f1..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/aol.gif and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/blogger-1.png b/forum/skins/default/media/jquery-openid/images/blogger-1.png deleted file mode 100644 index 8b360ea..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/blogger-1.png and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/blogger.ico b/forum/skins/default/media/jquery-openid/images/blogger.ico deleted file mode 100644 index 1b9730b..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/blogger.ico and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/claimid-0.png b/forum/skins/default/media/jquery-openid/images/claimid-0.png deleted file mode 100644 index 4a0ea1b..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/claimid-0.png and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/claimid.ico b/forum/skins/default/media/jquery-openid/images/claimid.ico deleted file mode 100644 index 2b80f49..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/claimid.ico and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/facebook.gif b/forum/skins/default/media/jquery-openid/images/facebook.gif deleted file mode 100644 index b997b35..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/facebook.gif and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/flickr.ico b/forum/skins/default/media/jquery-openid/images/flickr.ico deleted file mode 100644 index 11f6e07..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/flickr.ico and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/flickr.png b/forum/skins/default/media/jquery-openid/images/flickr.png deleted file mode 100644 index 142405a..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/flickr.png and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/google.gif b/forum/skins/default/media/jquery-openid/images/google.gif deleted file mode 100644 index 1b6cd07..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/google.gif and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/livejournal-1.png b/forum/skins/default/media/jquery-openid/images/livejournal-1.png deleted file mode 100644 index e643608..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/livejournal-1.png and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/livejournal.ico b/forum/skins/default/media/jquery-openid/images/livejournal.ico deleted file mode 100644 index f3d21ec..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/livejournal.ico and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/myopenid-2.png b/forum/skins/default/media/jquery-openid/images/myopenid-2.png deleted file mode 100644 index f64fb8e..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/myopenid-2.png and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/myopenid.ico b/forum/skins/default/media/jquery-openid/images/myopenid.ico deleted file mode 100644 index ceb06e6..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/myopenid.ico and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/openid-inputicon.gif b/forum/skins/default/media/jquery-openid/images/openid-inputicon.gif deleted file mode 100644 index cde836c..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/openid-inputicon.gif and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/openid.gif b/forum/skins/default/media/jquery-openid/images/openid.gif deleted file mode 100644 index c718b0e..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/openid.gif and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/openidico.png b/forum/skins/default/media/jquery-openid/images/openidico.png deleted file mode 100644 index ab62266..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/openidico.png and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/openidico16.png b/forum/skins/default/media/jquery-openid/images/openidico16.png deleted file mode 100644 index ad718ac..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/openidico16.png and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/technorati-1.png b/forum/skins/default/media/jquery-openid/images/technorati-1.png deleted file mode 100644 index f719524..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/technorati-1.png and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/technorati.ico b/forum/skins/default/media/jquery-openid/images/technorati.ico deleted file mode 100644 index fa1083c..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/technorati.ico and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/verisign-2.png b/forum/skins/default/media/jquery-openid/images/verisign-2.png deleted file mode 100644 index c146700..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/verisign-2.png and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/verisign.ico b/forum/skins/default/media/jquery-openid/images/verisign.ico deleted file mode 100644 index 3953af9..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/verisign.ico and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/vidoop.ico b/forum/skins/default/media/jquery-openid/images/vidoop.ico deleted file mode 100644 index bbd9a0d..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/vidoop.ico and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/vidoop.png b/forum/skins/default/media/jquery-openid/images/vidoop.png deleted file mode 100644 index 032c9e9..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/vidoop.png and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/wordpress.ico b/forum/skins/default/media/jquery-openid/images/wordpress.ico deleted file mode 100644 index 31b7d2c..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/wordpress.ico and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/wordpress.png b/forum/skins/default/media/jquery-openid/images/wordpress.png deleted file mode 100644 index ee29f0c..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/wordpress.png and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/images/yahoo.gif b/forum/skins/default/media/jquery-openid/images/yahoo.gif deleted file mode 100644 index 42adbfa..0000000 Binary files a/forum/skins/default/media/jquery-openid/images/yahoo.gif and /dev/null differ diff --git a/forum/skins/default/media/jquery-openid/jquery.openid.js b/forum/skins/default/media/jquery-openid/jquery.openid.js deleted file mode 100644 index 8d1cd20..0000000 --- a/forum/skins/default/media/jquery-openid/jquery.openid.js +++ /dev/null @@ -1,111 +0,0 @@ -//jQuery OpenID Plugin 1.1 Copyright 2009 Jarrett Vance http://jvance.com/pages/jQueryOpenIdPlugin.xhtml -$.fn.openid = function() { - var $this = $(this); - - //name input value - needed for name based OpenID - var $usr = $this.find('input[name=openid_username]'); - - //final url input value - var $id = $this.find('input[name=openid_url]'); - - //beginning and end of name OpenID url (name being the middle) - var $front = $this.find('p:has(input[name=openid_username])>span:eq(0)'); - var $end = $this.find('p:has(input[name=openid_username])>span:eq(1)'); - - //needed for special effects only - var $localfs = $this.find('fieldset:has(input[name=username])'); - var $usrfs = $this.find('fieldset:has(input[name=openid_username])'); - var $idfs = $this.find('fieldset:has(input[name=openid_url])'); - - var submitusr = function() { - if ($usr.val().length < 1) { - $usr.focus(); - return false; - } - $id.val($front.text() + $usr.val() + $end.text()); - return true; - }; - - var submitid = function() { - if ($id.val().length < 1) { - $id.focus(); - return false; - } - return true; - - }; - var local = function() { - var $li = $(this); - $('#openid_form .providers li').removeClass('highlight'); - $li.addClass('highlight'); - $usrfs.hide(); - $idfs.hide(); - $localfs.show(); - $this.unbind('submit').submit(submitid); - return false; - }; - - var direct = function() { - var $li = $(this); - $('#openid_form .providers li').removeClass('highlight'); - $li.addClass('highlight'); - $usrfs.fadeOut('slow'); - $localfs.fadeOut('slow'); - $idfs.fadeOut('slow'); - $id.val($this.find("li.highlight span").text()); - setTimeout(function(){$('#bsignin').click();},1000); - return false; - }; - - var openid = function() { - var $li = $(this); - $('#openid_form .providers li').removeClass('highlight'); - $li.addClass('highlight'); - $usrfs.hide(); - $localfs.hide(); - $idfs.show(); - $id.focus(); - $this.unbind('submit').submit(submitid); - return false; - }; - - var username = function() { - var $li = $(this); - $('#openid_form .providers li').removeClass('highlight'); - $li.addClass('highlight'); - $idfs.hide(); - $localfs.hide(); - $usrfs.show(); - $this.find('#enter_your_what').text($li.attr("title")); - $front.text($li.find("span").text().split("username")[0]); - $end.text("").text($li.find("span").text().split("username")[1]); - $id.focus(); - $this.unbind('submit').submit(submitusr); - return false; - }; - - $this.find('li.local').click(local); - $this.find('li.direct').click(direct); - $this.find('li.openid').click(openid); - $this.find('li.username').click(username); - $id.keypress(function(e) { - if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) { - return submitid(); - } - }); - $usr.keypress(function(e) { - if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) { - return submitusr(); - } - }); - $this.find('li span').hide(); - $this.find('li').css('line-height', 0).css('cursor', 'pointer'); - $usrfs.hide(); - $idfs.hide(); - $localfs.hide(); - $this.find('li:eq(0)').click(); - - return this; -}; -// submitting next=%2F&openid_username=&openid_url=http%3A%2F%2Fyahoo.com%2F -// submitting next=%2F&openid_username=&openid_url=http%3A%2F%2Fyahoo.com%2F diff --git a/forum/skins/default/media/jquery-openid/openid.css b/forum/skins/default/media/jquery-openid/openid.css deleted file mode 100644 index 1b7aaf8..0000000 --- a/forum/skins/default/media/jquery-openid/openid.css +++ /dev/null @@ -1,75 +0,0 @@ -fieldset { border-style:none; } -img {border-style:none;} - -.logo_box {display:inline-block;float:left;width:90px;height:40px;background:white;border:1px solid #dddddd;} -.openid_box img {margin-top:6px;} -.aol_box img {margin-top:6px;} -.yahoo_box img {margin-top:13px;} -.google_box img {margin-top:6px;} -.local_login_box img {margin-top:2px;margin-left:-3px;} - -form.openid ul{ margin:0;padding:0;text-align:center; list-style-type:none; display:block;} -form.openid ul li {float:left; padding:4px;display:inline-block;} -form.openid ul li div {display:inline-block;} -form.openid ul li span {padding:0 1em 0 3px} -form.openid ul li.first_tiny_li {clear:left;} -form.openid fieldset {clear:both;padding:10px 0px 0px 0px;} -form.openid div+fieldset {display:none} -form.openid label {display:block; font-weight:bold;} -input[name=openid_username] {width:8em} -input[name=openid_identifier] {width:18em} -form.openid ul li.highlight { -moz-border-radius:4px; -webkit-border-radius:4px; background-color: #FD6} -form.openid fieldset div { - -moz-border-radius:4px; - -webkit-border-radius:4px; - background: #DCDCDC; - padding:10px; - display:inline-block; - float:left; -} -form.openid p {margin-bottom:4px;} -form.openid fieldset div p {padding:0px;margin:0px;} -form.openid fieldset div p.login {padding:0px;margin:0 0 10px 0;} -form.openid label { - display:inline-block; - font-weight:normal; - width:6em; - text-align:right; -} -#local_login_fs div { - padding-bottom:4px; -} -#local_login_buttons { - text-align:center; - line-height:1.8em; - margin-top:3px; -} -/*form.openid input[type='submit'] {margin-left:1em;}*/ -#openid_username {background:#ffffa0;} -#openid_url {background:#ffffa0;} - -.openid_logo{color:#F7931E;padding:6px 0px 8px 28px; -background: url(images/openidico.png) no-repeat; -} - -#openid_login {float:left; width:30%; margin:2em 1em; text-align:center} -#openid_login div{margin-top:0.5em} - -form.openid ul.errorlist { - border: none; - list-style-position:inside; - list-style-type: disc; - margin-bottom:5px; -} -form.openid ul.errorlist li { - text-align: left; - margin: 5px; - float: none; - color:blue; -} -#openid_small_providers li { - margin-top:4px; -} -#openid_small_providers li.facebook { - margin-top:0px; -} diff --git a/forum/skins/default/media/js/com.cnprog.admin.js b/forum/skins/default/media/js/com.cnprog.admin.js deleted file mode 100644 index 39dff48..0000000 --- a/forum/skins/default/media/js/com.cnprog.admin.js +++ /dev/null @@ -1,13 +0,0 @@ -$(document).ready( function(){ - var options = { - success: function(a,b){$('.admin #action_status').html($.i18n._('changes saved'));}, - dataType:'json', - timeout:5000, - url: scriptUrl + $.i18n._('moderate-user/') + viewUserID + '/' - }; - var form = $('.admin #moderate_user_form').ajaxForm(options); - var box = $('.admin input#id_is_approved').click(function(){ - $('.admin #action_status').html($.i18n._('sending data...')); - form.ajaxSubmit(options); - }); -}); diff --git a/forum/skins/default/media/js/com.cnprog.editor.js b/forum/skins/default/media/js/com.cnprog.editor.js deleted file mode 100644 index 18cc516..0000000 --- a/forum/skins/default/media/js/com.cnprog.editor.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - 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('
    ').parent().append($('
    ').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(iMousePos1&&!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;i1){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]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"$1");},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;i0){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=$("
    ").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("
      ").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=listItems.size()){active=0;}}function limitNumberOfItems(available){return options.max&&options.max").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('