summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-01-24 14:12:22 -0500
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-01-24 14:35:50 -0500
commit4e7d6e104d95115b091e6bbf29148b5b1c96aa7e (patch)
treeaea8a8b5569a337b75bc043626ff63d03b8b92fe
parentd036b432cb49742e26e66d652f67e54e9a9a8cd2 (diff)
downloadaskbot-4e7d6e104d95115b091e6bbf29148b5b1c96aa7e.tar.gz
askbot-4e7d6e104d95115b091e6bbf29148b5b1c96aa7e.tar.bz2
askbot-4e7d6e104d95115b091e6bbf29148b5b1c96aa7e.zip
dos2unix everything, added dos2unix.sh script
-rw-r--r--INSTALL602
-rw-r--r--README6
-rwxr-xr-xdjango_authopenid/urls.py62
-rwxr-xr-xdjango_authopenid/views.py2142
-rw-r--r--dos2unix.sh10
-rwxr-xr-xfbconnect/tests.py46
-rw-r--r--forum/forms.py636
-rw-r--r--forum/managers.py482
-rw-r--r--forum/templatetags/extra_tags.py702
-rw-r--r--forum/templatetags/smart_if.py802
-rw-r--r--forum/views.py4774
-rw-r--r--templates/content/js/com.cnprog.post.js1380
12 files changed, 5830 insertions, 5814 deletions
diff --git a/INSTALL b/INSTALL
index fe41befe..612cd371 100644
--- a/INSTALL
+++ b/INSTALL
@@ -1,301 +1,301 @@
-CONTENTS
-------------------
-A. PREREQUISITES
-B. INSTALLATION
- 1. Settings file
- 2. Database
- 3. Running CNPROG 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
-
-NOTES: django_authopenid is included into CNPROG 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 cnprog installation server-readable on Linux command might be:
- chown -R yourlogin:apache /path/to/CNPROG
-
- directories templates/upfiles and log must be server writable
-
- on Linux type chmod
- chmod -R g+w /path/to/CNPROG/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 cnprog DEFAULT CHARACTER SET UTF8 COLLATE utf8_general_ci;
-grant all on cnprog.* to 'cnprog'@'localhost';
-And then run "python manage.py syncdb" to synchronize your database.
-
-3. Running CNPROG 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/CNPROG') #maybe this is not necessary
-os.environ['DJANGO_SETTINGS_MODULE'] = 'CNPROG.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
-<VirtualHost ...your ip...:80>
- ServerAdmin forum@example.com
- DocumentRoot /path/to/cnprog
- 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 CNPROG
- WSGIProcessGroup CNPROG
-
- #force all content to be served as static files
- #otherwise django will be crunching images through itself wasting time
- Alias /content/ /path/to/cnprog/templates/content/
- AliasMatch /([^/]*\.css) /path/to/cnprog/templates/content/style/$1
- <Directory /path/to/cnprog/templates/content>
- Order deny,allow
- Allow from all
- </Directory>
-
- #this is your wsgi script described in the prev section
- WSGIScriptAlias / /path/to/cnprog/cnprog.wsgi
-
- #this will force admin interface to work only
- #through https (optional)
- #"nimda" is the secret spelling of "admin" ;)
- <Location "/nimda">
- RewriteEngine on
- RewriteRule /nimda(.*)$ https://example.com/nimda$1 [L,R=301]
- </Location>
- CustomLog /var/log/httpd/CNPROG/access_log common
- ErrorLog /var/log/httpd/CNPROG/error_log
-</VirtualHost>
-#(optional) run admin interface under https
-<VirtualHost ..your ip..:443>
- ServerAdmin forum@example.com
- DocumentRoot /path/to/cnrpog
- ServerName example.com
- SSLEngine on
- SSLCertificateFile /path/to/ssl-certificate/server.crt
- SSLCertificateKeyFile /path/to/ssl-certificate/server.key
- WSGIScriptAlias / /path/to/cnprogcnprog.wsgi
- CustomLog /var/log/httpd/CNPROG/access_log common
- ErrorLog /var/log/httpd/CNPROG/error_log
- DirectoryIndex index.html
-</VirtualHost>
--------------
-
-5. Full text search (using sphinx search)
- Currently full text search works only with sphinx search engine
- 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 cnprog index first time manually
-
- % indexer --config /path/to/sphinx.conf --index cnprog
-
- 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 /<settings_local.FORUM_SCRIPT_ALIAS>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 = 'cnprog.log' #where logging messages should go
-DATABASE_NAME = 'cnprog' # 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 = '[CNPROG] '
-EMAIL_HOST='cnprog.com'
-EMAIL_PORT='25'
-EMAIL_USE_TLS=False
-TIME_ZONE = 'America/Tijuana'
-APP_TITLE = u'CNPROG Q&A Forum' #title of your forum
-APP_KEYWORDS = u'CNPROG,forum,community' #keywords for search engines
-APP_DESCRIPTION = u'Ask and answer questions.' #site description for searche engines
-APP_INTRO = u'<p>Ask and answer questions, make the world better!</p>' #slogan that goes to front page in logged out mode
-APP_COPYRIGHT = '' #copyright message
-
-#if you set FORUM_SCRIPT_ALIAS= 'forum/'
-#then CNPROG 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://cnprog.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 CNPROG uses it's own login/password
-EXTERNAL_LEGACY_LOGIN_HOST = 'login.cnprog.com'
-EXTERNAL_LEGACY_LOGIN_PORT = 80
-EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME = '<span class="orange">CNPROG</span>'
-
-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
- "CNPROG" 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
+CONTENTS
+------------------
+A. PREREQUISITES
+B. INSTALLATION
+ 1. Settings file
+ 2. Database
+ 3. Running CNPROG 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
+
+NOTES: django_authopenid is included into CNPROG 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 cnprog installation server-readable on Linux command might be:
+ chown -R yourlogin:apache /path/to/CNPROG
+
+ directories templates/upfiles and log must be server writable
+
+ on Linux type chmod
+ chmod -R g+w /path/to/CNPROG/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 cnprog DEFAULT CHARACTER SET UTF8 COLLATE utf8_general_ci;
+grant all on cnprog.* to 'cnprog'@'localhost';
+And then run "python manage.py syncdb" to synchronize your database.
+
+3. Running CNPROG 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/CNPROG') #maybe this is not necessary
+os.environ['DJANGO_SETTINGS_MODULE'] = 'CNPROG.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
+<VirtualHost ...your ip...:80>
+ ServerAdmin forum@example.com
+ DocumentRoot /path/to/cnprog
+ 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 CNPROG
+ WSGIProcessGroup CNPROG
+
+ #force all content to be served as static files
+ #otherwise django will be crunching images through itself wasting time
+ Alias /content/ /path/to/cnprog/templates/content/
+ AliasMatch /([^/]*\.css) /path/to/cnprog/templates/content/style/$1
+ <Directory /path/to/cnprog/templates/content>
+ Order deny,allow
+ Allow from all
+ </Directory>
+
+ #this is your wsgi script described in the prev section
+ WSGIScriptAlias / /path/to/cnprog/cnprog.wsgi
+
+ #this will force admin interface to work only
+ #through https (optional)
+ #"nimda" is the secret spelling of "admin" ;)
+ <Location "/nimda">
+ RewriteEngine on
+ RewriteRule /nimda(.*)$ https://example.com/nimda$1 [L,R=301]
+ </Location>
+ CustomLog /var/log/httpd/CNPROG/access_log common
+ ErrorLog /var/log/httpd/CNPROG/error_log
+</VirtualHost>
+#(optional) run admin interface under https
+<VirtualHost ..your ip..:443>
+ ServerAdmin forum@example.com
+ DocumentRoot /path/to/cnrpog
+ ServerName example.com
+ SSLEngine on
+ SSLCertificateFile /path/to/ssl-certificate/server.crt
+ SSLCertificateKeyFile /path/to/ssl-certificate/server.key
+ WSGIScriptAlias / /path/to/cnprogcnprog.wsgi
+ CustomLog /var/log/httpd/CNPROG/access_log common
+ ErrorLog /var/log/httpd/CNPROG/error_log
+ DirectoryIndex index.html
+</VirtualHost>
+-------------
+
+5. Full text search (using sphinx search)
+ Currently full text search works only with sphinx search engine
+ 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 cnprog index first time manually
+
+ % indexer --config /path/to/sphinx.conf --index cnprog
+
+ 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 /<settings_local.FORUM_SCRIPT_ALIAS>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 = 'cnprog.log' #where logging messages should go
+DATABASE_NAME = 'cnprog' # 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 = '[CNPROG] '
+EMAIL_HOST='cnprog.com'
+EMAIL_PORT='25'
+EMAIL_USE_TLS=False
+TIME_ZONE = 'America/Tijuana'
+APP_TITLE = u'CNPROG Q&A Forum' #title of your forum
+APP_KEYWORDS = u'CNPROG,forum,community' #keywords for search engines
+APP_DESCRIPTION = u'Ask and answer questions.' #site description for searche engines
+APP_INTRO = u'<p>Ask and answer questions, make the world better!</p>' #slogan that goes to front page in logged out mode
+APP_COPYRIGHT = '' #copyright message
+
+#if you set FORUM_SCRIPT_ALIAS= 'forum/'
+#then CNPROG 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://cnprog.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 CNPROG uses it's own login/password
+EXTERNAL_LEGACY_LOGIN_HOST = 'login.cnprog.com'
+EXTERNAL_LEGACY_LOGIN_PORT = 80
+EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME = '<span class="orange">CNPROG</span>'
+
+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
+ "CNPROG" 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/README b/README
new file mode 100644
index 00000000..2a209b7a
--- /dev/null
+++ b/README
@@ -0,0 +1,6 @@
+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/django_authopenid/urls.py b/django_authopenid/urls.py
index 9ed621bc..112cbbe1 100755
--- a/django_authopenid/urls.py
+++ b/django_authopenid/urls.py
@@ -1,31 +1,31 @@
-# -*- coding: utf-8 -*-
-from django.conf.urls.defaults import patterns, url
-from django.utils.translation import ugettext as _
-
-urlpatterns = patterns('django_authopenid.views',
- # yadis rdf
- url(r'^yadis.xrdf$', 'xrdf', name='yadis_xrdf'),
- # manage account registration
- url(r'^%s$' % _('signin/'), 'signin', name='user_signin'),
- url(r'^%s%s$' % (_('signin/'),_('newquestion/')), 'signin', kwargs = {'newquestion':True}, name='user_signin_new_question'),
- url(r'^%s%s$' % (_('signin/'),_('newanswer/')), 'signin', kwargs = {'newanswer':True}, name='user_signin_new_answer'),
- url(r'^%s$' % _('signout/'), 'signout', name='user_signout'),
- url(r'^%s%s$' % (_('signin/'), _('complete/')), 'complete_signin',
- name='user_complete_signin'),
- url('^%s$' % _('external-login/'),'external_legacy_login_info', name='user_external_legacy_login_issues'),
- url(r'^%s$' % _('register/'), 'register', name='user_register'),
- url(r'^%s$' % _('signup/'), 'signup', name='user_signup'),
- #disable current sendpw function
- url(r'^%s$' % _('sendpw/'), 'sendpw', name='user_sendpw'),
- url(r'^%s%s$' % (_('password/'), _('confirm/')), 'confirmchangepw', name='user_confirmchangepw'),
-
- # manage account settings
- url(r'^$', _('account_settings'), name='user_account_settings'),
- url(r'^%s$' % _('password/'), 'changepw', name='user_changepw'),
- url(r'^%s%s$' % (_('email/'),_('validate/')), 'changeemail', name='user_validateemail',kwargs = {'action':'validate'}),
- url(r'^%s%s$' % (_('email/'), _('change/')), 'changeemail', name='user_changeemail'),
- url(r'^%s%s$' % (_('email/'), _('sendkey/')), 'send_email_key', name='send_email_key'),
- url(r'^%s%s(?P<id>\d+)/(?P<key>[\dabcdef]{32})/$' % (_('email/'), _('verify/')), 'verifyemail', name='user_verifyemail'),
- url(r'^%s$' % _('openid/'), 'changeopenid', name='user_changeopenid'),
- url(r'^%s$' % _('delete/'), 'delete', name='user_delete'),
-)
+# -*- coding: utf-8 -*-
+from django.conf.urls.defaults import patterns, url
+from django.utils.translation import ugettext as _
+
+urlpatterns = patterns('django_authopenid.views',
+ # yadis rdf
+ url(r'^yadis.xrdf$', 'xrdf', name='yadis_xrdf'),
+ # manage account registration
+ url(r'^%s$' % _('signin/'), 'signin', name='user_signin'),
+ url(r'^%s%s$' % (_('signin/'),_('newquestion/')), 'signin', kwargs = {'newquestion':True}, name='user_signin_new_question'),
+ url(r'^%s%s$' % (_('signin/'),_('newanswer/')), 'signin', kwargs = {'newanswer':True}, name='user_signin_new_answer'),
+ url(r'^%s$' % _('signout/'), 'signout', name='user_signout'),
+ url(r'^%s%s$' % (_('signin/'), _('complete/')), 'complete_signin',
+ name='user_complete_signin'),
+ url('^%s$' % _('external-login/'),'external_legacy_login_info', name='user_external_legacy_login_issues'),
+ url(r'^%s$' % _('register/'), 'register', name='user_register'),
+ url(r'^%s$' % _('signup/'), 'signup', name='user_signup'),
+ #disable current sendpw function
+ url(r'^%s$' % _('sendpw/'), 'sendpw', name='user_sendpw'),
+ url(r'^%s%s$' % (_('password/'), _('confirm/')), 'confirmchangepw', name='user_confirmchangepw'),
+
+ # manage account settings
+ url(r'^$', _('account_settings'), name='user_account_settings'),
+ url(r'^%s$' % _('password/'), 'changepw', name='user_changepw'),
+ url(r'^%s%s$' % (_('email/'),_('validate/')), 'changeemail', name='user_validateemail',kwargs = {'action':'validate'}),
+ url(r'^%s%s$' % (_('email/'), _('change/')), 'changeemail', name='user_changeemail'),
+ url(r'^%s%s$' % (_('email/'), _('sendkey/')), 'send_email_key', name='send_email_key'),
+ url(r'^%s%s(?P<id>\d+)/(?P<key>[\dabcdef]{32})/$' % (_('email/'), _('verify/')), 'verifyemail', name='user_verifyemail'),
+ url(r'^%s$' % _('openid/'), 'changeopenid', name='user_changeopenid'),
+ url(r'^%s$' % _('delete/'), 'delete', name='user_delete'),
+)
diff --git a/django_authopenid/views.py b/django_authopenid/views.py
index 36f08018..d087d215 100755
--- a/django_authopenid/views.py
+++ b/django_authopenid/views.py
@@ -1,1071 +1,1071 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2007, 2008, BenoƮt Chesneau
-# Copyright (c) 2007 Simon Willison, original work on django-openid
-#
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-# * Redistributions of source code must retain the above copyright
-# * notice, this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright
-# * notice, this list of conditions and the following disclaimer in the
-# * documentation and/or other materials provided with the
-# * distribution. Neither the name of the <ORGANIZATION> nor the names
-# * of its contributors may be used to endorse or promote products
-# * derived from this software without specific prior written
-# * permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
-# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
-# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-from django.http import HttpResponseRedirect, get_host, Http404, \
- HttpResponseServerError
-from django.shortcuts import render_to_response as render
-from django.template import RequestContext, loader, Context
-from django.conf import settings
-from django.contrib.auth.models import User
-from django.contrib.auth.decorators import login_required
-from django.contrib.auth import authenticate
-from django.core.urlresolvers import reverse
-from django.utils.encoding import smart_unicode
-from django.utils.html import escape
-from django.utils.translation import ugettext as _
-from django.utils.http import urlquote_plus
-from django.utils.safestring import mark_safe
-from django.core.mail import send_mail
-from django.views.defaults import server_error
-
-from openid.consumer.consumer import Consumer, \
- SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
-from openid.consumer.discover import DiscoveryFailure
-from openid.extensions import sreg
-# needed for some linux distributions like debian
-try:
- from openid.yadis import xri
-except ImportError:
- from yadis import xri
-
-import re
-import urllib
-
-
-from forum.forms import EditUserEmailFeedsForm
-from django_authopenid.util import OpenID, DjangoOpenIDStore, from_openid_response, get_next_url
-from django_authopenid.models import UserAssociation, UserPasswordQueue, ExternalLoginData
-from django_authopenid.forms import OpenidSigninForm, ClassicLoginForm, OpenidRegisterForm, \
- OpenidVerifyForm, ClassicRegisterForm, ChangePasswordForm, ChangeEmailForm, \
- ChangeopenidForm, DeleteForm, EmailPasswordForm
-import external_login
-import logging
-
-def login(request,user):
- from django.contrib.auth import login as _login
- from forum.models import user_logged_in #custom signal
-
- print 'in login call'
-
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- external_login.login(request,user)
-
- #1) get old session key
- session_key = request.session.session_key
- #2) login and get new session key
- _login(request,user)
- #3) send signal with old session key as argument
- user_logged_in.send(user=user,session_key=session_key,sender=None)
-
-def logout(request):
- from django.contrib.auth import logout as _logout#for login I've added wrapper below - called login
- _logout(request)
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- external_login.logout(request)
-
-def get_url_host(request):
- if request.is_secure():
- protocol = 'https'
- else:
- protocol = 'http'
- host = escape(get_host(request))
- return '%s://%s' % (protocol, host)
-
-def get_full_url(request):
- return get_url_host(request) + request.get_full_path()
-
-def ask_openid(request, openid_url, redirect_to, on_failure=None,
- sreg_request=None):
- """ basic function to ask openid and return response """
- request.encoding = 'UTF-8'
- on_failure = on_failure or signin_failure
-
- trust_root = getattr(
- settings, 'OPENID_TRUST_ROOT', get_url_host(request) + '/'
- )
- if xri.identifierScheme(openid_url) == 'XRI' and getattr(
- settings, 'OPENID_DISALLOW_INAMES', False
- ):
- msg = _("i-names are not supported")
- return on_failure(request, msg)
- consumer = Consumer(request.session, DjangoOpenIDStore())
- try:
- auth_request = consumer.begin(openid_url)
- except DiscoveryFailure:
- msg = _(u"OpenID %(openid_url)s is invalid" % {'openid_url':openid_url})
- return on_failure(request, msg)
-
- if sreg_request:
- auth_request.addExtension(sreg_request)
- redirect_url = auth_request.redirectURL(trust_root, redirect_to)
- return HttpResponseRedirect(redirect_url)
-
-def complete(request, on_success=None, on_failure=None, return_to=None):
- """ complete openid signin """
- on_success = on_success or default_on_success
- on_failure = on_failure or default_on_failure
-
- consumer = Consumer(request.session, DjangoOpenIDStore())
- # make sure params are encoded in utf8
- params = dict((k,smart_unicode(v)) for k, v in request.GET.items())
- openid_response = consumer.complete(params, return_to)
-
- if openid_response.status == SUCCESS:
- return on_success(request, openid_response.identity_url,
- openid_response)
- elif openid_response.status == CANCEL:
- return on_failure(request, 'The request was canceled')
- elif openid_response.status == FAILURE:
- return on_failure(request, openid_response.message)
- elif openid_response.status == SETUP_NEEDED:
- return on_failure(request, 'Setup needed')
- else:
- assert False, "Bad openid status: %s" % openid_response.status
-
-def default_on_success(request, identity_url, openid_response):
- """ default action on openid signin success """
- request.session['openid'] = from_openid_response(openid_response)
- return HttpResponseRedirect(get_next_url(request))
-
-def default_on_failure(request, message):
- """ default failure action on signin """
- return render('openid_failure.html', {
- 'message': message
- })
-
-
-def not_authenticated(func):
- """ decorator that redirect user to next page if
- he is already logged."""
- def decorated(request, *args, **kwargs):
- if request.user.is_authenticated():
- return HttpResponseRedirect(get_next_url(request))
- return func(request, *args, **kwargs)
- return decorated
-
-@not_authenticated
-def signin(request,newquestion=False,newanswer=False):
- """
- signin page. It manages the legacy authentification (user/password)
- and openid authentification
-
- url: /signin/
-
- template : authopenid/signin.htm
- """
- request.encoding = 'UTF-8'
- on_failure = signin_failure
- email_feeds_form = EditUserEmailFeedsForm()
- next = get_next_url(request)
- form_signin = OpenidSigninForm(initial={'next':next})
- form_auth = ClassicLoginForm(initial={'next':next})
-
- if request.POST:
- #'blogin' - password login
- if 'blogin' in request.POST.keys():
- form_auth = ClassicLoginForm(request.POST)
- if form_auth.is_valid():
- #have login and password and need to login through external website
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- username = form_auth.cleaned_data['username']
- password = form_auth.cleaned_data['password']
- next = form_auth.cleaned_data['next']
- if form_auth.get_user() == None:
- #need to create internal user
-
- #1) save login and password temporarily in session
- request.session['external_username'] = username
- request.session['external_password'] = password
-
- #2) see if username clashes with some existing user
- #if so, we have to prompt the user to pick a different name
- username_taken = User.is_username_taken(username)
- #try:
- # User.objects.get(username=username)
- # username_taken = True
- #except User.DoesNotExist:
- # username_taken = False
-
- #3) try to extract user email from external service
- email = external_login.get_email(username,password)
-
- email_feeds_form = EditUserEmailFeedsForm()
- form_data = {'username':username,'email':email,'next':next}
- form = OpenidRegisterForm(initial=form_data)
- template_data = {'form1':form,'username':username,\
- 'email_feeds_form':email_feeds_form,\
- 'provider':mark_safe(settings.EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME),\
- 'login_type':'legacy',\
- 'gravatar_faq_url':reverse('faq') + '#gravatar',\
- 'external_login_name_is_taken':username_taken}
- return render('authopenid/complete.html',template_data,\
- context_instance=RequestContext(request))
- else:
- #user existed, external password is ok
- user = form_auth.get_user()
- login(request,user)
- response = HttpResponseRedirect(get_next_url(request))
- external_login.set_login_cookies(response,user)
- return response
- else:
- #regular password authentication
- user = form_auth.get_user()
- login(request, user)
- return HttpResponseRedirect(get_next_url(request))
-
- elif 'bnewaccount' in request.POST.keys():
- #register externally logged in password user with a new local account
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- form = OpenidRegisterForm(request.POST)
- email_feeds_form = EditUserEmailFeedsForm(request.POST)
- form1_is_valid = form.is_valid()
- form2_is_valid = email_feeds_form.is_valid()
- if form1_is_valid and form2_is_valid:
- #create the user
- username = form.cleaned_data['username']
- password = request.session.get('external_password',None)
- email = form.cleaned_data['email']
- print 'got email addr %s' % email
- if password and username:
- User.objects.create_user(username,email,password)
- user = authenticate(username=username,password=password)
- external_username = request.session['external_username']
- eld = ExternalLoginData.objects.get(external_username=external_username)
- eld.user = user
- eld.save()
- login(request,user)
- email_feeds_form.save(user)
- del request.session['external_username']
- del request.session['external_password']
- return HttpResponseRedirect(reverse('index'))
- else:
- if password:
- del request.session['external_username']
- if username:
- del request.session['external_password']
- return HttpResponseServerError()
- else:
- username = request.POST.get('username',None)
- provider = mark_safe(settings.EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME)
- username_taken = User.is_username_taken(username)
- data = {'login_type':'legacy','form1':form,'username':username,\
- 'email_feeds_form':email_feeds_form,'provider':provider,\
- 'gravatar_faq_url':reverse('faq') + '#gravatar',\
- 'external_login_name_is_taken':username_taken}
- return render('authopenid/complete.html',data,
- context_instance=RequestContext(request))
- else:
- raise Http404
-
- elif 'bsignin' in request.POST.keys() or 'openid_username' in request.POST.keys():
- form_signin = OpenidSigninForm(request.POST)
- if form_signin.is_valid():
- next = form_signin.cleaned_data['next']
- sreg_req = sreg.SRegRequest(optional=['nickname', 'email'])
- redirect_to = "%s%s?%s" % (
- get_url_host(request),
- reverse('user_complete_signin'),
- urllib.urlencode({'next':next})
- )
- return ask_openid(request,
- form_signin.cleaned_data['openid_url'],
- redirect_to,
- on_failure=signin_failure,
- sreg_request=sreg_req)
-
-
- #if request is GET
- question = None
- if newquestion == True:
- from forum.models import AnonymousQuestion as AQ
- session_key = request.session.session_key
- qlist = AQ.objects.filter(session_key=session_key).order_by('-added_at')
- if len(qlist) > 0:
- question = qlist[0]
- answer = None
- if newanswer == True:
- from forum.models import AnonymousAnswer as AA
- session_key = request.session.session_key
- alist = AA.objects.filter(session_key=session_key).order_by('-added_at')
- if len(alist) > 0:
- answer = alist[0]
-
- return render('authopenid/signin.html', {
- 'question':question,
- 'answer':answer,
- 'form1': form_auth,
- 'form2': form_signin,
- 'msg': request.GET.get('msg',''),
- 'sendpw_url': reverse('user_sendpw'),
- 'fb_api_key': settings.FB_API_KEY,
- }, context_instance=RequestContext(request))
-
-def complete_signin(request):
- """ in case of complete signin with openid """
- return complete(request, signin_success, signin_failure,
- get_url_host(request) + reverse('user_complete_signin'))
-
-def signin_success(request, identity_url, openid_response):
- """
- openid signin success.
-
- If the openid is already registered, the user is redirected to
- url set par next or in settings with OPENID_REDIRECT_NEXT variable.
- If none of these urls are set user is redirectd to /.
-
- if openid isn't registered user is redirected to register page.
- """
-
- openid_ = from_openid_response(openid_response) #create janrain OpenID object
- request.session['openid'] = openid_
- try:
- rel = UserAssociation.objects.get(openid_url__exact = str(openid_))
- except:
- # try to register this new user
- return register(request)
- user_ = rel.user
- if user_.is_active:
- user_.backend = "django.contrib.auth.backends.ModelBackend"
- login(request, user_)
-
- return HttpResponseRedirect(get_next_url(request))
-
-def is_association_exist(openid_url):
- """ test if an openid is already in database """
- is_exist = True
- try:
- uassoc = UserAssociation.objects.get(openid_url__exact = openid_url)
- except:
- is_exist = False
- return is_exist
-
-@not_authenticated
-def register(request):
- """
- register an openid.
-
- If user is already a member he can associate its openid with
- its account.
-
- A new account could also be created and automaticaly associated
- to the openid.
-
- url : /complete/
-
- template : authopenid/complete.html
- """
-
- openid_ = request.session.get('openid', None)
- next = get_next_url(request)
- if not openid_:
- return HttpResponseRedirect(reverse('user_signin') + '?next=%s' % next)
-
- nickname = openid_.sreg.get('nickname', '')
- email = openid_.sreg.get('email', '')
- form1 = OpenidRegisterForm(initial={
- 'next': next,
- 'username': nickname,
- 'email': email,
- })
- form2 = OpenidVerifyForm(initial={
- 'next': next,
- 'username': nickname,
- })
- email_feeds_form = EditUserEmailFeedsForm()
-
- user_ = None
- is_redirect = False
- if request.POST:
- if 'bnewaccount' in request.POST.keys():
- form1 = OpenidRegisterForm(request.POST)
- email_feeds_form = EditUserEmailFeedsForm(request.POST)
- if form1.is_valid() and email_feeds_form.is_valid():
- next = form1.cleaned_data['next']
- is_redirect = True
- tmp_pwd = User.objects.make_random_password()
- user_ = User.objects.create_user(form1.cleaned_data['username'],
- form1.cleaned_data['email'], tmp_pwd)
-
- user_.set_unusable_password()
- # make association with openid
- uassoc = UserAssociation(openid_url=str(openid_),
- user_id=user_.id)
- uassoc.save()
-
- # login
- user_.backend = "django.contrib.auth.backends.ModelBackend"
- login(request, user_)
- email_feeds_form.save(user_)
- elif 'bverify' in request.POST.keys():
- form2 = OpenidVerifyForm(request.POST)
- if form2.is_valid():
- is_redirect = True
- next = form2.cleaned_data['next']
- user_ = form2.get_user()
-
- uassoc = UserAssociation(openid_url=str(openid_),
- user_id=user_.id)
- uassoc.save()
- login(request, user_)
-
- #check if we need to post a question that was added anonymously
- #this needs to be a function call becase this is also done
- #if user just logged in and did not need to create the new account
-
- if user_ != None:
- if settings.EMAIL_VALIDATION == 'on':
- send_new_email_key(user_,nomessage=True)
- output = validation_email_sent(request)
- set_email_validation_message(user_) #message set after generating view
- return output
- if user_.is_authenticated():
- return HttpResponseRedirect(reverse('index'))
- else:
- raise Exception('openid login failed')#should not ever get here
-
- openid_str = str(openid_)
- bits = openid_str.split('/')
- base_url = bits[2] #assume this is base url
- url_bits = base_url.split('.')
- provider_name = url_bits[-2].lower()
-
- providers = {'yahoo':'<font color="purple">Yahoo!</font>',
- 'flickr':'<font color="#0063dc">flick</font><font color="#ff0084">r</font>&trade;',
- 'google':'Google&trade;',
- 'aol':'<font color="#31658e">AOL</font>',
- 'myopenid':'MyOpenID',
- }
- if provider_name not in providers:
- provider_logo = provider_name
- else:
- provider_logo = providers[provider_name]
-
- return render('authopenid/complete.html', {
- 'form1': form1,
- 'form2': form2,
- 'email_feeds_form': email_feeds_form,
- 'provider':mark_safe(provider_logo),
- 'username': nickname,
- 'email': email,
- 'login_type':'openid',
- 'gravatar_faq_url':reverse('faq') + '#gravatar',
- }, context_instance=RequestContext(request))
-
-def signin_failure(request, message):
- """
- falure with openid signin. Go back to signin page.
-
- template : "authopenid/signin.html"
- """
- next = get_next_url(request)
- form_signin = OpenidSigninForm(initial={'next': next})
- form_auth = ClassicLoginForm(initial={'next': next})
-
- return render('authopenid/signin.html', {
- 'msg': message,
- 'form1': form_auth,
- 'form2': form_signin,
- }, context_instance=RequestContext(request))
-
-@not_authenticated
-def signup(request):
- """
- signup page. Create a legacy account
-
- url : /signup/"
-
- templates: authopenid/signup.html, authopenid/confirm_email.txt
- """
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
- next = get_next_url(request)
- if request.POST:
- form = ClassicRegisterForm(request.POST)
- email_feeds_form = EditUserEmailFeedsForm(request.POST)
-
- #validation outside if to remember form values
- form1_is_valid = form.is_valid()
- form2_is_valid = email_feeds_form.is_valid()
- if form1_is_valid and form2_is_valid:
- next = form.cleaned_data['next']
- username = form.cleaned_data['username']
- password = form.cleaned_data['password1']
- email = form.cleaned_data['email']
-
- user_ = User.objects.create_user( username,email,password )
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- external_login.create_user(username,email,password)
-
- user_.backend = "django.contrib.auth.backends.ModelBackend"
- login(request, user_)
- email_feeds_form.save(user_)
-
- # send email
- subject = _("Welcome email subject line")
- message_template = loader.get_template(
- 'authopenid/confirm_email.txt'
- )
- message_context = Context({
- 'signup_url': settings.APP_URL + reverse('user_signin'),
- 'username': username,
- 'password': password,
- })
- message = message_template.render(message_context)
- send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
- [user_.email])
- return HttpResponseRedirect(next)
- else:
- form = ClassicRegisterForm(initial={'next':next})
- email_feeds_form = EditUserEmailFeedsForm()
- return render('authopenid/signup.html', {
- 'form': form,
- 'email_feeds_form': email_feeds_form
- }, context_instance=RequestContext(request))
- #what if request is not posted?
-
-@login_required
-def signout(request):
- """
- signout from the website. Remove openid from session and kill it.
-
- url : /signout/"
- """
- try:
- del request.session['openid']
- except KeyError:
- pass
- logout(request)
- return HttpResponseRedirect(get_next_url(request))
-
-def xrdf(request):
- url_host = get_url_host(request)
- return_to = [
- "%s%s" % (url_host, reverse('user_complete_signin'))
- ]
- return render('authopenid/yadis.xrdf', {
- 'return_to': return_to
- }, context_instance=RequestContext(request))
-
-@login_required
-def account_settings(request):
- """
- index pages to changes some basic account settings :
- - change password
- - change email
- - associate a new openid
- - delete account
-
- url : /
-
- template : authopenid/settings.html
- """
- msg = request.GET.get('msg', '')
- is_openid = True
-
- try:
- uassoc = UserAssociation.objects.get(
- user__username__exact=request.user.username
- )
- except:
- is_openid = False
-
-
- return render('authopenid/settings.html', {
- 'msg': msg,
- 'is_openid': is_openid
- }, context_instance=RequestContext(request))
-
-@login_required
-def changepw(request):
- """
- change password view.
-
- url : /changepw/
- template: authopenid/changepw.html
- """
- user_ = request.user
-
- if user_.has_usable_password():
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
- else:
- raise Http404
-
- if request.POST:
- form = ChangePasswordForm(request.POST, user=user_)
- if form.is_valid():
- user_.set_password(form.cleaned_data['password1'])
- user_.save()
- msg = _("Password changed.")
- redirect = "%s?msg=%s" % (
- reverse('user_account_settings'),
- urlquote_plus(msg))
- return HttpResponseRedirect(redirect)
- else:
- form = ChangePasswordForm(user=user_)
-
- return render('authopenid/changepw.html', {'form': form },
- context_instance=RequestContext(request))
-
-def find_email_validation_messages(user):
- msg_text = _('your email needs to be validated see %(details_url)s') \
- % {'details_url':reverse('faq') + '#validate'}
- return user.message_set.filter(message__exact=msg_text)
-
-def set_email_validation_message(user):
- messages = find_email_validation_messages(user)
- msg_text = _('your email needs to be validated see %(details_url)s') \
- % {'details_url':reverse('faq') + '#validate'}
- if len(messages) == 0:
- user.message_set.create(message=msg_text)
-
-def clear_email_validation_message(user):
- messages = find_email_validation_messages(user)
- messages.delete()
-
-def set_new_email(user, new_email, nomessage=False):
- if new_email != user.email:
- user.email = new_email
- user.email_isvalid = False
- user.save()
- if settings.EMAIL_VALIDATION == 'on':
- send_new_email_key(user,nomessage=nomessage)
-
-def _send_email_key(user):
- """private function. sends email containing validation key
- to user's email address
- """
- subject = _("Email verification subject line")
- message_template = loader.get_template('authopenid/email_validation.txt')
- import settings
- message_context = Context({
- 'validation_link': settings.APP_URL + reverse('user_verifyemail', kwargs={'id':user.id,'key':user.email_key})
- })
- message = message_template.render(message_context)
- send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email])
-
-def send_new_email_key(user,nomessage=False):
- import random
- random.seed()
- user.email_key = '%032x' % random.getrandbits(128)
- user.save()
- _send_email_key(user)
- if nomessage==False:
- set_email_validation_message(user)
-
-@login_required
-def send_email_key(request):
- """
- url = /email/sendkey/
-
- view that is shown right after sending email key
- email sending is called internally
-
- raises 404 if email validation is off
- if current email is valid shows 'key_not_sent' view of
- authopenid/changeemail.html template
- """
-
- if settings.EMAIL_VALIDATION != 'off':
- if request.user.email_isvalid:
- return render('authopenid/changeemail.html',
- { 'email': request.user.email,
- 'action_type': 'key_not_sent',
- 'change_link': reverse('user_changeemail')},
- context_instance=RequestContext(request)
- )
- else:
- send_new_email_key(request.user)
- return validation_email_sent(request)
- else:
- raise Http404
-
-
-#internal server view used as return value by other views
-def validation_email_sent(request):
- return render('authopenid/changeemail.html',
- { 'email': request.user.email,
- 'change_email_url': reverse('user_changeemail'),
- 'action_type': 'validate', },
- context_instance=RequestContext(request))
-
-def verifyemail(request,id=None,key=None):
- """
- view that is shown when user clicks email validation link
- url = /email/verify/{{user.id}}/{{user.email_key}}/
- """
- if settings.EMAIL_VALIDATION != 'off':
- user = User.objects.get(id=id)
- if user:
- if user.email_key == key:
- user.email_isvalid = True
- clear_email_validation_message(user)
- user.save()
- return render('authopenid/changeemail.html', {
- 'action_type': 'validation_complete',
- }, context_instance=RequestContext(request))
- raise Http404
-
-@login_required
-def changeemail(request, action='change'):
- """
- changeemail view. requires openid with request type GET
-
- url: /email/*
-
- template : authopenid/changeemail.html
- """
- msg = request.GET.get('msg', None)
- extension_args = {}
- user_ = request.user
-
- if request.POST:
- if 'cancel' in request.POST:
- msg = _('your email was not changed')
- request.user.message_set.create(message=msg)
- return HttpResponseRedirect(get_next_url(request))
- form = ChangeEmailForm(request.POST, user=user_)
- if form.is_valid():
- new_email = form.cleaned_data['email']
- if new_email != user_.email:
- if settings.EMAIL_VALIDATION == 'on':
- action = 'validate'
- else:
- action = 'done_novalidate'
- set_new_email(user_, new_email,nomessage=True)
- else:
- action = 'keep'
-
- elif not request.POST and 'openid.mode' in request.GET:
- redirect_to = get_url_host(request) + reverse('user_changeemail')
- return complete(request, emailopenid_success,
- emailopenid_failure, redirect_to)
- else:
- form = ChangeEmailForm(initial={'email': user_.email},
- user=user_)
-
- output = render('authopenid/changeemail.html', {
- 'form': form,
- 'email': user_.email,
- 'action_type': action,
- 'gravatar_faq_url': reverse('faq') + '#gravatar',
- 'change_email_url': reverse('user_changeemail'),
- 'msg': msg
- }, context_instance=RequestContext(request))
-
- if action == 'validate':
- set_email_validation_message(user_)
-
- return output
-
-def emailopenid_success(request, identity_url, openid_response):
- openid_ = from_openid_response(openid_response)
-
- user_ = request.user
- try:
- uassoc = UserAssociation.objects.get(
- openid_url__exact=identity_url
- )
- except:
- return emailopenid_failure(request,
- _("No OpenID %s found associated in our database" % identity_url))
-
- if uassoc.user.username != request.user.username:
- return emailopenid_failure(request,
- _("The OpenID %s isn't associated to current user logged in" %
- identity_url))
-
- new_email = request.session.get('new_email', '')
- if new_email:
- user_.email = new_email
- user_.save()
- del request.session['new_email']
- msg = _("Email Changed.")
-
- redirect = "%s?msg=%s" % (reverse('user_account_settings'),
- urlquote_plus(msg))
- return HttpResponseRedirect(redirect)
-
-
-def emailopenid_failure(request, message):
- redirect_to = "%s?msg=%s" % (
- reverse('user_changeemail'), urlquote_plus(message))
- return HttpResponseRedirect(redirect_to)
-
-@login_required
-def changeopenid(request):
- """
- change openid view. Allow user to change openid
- associated to its username.
-
- url : /changeopenid/
-
- template: authopenid/changeopenid.html
- """
-
- extension_args = {}
- openid_url = ''
- has_openid = True
- msg = request.GET.get('msg', '')
-
- user_ = request.user
-
- try:
- uopenid = UserAssociation.objects.get(user=user_)
- openid_url = uopenid.openid_url
- except:
- has_openid = False
-
- redirect_to = get_url_host(request) + reverse('user_changeopenid')
- if request.POST and has_openid:
- form = ChangeopenidForm(request.POST, user=user_)
- if form.is_valid():
- return ask_openid(request, form.cleaned_data['openid_url'],
- redirect_to, on_failure=changeopenid_failure)
- elif not request.POST and has_openid:
- if 'openid.mode' in request.GET:
- return complete(request, changeopenid_success,
- changeopenid_failure, redirect_to)
-
- form = ChangeopenidForm(initial={'openid_url': openid_url }, user=user_)
- return render('authopenid/changeopenid.html', {
- 'form': form,
- 'has_openid': has_openid,
- 'msg': msg
- }, context_instance=RequestContext(request))
-
-def changeopenid_success(request, identity_url, openid_response):
- openid_ = from_openid_response(openid_response)
- is_exist = True
- try:
- uassoc = UserAssociation.objects.get(openid_url__exact=identity_url)
- except:
- is_exist = False
-
- if not is_exist:
- try:
- uassoc = UserAssociation.objects.get(
- user__username__exact=request.user.username
- )
- uassoc.openid_url = identity_url
- uassoc.save()
- except:
- uassoc = UserAssociation(user=request.user,
- openid_url=identity_url)
- uassoc.save()
- elif uassoc.user.username != request.user.username:
- return changeopenid_failure(request,
- _('This OpenID is already associated with another account.'))
-
- request.session['openids'] = []
- request.session['openids'].append(openid_)
-
- msg = _("OpenID %s is now associated with your account." % identity_url)
- redirect = "%s?msg=%s" % (
- reverse('user_account_settings'),
- urlquote_plus(msg))
- return HttpResponseRedirect(redirect)
-
-
-def changeopenid_failure(request, message):
- redirect_to = "%s?msg=%s" % (
- reverse('user_changeopenid'),
- urlquote_plus(message))
- return HttpResponseRedirect(redirect_to)
-
-@login_required
-def delete(request):
- """
- delete view. Allow user to delete its account. Password/openid are required to
- confirm it. He should also check the confirm checkbox.
-
- url : /delete
-
- template : authopenid/delete.html
- """
-
- extension_args = {}
-
- user_ = request.user
-
- redirect_to = get_url_host(request) + reverse('user_delete')
- if request.POST:
- form = DeleteForm(request.POST, user=user_)
- if form.is_valid():
- if not form.test_openid:
- user_.delete()
- return signout(request)
- else:
- return ask_openid(request, form.cleaned_data['password'],
- redirect_to, on_failure=deleteopenid_failure)
- elif not request.POST and 'openid.mode' in request.GET:
- return complete(request, deleteopenid_success, deleteopenid_failure,
- redirect_to)
-
- form = DeleteForm(user=user_)
-
- msg = request.GET.get('msg','')
- return render('authopenid/delete.html', {
- 'form': form,
- 'msg': msg,
- }, context_instance=RequestContext(request))
-
-def deleteopenid_success(request, identity_url, openid_response):
- openid_ = from_openid_response(openid_response)
-
- user_ = request.user
- try:
- uassoc = UserAssociation.objects.get(
- openid_url__exact=identity_url
- )
- except:
- return deleteopenid_failure(request,
- _("No OpenID %s found associated in our database" % identity_url))
-
- if uassoc.user.username == user_.username:
- user_.delete()
- return signout(request)
- else:
- return deleteopenid_failure(request,
- _("The OpenID %s isn't associated to current user logged in" %
- identity_url))
-
- msg = _("Account deleted.")
- redirect = reverse('index') + u"/?msg=%s" % (urlquote_plus(msg))
- return HttpResponseRedirect(redirect)
-
-
-def deleteopenid_failure(request, message):
- redirect_to = "%s?msg=%s" % (reverse('user_delete'), urlquote_plus(message))
- return HttpResponseRedirect(redirect_to)
-
-def external_legacy_login_info(request):
- return render('authopenid/external_legacy_login_info.html', context_instance=RequestContext(request))
-
-def sendpw(request):
- """
- send a new password to the user. It return a mail with
- a new pasword and a confirm link in. To activate the
- new password, the user should click on confirm link.
-
- url : /sendpw/
-
- templates : authopenid/sendpw_email.txt, authopenid/sendpw.html
- """
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
-
- msg = request.GET.get('msg','')
- if request.POST:
- form = EmailPasswordForm(request.POST)
- if form.is_valid():
- new_pw = User.objects.make_random_password()
- confirm_key = UserPasswordQueue.objects.get_new_confirm_key()
- try:
- uqueue = UserPasswordQueue.objects.get(
- user=form.user_cache
- )
- except:
- uqueue = UserPasswordQueue(
- user=form.user_cache
- )
- uqueue.new_password = new_pw
- uqueue.confirm_key = confirm_key
- uqueue.save()
- # send email
- subject = _("Request for new password")
- message_template = loader.get_template(
- 'authopenid/sendpw_email.txt')
- key_link = settings.APP_URL + reverse('user_confirmchangepw') + '?key=' + confirm_key
- message_context = Context({
- 'site_url': settings.APP_URL + reverse('index'),
- 'key_link': key_link,
- 'username': form.user_cache.username,
- 'password': new_pw,
- })
- message = message_template.render(message_context)
- send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
- [form.user_cache.email])
- msg = _("A new password and the activation link were sent to your email address.")
- else:
- form = EmailPasswordForm()
-
- return render('authopenid/sendpw.html', {
- 'form': form,
- 'msg': msg
- }, context_instance=RequestContext(request))
-
-
-def confirmchangepw(request):
- """
- view to set new password when the user click on confirm link
- in its mail. Basically it check if the confirm key exist, then
- replace old password with new password and remove confirm
- ley from the queue. Then it redirect the user to signin
- page.
-
- url : /sendpw/confirm/?key
-
- """
- confirm_key = request.GET.get('key', '')
- if not confirm_key:
- return HttpResponseRedirect(reverse('index'))
-
- try:
- uqueue = UserPasswordQueue.objects.get(
- confirm_key__exact=confirm_key
- )
- except:
- msg = _("Could not change password. Confirmation key '%s'\
- is not registered." % confirm_key)
- redirect = "%s?msg=%s" % (
- reverse('user_sendpw'), urlquote_plus(msg))
- return HttpResponseRedirect(redirect)
-
- try:
- user_ = User.objects.get(id=uqueue.user.id)
- except:
- msg = _("Can not change password. User don't exist anymore \
- in our database.")
- redirect = "%s?msg=%s" % (reverse('user_sendpw'),
- urlquote_plus(msg))
- return HttpResponseRedirect(redirect)
-
- user_.set_password(uqueue.new_password)
- user_.save()
- uqueue.delete()
- msg = _("Password changed for %s. You may now sign in." %
- user_.username)
- redirect = "%s?msg=%s" % (reverse('user_signin'),
- urlquote_plus(msg))
-
- return HttpResponseRedirect(redirect)
+# -*- coding: utf-8 -*-
+# Copyright (c) 2007, 2008, BenoƮt Chesneau
+# Copyright (c) 2007 Simon Willison, original work on django-openid
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# * notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# * notice, this list of conditions and the following disclaimer in the
+# * documentation and/or other materials provided with the
+# * distribution. Neither the name of the <ORGANIZATION> nor the names
+# * of its contributors may be used to endorse or promote products
+# * derived from this software without specific prior written
+# * permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from django.http import HttpResponseRedirect, get_host, Http404, \
+ HttpResponseServerError
+from django.shortcuts import render_to_response as render
+from django.template import RequestContext, loader, Context
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth import authenticate
+from django.core.urlresolvers import reverse
+from django.utils.encoding import smart_unicode
+from django.utils.html import escape
+from django.utils.translation import ugettext as _
+from django.utils.http import urlquote_plus
+from django.utils.safestring import mark_safe
+from django.core.mail import send_mail
+from django.views.defaults import server_error
+
+from openid.consumer.consumer import Consumer, \
+ SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
+from openid.consumer.discover import DiscoveryFailure
+from openid.extensions import sreg
+# needed for some linux distributions like debian
+try:
+ from openid.yadis import xri
+except ImportError:
+ from yadis import xri
+
+import re
+import urllib
+
+
+from forum.forms import EditUserEmailFeedsForm
+from django_authopenid.util import OpenID, DjangoOpenIDStore, from_openid_response, get_next_url
+from django_authopenid.models import UserAssociation, UserPasswordQueue, ExternalLoginData
+from django_authopenid.forms import OpenidSigninForm, ClassicLoginForm, OpenidRegisterForm, \
+ OpenidVerifyForm, ClassicRegisterForm, ChangePasswordForm, ChangeEmailForm, \
+ ChangeopenidForm, DeleteForm, EmailPasswordForm
+import external_login
+import logging
+
+def login(request,user):
+ from django.contrib.auth import login as _login
+ from forum.models import user_logged_in #custom signal
+
+ print 'in login call'
+
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ external_login.login(request,user)
+
+ #1) get old session key
+ session_key = request.session.session_key
+ #2) login and get new session key
+ _login(request,user)
+ #3) send signal with old session key as argument
+ user_logged_in.send(user=user,session_key=session_key,sender=None)
+
+def logout(request):
+ from django.contrib.auth import logout as _logout#for login I've added wrapper below - called login
+ _logout(request)
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ external_login.logout(request)
+
+def get_url_host(request):
+ if request.is_secure():
+ protocol = 'https'
+ else:
+ protocol = 'http'
+ host = escape(get_host(request))
+ return '%s://%s' % (protocol, host)
+
+def get_full_url(request):
+ return get_url_host(request) + request.get_full_path()
+
+def ask_openid(request, openid_url, redirect_to, on_failure=None,
+ sreg_request=None):
+ """ basic function to ask openid and return response """
+ request.encoding = 'UTF-8'
+ on_failure = on_failure or signin_failure
+
+ trust_root = getattr(
+ settings, 'OPENID_TRUST_ROOT', get_url_host(request) + '/'
+ )
+ if xri.identifierScheme(openid_url) == 'XRI' and getattr(
+ settings, 'OPENID_DISALLOW_INAMES', False
+ ):
+ msg = _("i-names are not supported")
+ return on_failure(request, msg)
+ consumer = Consumer(request.session, DjangoOpenIDStore())
+ try:
+ auth_request = consumer.begin(openid_url)
+ except DiscoveryFailure:
+ msg = _(u"OpenID %(openid_url)s is invalid" % {'openid_url':openid_url})
+ return on_failure(request, msg)
+
+ if sreg_request:
+ auth_request.addExtension(sreg_request)
+ redirect_url = auth_request.redirectURL(trust_root, redirect_to)
+ return HttpResponseRedirect(redirect_url)
+
+def complete(request, on_success=None, on_failure=None, return_to=None):
+ """ complete openid signin """
+ on_success = on_success or default_on_success
+ on_failure = on_failure or default_on_failure
+
+ consumer = Consumer(request.session, DjangoOpenIDStore())
+ # make sure params are encoded in utf8
+ params = dict((k,smart_unicode(v)) for k, v in request.GET.items())
+ openid_response = consumer.complete(params, return_to)
+
+ if openid_response.status == SUCCESS:
+ return on_success(request, openid_response.identity_url,
+ openid_response)
+ elif openid_response.status == CANCEL:
+ return on_failure(request, 'The request was canceled')
+ elif openid_response.status == FAILURE:
+ return on_failure(request, openid_response.message)
+ elif openid_response.status == SETUP_NEEDED:
+ return on_failure(request, 'Setup needed')
+ else:
+ assert False, "Bad openid status: %s" % openid_response.status
+
+def default_on_success(request, identity_url, openid_response):
+ """ default action on openid signin success """
+ request.session['openid'] = from_openid_response(openid_response)
+ return HttpResponseRedirect(get_next_url(request))
+
+def default_on_failure(request, message):
+ """ default failure action on signin """
+ return render('openid_failure.html', {
+ 'message': message
+ })
+
+
+def not_authenticated(func):
+ """ decorator that redirect user to next page if
+ he is already logged."""
+ def decorated(request, *args, **kwargs):
+ if request.user.is_authenticated():
+ return HttpResponseRedirect(get_next_url(request))
+ return func(request, *args, **kwargs)
+ return decorated
+
+@not_authenticated
+def signin(request,newquestion=False,newanswer=False):
+ """
+ signin page. It manages the legacy authentification (user/password)
+ and openid authentification
+
+ url: /signin/
+
+ template : authopenid/signin.htm
+ """
+ request.encoding = 'UTF-8'
+ on_failure = signin_failure
+ email_feeds_form = EditUserEmailFeedsForm()
+ next = get_next_url(request)
+ form_signin = OpenidSigninForm(initial={'next':next})
+ form_auth = ClassicLoginForm(initial={'next':next})
+
+ if request.POST:
+ #'blogin' - password login
+ if 'blogin' in request.POST.keys():
+ form_auth = ClassicLoginForm(request.POST)
+ if form_auth.is_valid():
+ #have login and password and need to login through external website
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ username = form_auth.cleaned_data['username']
+ password = form_auth.cleaned_data['password']
+ next = form_auth.cleaned_data['next']
+ if form_auth.get_user() == None:
+ #need to create internal user
+
+ #1) save login and password temporarily in session
+ request.session['external_username'] = username
+ request.session['external_password'] = password
+
+ #2) see if username clashes with some existing user
+ #if so, we have to prompt the user to pick a different name
+ username_taken = User.is_username_taken(username)
+ #try:
+ # User.objects.get(username=username)
+ # username_taken = True
+ #except User.DoesNotExist:
+ # username_taken = False
+
+ #3) try to extract user email from external service
+ email = external_login.get_email(username,password)
+
+ email_feeds_form = EditUserEmailFeedsForm()
+ form_data = {'username':username,'email':email,'next':next}
+ form = OpenidRegisterForm(initial=form_data)
+ template_data = {'form1':form,'username':username,\
+ 'email_feeds_form':email_feeds_form,\
+ 'provider':mark_safe(settings.EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME),\
+ 'login_type':'legacy',\
+ 'gravatar_faq_url':reverse('faq') + '#gravatar',\
+ 'external_login_name_is_taken':username_taken}
+ return render('authopenid/complete.html',template_data,\
+ context_instance=RequestContext(request))
+ else:
+ #user existed, external password is ok
+ user = form_auth.get_user()
+ login(request,user)
+ response = HttpResponseRedirect(get_next_url(request))
+ external_login.set_login_cookies(response,user)
+ return response
+ else:
+ #regular password authentication
+ user = form_auth.get_user()
+ login(request, user)
+ return HttpResponseRedirect(get_next_url(request))
+
+ elif 'bnewaccount' in request.POST.keys():
+ #register externally logged in password user with a new local account
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ form = OpenidRegisterForm(request.POST)
+ email_feeds_form = EditUserEmailFeedsForm(request.POST)
+ form1_is_valid = form.is_valid()
+ form2_is_valid = email_feeds_form.is_valid()
+ if form1_is_valid and form2_is_valid:
+ #create the user
+ username = form.cleaned_data['username']
+ password = request.session.get('external_password',None)
+ email = form.cleaned_data['email']
+ print 'got email addr %s' % email
+ if password and username:
+ User.objects.create_user(username,email,password)
+ user = authenticate(username=username,password=password)
+ external_username = request.session['external_username']
+ eld = ExternalLoginData.objects.get(external_username=external_username)
+ eld.user = user
+ eld.save()
+ login(request,user)
+ email_feeds_form.save(user)
+ del request.session['external_username']
+ del request.session['external_password']
+ return HttpResponseRedirect(reverse('index'))
+ else:
+ if password:
+ del request.session['external_username']
+ if username:
+ del request.session['external_password']
+ return HttpResponseServerError()
+ else:
+ username = request.POST.get('username',None)
+ provider = mark_safe(settings.EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME)
+ username_taken = User.is_username_taken(username)
+ data = {'login_type':'legacy','form1':form,'username':username,\
+ 'email_feeds_form':email_feeds_form,'provider':provider,\
+ 'gravatar_faq_url':reverse('faq') + '#gravatar',\
+ 'external_login_name_is_taken':username_taken}
+ return render('authopenid/complete.html',data,
+ context_instance=RequestContext(request))
+ else:
+ raise Http404
+
+ elif 'bsignin' in request.POST.keys() or 'openid_username' in request.POST.keys():
+ form_signin = OpenidSigninForm(request.POST)
+ if form_signin.is_valid():
+ next = form_signin.cleaned_data['next']
+ sreg_req = sreg.SRegRequest(optional=['nickname', 'email'])
+ redirect_to = "%s%s?%s" % (
+ get_url_host(request),
+ reverse('user_complete_signin'),
+ urllib.urlencode({'next':next})
+ )
+ return ask_openid(request,
+ form_signin.cleaned_data['openid_url'],
+ redirect_to,
+ on_failure=signin_failure,
+ sreg_request=sreg_req)
+
+
+ #if request is GET
+ question = None
+ if newquestion == True:
+ from forum.models import AnonymousQuestion as AQ
+ session_key = request.session.session_key
+ qlist = AQ.objects.filter(session_key=session_key).order_by('-added_at')
+ if len(qlist) > 0:
+ question = qlist[0]
+ answer = None
+ if newanswer == True:
+ from forum.models import AnonymousAnswer as AA
+ session_key = request.session.session_key
+ alist = AA.objects.filter(session_key=session_key).order_by('-added_at')
+ if len(alist) > 0:
+ answer = alist[0]
+
+ return render('authopenid/signin.html', {
+ 'question':question,
+ 'answer':answer,
+ 'form1': form_auth,
+ 'form2': form_signin,
+ 'msg': request.GET.get('msg',''),
+ 'sendpw_url': reverse('user_sendpw'),
+ 'fb_api_key': settings.FB_API_KEY,
+ }, context_instance=RequestContext(request))
+
+def complete_signin(request):
+ """ in case of complete signin with openid """
+ return complete(request, signin_success, signin_failure,
+ get_url_host(request) + reverse('user_complete_signin'))
+
+def signin_success(request, identity_url, openid_response):
+ """
+ openid signin success.
+
+ If the openid is already registered, the user is redirected to
+ url set par next or in settings with OPENID_REDIRECT_NEXT variable.
+ If none of these urls are set user is redirectd to /.
+
+ if openid isn't registered user is redirected to register page.
+ """
+
+ openid_ = from_openid_response(openid_response) #create janrain OpenID object
+ request.session['openid'] = openid_
+ try:
+ rel = UserAssociation.objects.get(openid_url__exact = str(openid_))
+ except:
+ # try to register this new user
+ return register(request)
+ user_ = rel.user
+ if user_.is_active:
+ user_.backend = "django.contrib.auth.backends.ModelBackend"
+ login(request, user_)
+
+ return HttpResponseRedirect(get_next_url(request))
+
+def is_association_exist(openid_url):
+ """ test if an openid is already in database """
+ is_exist = True
+ try:
+ uassoc = UserAssociation.objects.get(openid_url__exact = openid_url)
+ except:
+ is_exist = False
+ return is_exist
+
+@not_authenticated
+def register(request):
+ """
+ register an openid.
+
+ If user is already a member he can associate its openid with
+ its account.
+
+ A new account could also be created and automaticaly associated
+ to the openid.
+
+ url : /complete/
+
+ template : authopenid/complete.html
+ """
+
+ openid_ = request.session.get('openid', None)
+ next = get_next_url(request)
+ if not openid_:
+ return HttpResponseRedirect(reverse('user_signin') + '?next=%s' % next)
+
+ nickname = openid_.sreg.get('nickname', '')
+ email = openid_.sreg.get('email', '')
+ form1 = OpenidRegisterForm(initial={
+ 'next': next,
+ 'username': nickname,
+ 'email': email,
+ })
+ form2 = OpenidVerifyForm(initial={
+ 'next': next,
+ 'username': nickname,
+ })
+ email_feeds_form = EditUserEmailFeedsForm()
+
+ user_ = None
+ is_redirect = False
+ if request.POST:
+ if 'bnewaccount' in request.POST.keys():
+ form1 = OpenidRegisterForm(request.POST)
+ email_feeds_form = EditUserEmailFeedsForm(request.POST)
+ if form1.is_valid() and email_feeds_form.is_valid():
+ next = form1.cleaned_data['next']
+ is_redirect = True
+ tmp_pwd = User.objects.make_random_password()
+ user_ = User.objects.create_user(form1.cleaned_data['username'],
+ form1.cleaned_data['email'], tmp_pwd)
+
+ user_.set_unusable_password()
+ # make association with openid
+ uassoc = UserAssociation(openid_url=str(openid_),
+ user_id=user_.id)
+ uassoc.save()
+
+ # login
+ user_.backend = "django.contrib.auth.backends.ModelBackend"
+ login(request, user_)
+ email_feeds_form.save(user_)
+ elif 'bverify' in request.POST.keys():
+ form2 = OpenidVerifyForm(request.POST)
+ if form2.is_valid():
+ is_redirect = True
+ next = form2.cleaned_data['next']
+ user_ = form2.get_user()
+
+ uassoc = UserAssociation(openid_url=str(openid_),
+ user_id=user_.id)
+ uassoc.save()
+ login(request, user_)
+
+ #check if we need to post a question that was added anonymously
+ #this needs to be a function call becase this is also done
+ #if user just logged in and did not need to create the new account
+
+ if user_ != None:
+ if settings.EMAIL_VALIDATION == 'on':
+ send_new_email_key(user_,nomessage=True)
+ output = validation_email_sent(request)
+ set_email_validation_message(user_) #message set after generating view
+ return output
+ if user_.is_authenticated():
+ return HttpResponseRedirect(reverse('index'))
+ else:
+ raise Exception('openid login failed')#should not ever get here
+
+ openid_str = str(openid_)
+ bits = openid_str.split('/')
+ base_url = bits[2] #assume this is base url
+ url_bits = base_url.split('.')
+ provider_name = url_bits[-2].lower()
+
+ providers = {'yahoo':'<font color="purple">Yahoo!</font>',
+ 'flickr':'<font color="#0063dc">flick</font><font color="#ff0084">r</font>&trade;',
+ 'google':'Google&trade;',
+ 'aol':'<font color="#31658e">AOL</font>',
+ 'myopenid':'MyOpenID',
+ }
+ if provider_name not in providers:
+ provider_logo = provider_name
+ else:
+ provider_logo = providers[provider_name]
+
+ return render('authopenid/complete.html', {
+ 'form1': form1,
+ 'form2': form2,
+ 'email_feeds_form': email_feeds_form,
+ 'provider':mark_safe(provider_logo),
+ 'username': nickname,
+ 'email': email,
+ 'login_type':'openid',
+ 'gravatar_faq_url':reverse('faq') + '#gravatar',
+ }, context_instance=RequestContext(request))
+
+def signin_failure(request, message):
+ """
+ falure with openid signin. Go back to signin page.
+
+ template : "authopenid/signin.html"
+ """
+ next = get_next_url(request)
+ form_signin = OpenidSigninForm(initial={'next': next})
+ form_auth = ClassicLoginForm(initial={'next': next})
+
+ return render('authopenid/signin.html', {
+ 'msg': message,
+ 'form1': form_auth,
+ 'form2': form_signin,
+ }, context_instance=RequestContext(request))
+
+@not_authenticated
+def signup(request):
+ """
+ signup page. Create a legacy account
+
+ url : /signup/"
+
+ templates: authopenid/signup.html, authopenid/confirm_email.txt
+ """
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
+ next = get_next_url(request)
+ if request.POST:
+ form = ClassicRegisterForm(request.POST)
+ email_feeds_form = EditUserEmailFeedsForm(request.POST)
+
+ #validation outside if to remember form values
+ form1_is_valid = form.is_valid()
+ form2_is_valid = email_feeds_form.is_valid()
+ if form1_is_valid and form2_is_valid:
+ next = form.cleaned_data['next']
+ username = form.cleaned_data['username']
+ password = form.cleaned_data['password1']
+ email = form.cleaned_data['email']
+
+ user_ = User.objects.create_user( username,email,password )
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ external_login.create_user(username,email,password)
+
+ user_.backend = "django.contrib.auth.backends.ModelBackend"
+ login(request, user_)
+ email_feeds_form.save(user_)
+
+ # send email
+ subject = _("Welcome email subject line")
+ message_template = loader.get_template(
+ 'authopenid/confirm_email.txt'
+ )
+ message_context = Context({
+ 'signup_url': settings.APP_URL + reverse('user_signin'),
+ 'username': username,
+ 'password': password,
+ })
+ message = message_template.render(message_context)
+ send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
+ [user_.email])
+ return HttpResponseRedirect(next)
+ else:
+ form = ClassicRegisterForm(initial={'next':next})
+ email_feeds_form = EditUserEmailFeedsForm()
+ return render('authopenid/signup.html', {
+ 'form': form,
+ 'email_feeds_form': email_feeds_form
+ }, context_instance=RequestContext(request))
+ #what if request is not posted?
+
+@login_required
+def signout(request):
+ """
+ signout from the website. Remove openid from session and kill it.
+
+ url : /signout/"
+ """
+ try:
+ del request.session['openid']
+ except KeyError:
+ pass
+ logout(request)
+ return HttpResponseRedirect(get_next_url(request))
+
+def xrdf(request):
+ url_host = get_url_host(request)
+ return_to = [
+ "%s%s" % (url_host, reverse('user_complete_signin'))
+ ]
+ return render('authopenid/yadis.xrdf', {
+ 'return_to': return_to
+ }, context_instance=RequestContext(request))
+
+@login_required
+def account_settings(request):
+ """
+ index pages to changes some basic account settings :
+ - change password
+ - change email
+ - associate a new openid
+ - delete account
+
+ url : /
+
+ template : authopenid/settings.html
+ """
+ msg = request.GET.get('msg', '')
+ is_openid = True
+
+ try:
+ uassoc = UserAssociation.objects.get(
+ user__username__exact=request.user.username
+ )
+ except:
+ is_openid = False
+
+
+ return render('authopenid/settings.html', {
+ 'msg': msg,
+ 'is_openid': is_openid
+ }, context_instance=RequestContext(request))
+
+@login_required
+def changepw(request):
+ """
+ change password view.
+
+ url : /changepw/
+ template: authopenid/changepw.html
+ """
+ user_ = request.user
+
+ if user_.has_usable_password():
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
+ else:
+ raise Http404
+
+ if request.POST:
+ form = ChangePasswordForm(request.POST, user=user_)
+ if form.is_valid():
+ user_.set_password(form.cleaned_data['password1'])
+ user_.save()
+ msg = _("Password changed.")
+ redirect = "%s?msg=%s" % (
+ reverse('user_account_settings'),
+ urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+ else:
+ form = ChangePasswordForm(user=user_)
+
+ return render('authopenid/changepw.html', {'form': form },
+ context_instance=RequestContext(request))
+
+def find_email_validation_messages(user):
+ msg_text = _('your email needs to be validated see %(details_url)s') \
+ % {'details_url':reverse('faq') + '#validate'}
+ return user.message_set.filter(message__exact=msg_text)
+
+def set_email_validation_message(user):
+ messages = find_email_validation_messages(user)
+ msg_text = _('your email needs to be validated see %(details_url)s') \
+ % {'details_url':reverse('faq') + '#validate'}
+ if len(messages) == 0:
+ user.message_set.create(message=msg_text)
+
+def clear_email_validation_message(user):
+ messages = find_email_validation_messages(user)
+ messages.delete()
+
+def set_new_email(user, new_email, nomessage=False):
+ if new_email != user.email:
+ user.email = new_email
+ user.email_isvalid = False
+ user.save()
+ if settings.EMAIL_VALIDATION == 'on':
+ send_new_email_key(user,nomessage=nomessage)
+
+def _send_email_key(user):
+ """private function. sends email containing validation key
+ to user's email address
+ """
+ subject = _("Email verification subject line")
+ message_template = loader.get_template('authopenid/email_validation.txt')
+ import settings
+ message_context = Context({
+ 'validation_link': settings.APP_URL + reverse('user_verifyemail', kwargs={'id':user.id,'key':user.email_key})
+ })
+ message = message_template.render(message_context)
+ send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email])
+
+def send_new_email_key(user,nomessage=False):
+ import random
+ random.seed()
+ user.email_key = '%032x' % random.getrandbits(128)
+ user.save()
+ _send_email_key(user)
+ if nomessage==False:
+ set_email_validation_message(user)
+
+@login_required
+def send_email_key(request):
+ """
+ url = /email/sendkey/
+
+ view that is shown right after sending email key
+ email sending is called internally
+
+ raises 404 if email validation is off
+ if current email is valid shows 'key_not_sent' view of
+ authopenid/changeemail.html template
+ """
+
+ if settings.EMAIL_VALIDATION != 'off':
+ if request.user.email_isvalid:
+ return render('authopenid/changeemail.html',
+ { 'email': request.user.email,
+ 'action_type': 'key_not_sent',
+ 'change_link': reverse('user_changeemail')},
+ context_instance=RequestContext(request)
+ )
+ else:
+ send_new_email_key(request.user)
+ return validation_email_sent(request)
+ else:
+ raise Http404
+
+
+#internal server view used as return value by other views
+def validation_email_sent(request):
+ return render('authopenid/changeemail.html',
+ { 'email': request.user.email,
+ 'change_email_url': reverse('user_changeemail'),
+ 'action_type': 'validate', },
+ context_instance=RequestContext(request))
+
+def verifyemail(request,id=None,key=None):
+ """
+ view that is shown when user clicks email validation link
+ url = /email/verify/{{user.id}}/{{user.email_key}}/
+ """
+ if settings.EMAIL_VALIDATION != 'off':
+ user = User.objects.get(id=id)
+ if user:
+ if user.email_key == key:
+ user.email_isvalid = True
+ clear_email_validation_message(user)
+ user.save()
+ return render('authopenid/changeemail.html', {
+ 'action_type': 'validation_complete',
+ }, context_instance=RequestContext(request))
+ raise Http404
+
+@login_required
+def changeemail(request, action='change'):
+ """
+ changeemail view. requires openid with request type GET
+
+ url: /email/*
+
+ template : authopenid/changeemail.html
+ """
+ msg = request.GET.get('msg', None)
+ extension_args = {}
+ user_ = request.user
+
+ if request.POST:
+ if 'cancel' in request.POST:
+ msg = _('your email was not changed')
+ request.user.message_set.create(message=msg)
+ return HttpResponseRedirect(get_next_url(request))
+ form = ChangeEmailForm(request.POST, user=user_)
+ if form.is_valid():
+ new_email = form.cleaned_data['email']
+ if new_email != user_.email:
+ if settings.EMAIL_VALIDATION == 'on':
+ action = 'validate'
+ else:
+ action = 'done_novalidate'
+ set_new_email(user_, new_email,nomessage=True)
+ else:
+ action = 'keep'
+
+ elif not request.POST and 'openid.mode' in request.GET:
+ redirect_to = get_url_host(request) + reverse('user_changeemail')
+ return complete(request, emailopenid_success,
+ emailopenid_failure, redirect_to)
+ else:
+ form = ChangeEmailForm(initial={'email': user_.email},
+ user=user_)
+
+ output = render('authopenid/changeemail.html', {
+ 'form': form,
+ 'email': user_.email,
+ 'action_type': action,
+ 'gravatar_faq_url': reverse('faq') + '#gravatar',
+ 'change_email_url': reverse('user_changeemail'),
+ 'msg': msg
+ }, context_instance=RequestContext(request))
+
+ if action == 'validate':
+ set_email_validation_message(user_)
+
+ return output
+
+def emailopenid_success(request, identity_url, openid_response):
+ openid_ = from_openid_response(openid_response)
+
+ user_ = request.user
+ try:
+ uassoc = UserAssociation.objects.get(
+ openid_url__exact=identity_url
+ )
+ except:
+ return emailopenid_failure(request,
+ _("No OpenID %s found associated in our database" % identity_url))
+
+ if uassoc.user.username != request.user.username:
+ return emailopenid_failure(request,
+ _("The OpenID %s isn't associated to current user logged in" %
+ identity_url))
+
+ new_email = request.session.get('new_email', '')
+ if new_email:
+ user_.email = new_email
+ user_.save()
+ del request.session['new_email']
+ msg = _("Email Changed.")
+
+ redirect = "%s?msg=%s" % (reverse('user_account_settings'),
+ urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+
+
+def emailopenid_failure(request, message):
+ redirect_to = "%s?msg=%s" % (
+ reverse('user_changeemail'), urlquote_plus(message))
+ return HttpResponseRedirect(redirect_to)
+
+@login_required
+def changeopenid(request):
+ """
+ change openid view. Allow user to change openid
+ associated to its username.
+
+ url : /changeopenid/
+
+ template: authopenid/changeopenid.html
+ """
+
+ extension_args = {}
+ openid_url = ''
+ has_openid = True
+ msg = request.GET.get('msg', '')
+
+ user_ = request.user
+
+ try:
+ uopenid = UserAssociation.objects.get(user=user_)
+ openid_url = uopenid.openid_url
+ except:
+ has_openid = False
+
+ redirect_to = get_url_host(request) + reverse('user_changeopenid')
+ if request.POST and has_openid:
+ form = ChangeopenidForm(request.POST, user=user_)
+ if form.is_valid():
+ return ask_openid(request, form.cleaned_data['openid_url'],
+ redirect_to, on_failure=changeopenid_failure)
+ elif not request.POST and has_openid:
+ if 'openid.mode' in request.GET:
+ return complete(request, changeopenid_success,
+ changeopenid_failure, redirect_to)
+
+ form = ChangeopenidForm(initial={'openid_url': openid_url }, user=user_)
+ return render('authopenid/changeopenid.html', {
+ 'form': form,
+ 'has_openid': has_openid,
+ 'msg': msg
+ }, context_instance=RequestContext(request))
+
+def changeopenid_success(request, identity_url, openid_response):
+ openid_ = from_openid_response(openid_response)
+ is_exist = True
+ try:
+ uassoc = UserAssociation.objects.get(openid_url__exact=identity_url)
+ except:
+ is_exist = False
+
+ if not is_exist:
+ try:
+ uassoc = UserAssociation.objects.get(
+ user__username__exact=request.user.username
+ )
+ uassoc.openid_url = identity_url
+ uassoc.save()
+ except:
+ uassoc = UserAssociation(user=request.user,
+ openid_url=identity_url)
+ uassoc.save()
+ elif uassoc.user.username != request.user.username:
+ return changeopenid_failure(request,
+ _('This OpenID is already associated with another account.'))
+
+ request.session['openids'] = []
+ request.session['openids'].append(openid_)
+
+ msg = _("OpenID %s is now associated with your account." % identity_url)
+ redirect = "%s?msg=%s" % (
+ reverse('user_account_settings'),
+ urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+
+
+def changeopenid_failure(request, message):
+ redirect_to = "%s?msg=%s" % (
+ reverse('user_changeopenid'),
+ urlquote_plus(message))
+ return HttpResponseRedirect(redirect_to)
+
+@login_required
+def delete(request):
+ """
+ delete view. Allow user to delete its account. Password/openid are required to
+ confirm it. He should also check the confirm checkbox.
+
+ url : /delete
+
+ template : authopenid/delete.html
+ """
+
+ extension_args = {}
+
+ user_ = request.user
+
+ redirect_to = get_url_host(request) + reverse('user_delete')
+ if request.POST:
+ form = DeleteForm(request.POST, user=user_)
+ if form.is_valid():
+ if not form.test_openid:
+ user_.delete()
+ return signout(request)
+ else:
+ return ask_openid(request, form.cleaned_data['password'],
+ redirect_to, on_failure=deleteopenid_failure)
+ elif not request.POST and 'openid.mode' in request.GET:
+ return complete(request, deleteopenid_success, deleteopenid_failure,
+ redirect_to)
+
+ form = DeleteForm(user=user_)
+
+ msg = request.GET.get('msg','')
+ return render('authopenid/delete.html', {
+ 'form': form,
+ 'msg': msg,
+ }, context_instance=RequestContext(request))
+
+def deleteopenid_success(request, identity_url, openid_response):
+ openid_ = from_openid_response(openid_response)
+
+ user_ = request.user
+ try:
+ uassoc = UserAssociation.objects.get(
+ openid_url__exact=identity_url
+ )
+ except:
+ return deleteopenid_failure(request,
+ _("No OpenID %s found associated in our database" % identity_url))
+
+ if uassoc.user.username == user_.username:
+ user_.delete()
+ return signout(request)
+ else:
+ return deleteopenid_failure(request,
+ _("The OpenID %s isn't associated to current user logged in" %
+ identity_url))
+
+ msg = _("Account deleted.")
+ redirect = reverse('index') + u"/?msg=%s" % (urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+
+
+def deleteopenid_failure(request, message):
+ redirect_to = "%s?msg=%s" % (reverse('user_delete'), urlquote_plus(message))
+ return HttpResponseRedirect(redirect_to)
+
+def external_legacy_login_info(request):
+ return render('authopenid/external_legacy_login_info.html', context_instance=RequestContext(request))
+
+def sendpw(request):
+ """
+ send a new password to the user. It return a mail with
+ a new pasword and a confirm link in. To activate the
+ new password, the user should click on confirm link.
+
+ url : /sendpw/
+
+ templates : authopenid/sendpw_email.txt, authopenid/sendpw.html
+ """
+ if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
+
+ msg = request.GET.get('msg','')
+ if request.POST:
+ form = EmailPasswordForm(request.POST)
+ if form.is_valid():
+ new_pw = User.objects.make_random_password()
+ confirm_key = UserPasswordQueue.objects.get_new_confirm_key()
+ try:
+ uqueue = UserPasswordQueue.objects.get(
+ user=form.user_cache
+ )
+ except:
+ uqueue = UserPasswordQueue(
+ user=form.user_cache
+ )
+ uqueue.new_password = new_pw
+ uqueue.confirm_key = confirm_key
+ uqueue.save()
+ # send email
+ subject = _("Request for new password")
+ message_template = loader.get_template(
+ 'authopenid/sendpw_email.txt')
+ key_link = settings.APP_URL + reverse('user_confirmchangepw') + '?key=' + confirm_key
+ message_context = Context({
+ 'site_url': settings.APP_URL + reverse('index'),
+ 'key_link': key_link,
+ 'username': form.user_cache.username,
+ 'password': new_pw,
+ })
+ message = message_template.render(message_context)
+ send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
+ [form.user_cache.email])
+ msg = _("A new password and the activation link were sent to your email address.")
+ else:
+ form = EmailPasswordForm()
+
+ return render('authopenid/sendpw.html', {
+ 'form': form,
+ 'msg': msg
+ }, context_instance=RequestContext(request))
+
+
+def confirmchangepw(request):
+ """
+ view to set new password when the user click on confirm link
+ in its mail. Basically it check if the confirm key exist, then
+ replace old password with new password and remove confirm
+ ley from the queue. Then it redirect the user to signin
+ page.
+
+ url : /sendpw/confirm/?key
+
+ """
+ confirm_key = request.GET.get('key', '')
+ if not confirm_key:
+ return HttpResponseRedirect(reverse('index'))
+
+ try:
+ uqueue = UserPasswordQueue.objects.get(
+ confirm_key__exact=confirm_key
+ )
+ except:
+ msg = _("Could not change password. Confirmation key '%s'\
+ is not registered." % confirm_key)
+ redirect = "%s?msg=%s" % (
+ reverse('user_sendpw'), urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+
+ try:
+ user_ = User.objects.get(id=uqueue.user.id)
+ except:
+ msg = _("Can not change password. User don't exist anymore \
+ in our database.")
+ redirect = "%s?msg=%s" % (reverse('user_sendpw'),
+ urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+
+ user_.set_password(uqueue.new_password)
+ user_.save()
+ uqueue.delete()
+ msg = _("Password changed for %s. You may now sign in." %
+ user_.username)
+ redirect = "%s?msg=%s" % (reverse('user_signin'),
+ urlquote_plus(msg))
+
+ return HttpResponseRedirect(redirect)
diff --git a/dos2unix.sh b/dos2unix.sh
new file mode 100644
index 00000000..96a51c9d
--- /dev/null
+++ b/dos2unix.sh
@@ -0,0 +1,10 @@
+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/fbconnect/tests.py b/fbconnect/tests.py
index a6f218a9..2247054b 100755
--- a/fbconnect/tests.py
+++ b/fbconnect/tests.py
@@ -1,23 +1,23 @@
-"""
-This file demonstrates two different styles of tests (one doctest and one
-unittest). These will both pass when you run "manage.py test".
-
-Replace these with more appropriate tests for your application.
-"""
-
-from django.test import TestCase
-
-class SimpleTest(TestCase):
- def test_basic_addition(self):
- """
- Tests that 1 + 1 always equals 2.
- """
- self.failUnlessEqual(1 + 1, 2)
-
-__test__ = {"doctest": """
-Another way to test that 1 + 1 is equal to 2.
-
->>> 1 + 1 == 2
-True
-"""}
-
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/forum/forms.py b/forum/forms.py
index d727440e..2d2021b5 100644
--- a/forum/forms.py
+++ b/forum/forms.py
@@ -1,318 +1,318 @@
-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_authopenid.forms import NextUrlField, UserNameField
-import settings
-
-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 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}))
- #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)
- #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 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):
- 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)
- new_value = self.cleaned_data[form_field]
- if s.frequency != new_value:
- s.frequency = self.cleaned_data[form_field]
- 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
+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_authopenid.forms import NextUrlField, UserNameField
+import settings
+
+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 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}))
+ #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)
+ #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 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):
+ 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)
+ new_value = self.cleaned_data[form_field]
+ if s.frequency != new_value:
+ s.frequency = self.cleaned_data[form_field]
+ 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/managers.py b/forum/managers.py
index ba174998..b352ad37 100644
--- a/forum/managers.py
+++ b/forum/managers.py
@@ -1,241 +1,241 @@
-ļ»æimport datetime
-import time
-import logging
-from django.contrib.auth.models import User, UserManager
-from django.db import connection, models, transaction
-from django.db.models import Q
-from forum.models import *
-from urllib import quote, unquote
-
-class QuestionManager(models.Manager):
-
- def update_tags(self, question, tagnames, user):
- """
- Updates Tag associations for a question to match the given
- tagname string.
-
- Returns ``True`` if tag usage counts were updated as a result,
- ``False`` otherwise.
- """
- from forum.models import Tag
- current_tags = list(question.tags.all())
- current_tagnames = set(t.name for t in current_tags)
- updated_tagnames = set(t for t in tagnames.split(' ') if t)
- modified_tags = []
-
- removed_tags = [t for t in current_tags
- if t.name not in updated_tagnames]
- if removed_tags:
- modified_tags.extend(removed_tags)
- question.tags.remove(*removed_tags)
-
- added_tagnames = updated_tagnames - current_tagnames
- if added_tagnames:
- added_tags = Tag.objects.get_or_create_multiple(added_tagnames,
- user)
- modified_tags.extend(added_tags)
- question.tags.add(*added_tags)
-
- if modified_tags:
- Tag.objects.update_use_counts(modified_tags)
- return True
-
- return False
-
- def update_answer_count(self, question):
- """
- Executes an UPDATE query to update denormalised data with the
- number of answers the given question has.
- """
-
- # for some reasons, this Answer class failed to be imported,
- # although we have imported all classes from models on top.
- from forum.models import Answer
- self.filter(id=question.id).update(
- answer_count=Answer.objects.get_answers_from_question(question).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
- """
- from forum.models import FavoriteQuestion
- self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count())
-
- def get_similar_questions(self, question):
- """
- Get 10 similar questions for given one.
- This will search the same tag list for give question(by exactly same string) first.
- Questions with the individual tags will be added to list if above questions are not full.
- """
- #print datetime.datetime.now()
- 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 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=0'
- ') '
- 'WHERE id IN (%s)')
-
- def get_valid_tags(self, page_size):
- from forum.models import Tag
- tags = Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-id")[:page_size]
- return tags
-
- def get_or_create_multiple(self, names, user):
- """
- Fetches a list of Tags with the given names, creating any Tags
- which don't exist when necesssary.
- """
- tags = list(self.filter(name__in=names))
- #Set all these tag visible
- for tag in tags:
- if tag.deleted:
- tag.deleted = False
- tag.deleted_by = None
- tag.deleted_at = None
- tag.save()
-
- if len(tags) < len(names):
- existing_names = set(tag.name for tag in tags)
- new_names = [name for name in names if name not in existing_names]
- tags.extend([self.create(name=name, created_by=user)
- for name in new_names if self.filter(name=name).count() == 0 and len(name.strip()) > 0])
-
- return tags
-
- def update_use_counts(self, tags):
- """Updates the given Tags with their current use counts."""
- if not tags:
- return
- cursor = connection.cursor()
- query = self.UPDATE_USED_COUNTS_QUERY % ','.join(['%s'] * len(tags))
- cursor.execute(query, [tag.id for tag in tags])
- transaction.commit_unless_managed()
-
- def get_tags_by_questions(self, questions):
- question_ids = []
- for question in questions:
- question_ids.append(question.id)
-
- question_ids_str = ','.join([str(id) for id in question_ids])
- related_tags = self.extra(
- tables=['tag', 'question_tags'],
- where=["tag.id = question_tags.tag_id AND question_tags.question_id IN (" + question_ids_str + ")"]
- ).distinct()
-
- return related_tags
-
-class AnswerManager(models.Manager):
- GET_ANSWERS_FROM_USER_QUESTIONS = u'SELECT answer.* FROM answer INNER JOIN question ON answer.question_id = question.id WHERE question.author_id =%s AND answer.author_id <> %s'
- def get_answers_from_question(self, question, user=None):
- """
- Retrieves visibile answers for the given question. Delete answers
- are only visibile to the person who deleted them.
- """
-
- if user is None or not user.is_authenticated():
- return self.filter(question=question, deleted=False)
- else:
- return self.filter(Q(question=question),
- Q(deleted=False) | Q(deleted_by=user))
-
- def get_answers_from_questions(self, user_id):
- """
- Retrieves visibile answers for the given question. Which are not included own answers
- """
- cursor = connection.cursor()
- cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id])
- return cursor.fetchall()
-
-class VoteManager(models.Manager):
- COUNT_UP_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = 1"
- COUNT_DOWN_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = -1"
- COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = %s"
- def get_up_vote_count_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_UP_VOTE_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
- else:
- return 0
-
- def get_down_vote_count_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_DOWN_VOTE_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
- else:
- return 0
-
- def get_votes_count_today_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-
-class FlaggedItemManager(models.Manager):
- COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = %s"
- def get_flagged_items_count_today(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-
-class ReputeManager(models.Manager):
- COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = %s"
- def get_reputation_by_upvoted_today(self, user):
- """
- For one user in one day, he can only earn rep till certain score (ep. +200)
- by upvoted(also substracted from upvoted canceled). This is because we need
- to prohibit gaming system by upvoting/cancel again and again.
- """
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-class AwardManager(models.Manager):
- def get_recent_awards(self):
- awards = super(AwardManager, self).extra(
- select={'badge_id': 'badge.id', 'badge_name':'badge.name',
- 'badge_description': 'badge.description', 'badge_type': 'badge.type',
- 'user_id': 'auth_user.id', 'user_name': 'auth_user.username'
- },
- tables=['award', 'badge', 'auth_user'],
- order_by=['-awarded_at'],
- where=['auth_user.id=award.user_id AND badge_id=badge.id'],
- ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name')
- return awards
+ļ»æimport datetime
+import time
+import logging
+from django.contrib.auth.models import User, UserManager
+from django.db import connection, models, transaction
+from django.db.models import Q
+from forum.models import *
+from urllib import quote, unquote
+
+class QuestionManager(models.Manager):
+
+ def update_tags(self, question, tagnames, user):
+ """
+ Updates Tag associations for a question to match the given
+ tagname string.
+
+ Returns ``True`` if tag usage counts were updated as a result,
+ ``False`` otherwise.
+ """
+ from forum.models import Tag
+ current_tags = list(question.tags.all())
+ current_tagnames = set(t.name for t in current_tags)
+ updated_tagnames = set(t for t in tagnames.split(' ') if t)
+ modified_tags = []
+
+ removed_tags = [t for t in current_tags
+ if t.name not in updated_tagnames]
+ if removed_tags:
+ modified_tags.extend(removed_tags)
+ question.tags.remove(*removed_tags)
+
+ added_tagnames = updated_tagnames - current_tagnames
+ if added_tagnames:
+ added_tags = Tag.objects.get_or_create_multiple(added_tagnames,
+ user)
+ modified_tags.extend(added_tags)
+ question.tags.add(*added_tags)
+
+ if modified_tags:
+ Tag.objects.update_use_counts(modified_tags)
+ return True
+
+ return False
+
+ def update_answer_count(self, question):
+ """
+ Executes an UPDATE query to update denormalised data with the
+ number of answers the given question has.
+ """
+
+ # for some reasons, this Answer class failed to be imported,
+ # although we have imported all classes from models on top.
+ from forum.models import Answer
+ self.filter(id=question.id).update(
+ answer_count=Answer.objects.get_answers_from_question(question).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
+ """
+ from forum.models import FavoriteQuestion
+ self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count())
+
+ def get_similar_questions(self, question):
+ """
+ Get 10 similar questions for given one.
+ This will search the same tag list for give question(by exactly same string) first.
+ Questions with the individual tags will be added to list if above questions are not full.
+ """
+ #print datetime.datetime.now()
+ 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 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=0'
+ ') '
+ 'WHERE id IN (%s)')
+
+ def get_valid_tags(self, page_size):
+ from forum.models import Tag
+ tags = Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-id")[:page_size]
+ return tags
+
+ def get_or_create_multiple(self, names, user):
+ """
+ Fetches a list of Tags with the given names, creating any Tags
+ which don't exist when necesssary.
+ """
+ tags = list(self.filter(name__in=names))
+ #Set all these tag visible
+ for tag in tags:
+ if tag.deleted:
+ tag.deleted = False
+ tag.deleted_by = None
+ tag.deleted_at = None
+ tag.save()
+
+ if len(tags) < len(names):
+ existing_names = set(tag.name for tag in tags)
+ new_names = [name for name in names if name not in existing_names]
+ tags.extend([self.create(name=name, created_by=user)
+ for name in new_names if self.filter(name=name).count() == 0 and len(name.strip()) > 0])
+
+ return tags
+
+ def update_use_counts(self, tags):
+ """Updates the given Tags with their current use counts."""
+ if not tags:
+ return
+ cursor = connection.cursor()
+ query = self.UPDATE_USED_COUNTS_QUERY % ','.join(['%s'] * len(tags))
+ cursor.execute(query, [tag.id for tag in tags])
+ transaction.commit_unless_managed()
+
+ def get_tags_by_questions(self, questions):
+ question_ids = []
+ for question in questions:
+ question_ids.append(question.id)
+
+ question_ids_str = ','.join([str(id) for id in question_ids])
+ related_tags = self.extra(
+ tables=['tag', 'question_tags'],
+ where=["tag.id = question_tags.tag_id AND question_tags.question_id IN (" + question_ids_str + ")"]
+ ).distinct()
+
+ return related_tags
+
+class AnswerManager(models.Manager):
+ GET_ANSWERS_FROM_USER_QUESTIONS = u'SELECT answer.* FROM answer INNER JOIN question ON answer.question_id = question.id WHERE question.author_id =%s AND answer.author_id <> %s'
+ def get_answers_from_question(self, question, user=None):
+ """
+ Retrieves visibile answers for the given question. Delete answers
+ are only visibile to the person who deleted them.
+ """
+
+ if user is None or not user.is_authenticated():
+ return self.filter(question=question, deleted=False)
+ else:
+ return self.filter(Q(question=question),
+ Q(deleted=False) | Q(deleted_by=user))
+
+ def get_answers_from_questions(self, user_id):
+ """
+ Retrieves visibile answers for the given question. Which are not included own answers
+ """
+ cursor = connection.cursor()
+ cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id])
+ return cursor.fetchall()
+
+class VoteManager(models.Manager):
+ COUNT_UP_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = 1"
+ COUNT_DOWN_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = -1"
+ COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = %s"
+ def get_up_vote_count_from_user(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_UP_VOTE_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+ else:
+ return 0
+
+ def get_down_vote_count_from_user(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_DOWN_VOTE_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+ else:
+ return 0
+
+ def get_votes_count_today_from_user(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())])
+ row = cursor.fetchone()
+ return row[0]
+
+ else:
+ return 0
+
+class FlaggedItemManager(models.Manager):
+ COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = %s"
+ def get_flagged_items_count_today(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())])
+ row = cursor.fetchone()
+ return row[0]
+
+ else:
+ return 0
+
+class ReputeManager(models.Manager):
+ COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = %s"
+ def get_reputation_by_upvoted_today(self, user):
+ """
+ For one user in one day, he can only earn rep till certain score (ep. +200)
+ by upvoted(also substracted from upvoted canceled). This is because we need
+ to prohibit gaming system by upvoting/cancel again and again.
+ """
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())])
+ row = cursor.fetchone()
+ return row[0]
+
+ else:
+ return 0
+class AwardManager(models.Manager):
+ def get_recent_awards(self):
+ awards = super(AwardManager, self).extra(
+ select={'badge_id': 'badge.id', 'badge_name':'badge.name',
+ 'badge_description': 'badge.description', 'badge_type': 'badge.type',
+ 'user_id': 'auth_user.id', 'user_name': 'auth_user.username'
+ },
+ tables=['award', 'badge', 'auth_user'],
+ order_by=['-awarded_at'],
+ where=['auth_user.id=award.user_id AND badge_id=badge.id'],
+ ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name')
+ return awards
diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py
index 938d853f..4f79e497 100644
--- a/forum/templatetags/extra_tags.py
+++ b/forum/templatetags/extra_tags.py
@@ -1,351 +1,351 @@
-import time
-import os
-import posixpath
-import datetime
-import math
-import re
-import logging
-from django import template
-from django.utils.encoding import smart_unicode
-from django.utils.safestring import mark_safe
-from forum.const import *
-from forum.models import Question, Answer, QuestionRevision, AnswerRevision
-from django.utils.translation import ugettext as _
-from django.utils.translation import ungettext
-from django.conf import settings
-
-register = template.Library()
-
-GRAVATAR_TEMPLATE = ('<img class="gravatar" width="%(size)s" height="%(size)s" '
- 'src="http://www.gravatar.com/avatar/%(gravatar_hash)s'
- '?s=%(size)s&amp;d=identicon&amp;r=PG" '
- 'alt="%(username)s\'s gravatar image" />')
-
-@register.simple_tag
-def gravatar(user, size):
- """
- Creates an ``<img>`` for a user's Gravatar with a given size.
-
- This tag can accept a User object, or a dict containing the
- appropriate values.
- """
- try:
- gravatar = user['gravatar']
- username = user['username']
- except (TypeError, AttributeError, KeyError):
- gravatar = user.gravatar
- username = user.username
- return mark_safe(GRAVATAR_TEMPLATE % {
- 'size': size,
- 'gravatar_hash': gravatar,
- 'username': template.defaultfilters.urlencode(username),
- })
-
-MAX_FONTSIZE = 18
-MIN_FONTSIZE = 12
-@register.simple_tag
-def tag_font_size(max_size, min_size, current_size):
- """
- do a logarithmic mapping calcuation for a proper size for tagging cloud
- Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/
- """
- #avoid invalid calculation
- if current_size == 0:
- current_size = 1
- try:
- weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size))
- except:
- weight = 0
- return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight)
-
-
-LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5
-LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 4
-NUM_PAGES_OUTSIDE_RANGE = 1
-ADJACENT_PAGES = 2
-@register.inclusion_tag("paginator.html")
-def cnprog_paginator(context):
- """
- custom paginator tag
- Inspired from http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/
- """
- if (context["is_paginated"]):
- " Initialize variables "
- in_leading_range = in_trailing_range = False
- pages_outside_leading_range = pages_outside_trailing_range = range(0)
-
- if (context["pages"] <= LEADING_PAGE_RANGE_DISPLAYED):
- in_leading_range = in_trailing_range = True
- page_numbers = [n for n in range(1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
- elif (context["page"] <= LEADING_PAGE_RANGE):
- in_leading_range = True
- page_numbers = [n for n in range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) if n > 0 and n <= context["pages"]]
- pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
- elif (context["page"] > context["pages"] - TRAILING_PAGE_RANGE):
- in_trailing_range = True
- page_numbers = [n for n in range(context["pages"] - TRAILING_PAGE_RANGE_DISPLAYED + 1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
- pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
- else:
- page_numbers = [n for n in range(context["page"] - ADJACENT_PAGES, context["page"] + ADJACENT_PAGES + 1) if n > 0 and n <= context["pages"]]
- pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
- pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
-
- extend_url = context.get('extend_url', '')
- return {
- "base_url": context["base_url"],
- "is_paginated": context["is_paginated"],
- "previous": context["previous"],
- "has_previous": context["has_previous"],
- "next": context["next"],
- "has_next": context["has_next"],
- "page": context["page"],
- "pages": context["pages"],
- "page_numbers": page_numbers,
- "in_leading_range" : in_leading_range,
- "in_trailing_range" : in_trailing_range,
- "pages_outside_leading_range": pages_outside_leading_range,
- "pages_outside_trailing_range": pages_outside_trailing_range,
- "extend_url" : extend_url
- }
-
-@register.inclusion_tag("pagesize.html")
-def cnprog_pagesize(context):
- """
- display the pagesize selection boxes for paginator
- """
- if (context["is_paginated"]):
- return {
- "base_url": context["base_url"],
- "pagesize" : context["pagesize"],
- "is_paginated": context["is_paginated"]
- }
-
-@register.inclusion_tag("post_contributor_info.html")
-def post_contributor_info(post,contributor_type='original_author'):
- """contributor_type: original_author|last_updater
- """
- if isinstance(post,Question):
- post_type = 'question'
- elif isinstance(post,Answer):
- post_type = 'answer'
- elif isinstance(post,AnswerRevision) or isinstance(post,QuestionRevision):
- post_type = 'revision'
- return {
- 'post':post,
- 'post_type':post_type,
- 'wiki_on':settings.WIKI_ON,
- 'contributor_type':contributor_type
- }
-
-@register.simple_tag
-def get_score_badge(user):
- BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(reputation)s</span>'
- if user.gold > 0 :
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">'
- '<span class="badge1">&#9679;</span>'
- '<span class="badgecount">%(gold)s</span>'
- '</span>')
- if user.silver > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">'
- '<span class="silver">&#9679;</span>'
- '<span class="badgecount">%(silver)s</span>'
- '</span>')
- if user.bronze > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">'
- '<span class="bronze">&#9679;</span>'
- '<span class="badgecount">%(bronze)s</span>'
- '</span>')
- BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
- return mark_safe(BADGE_TEMPLATE % {
- 'reputation' : user.reputation,
- 'gold' : user.gold,
- 'silver' : user.silver,
- 'bronze' : user.bronze,
- 'badgesword' : _('badges'),
- 'reputationword' : _('reputation points'),
- })
-
-@register.simple_tag
-def get_score_badge_by_details(rep, gold, silver, bronze):
- BADGE_TEMPLATE = '<span class="reputation-score" title="%(reputation)s %(repword)s">%(reputation)s</span>'
- if gold > 0 :
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgeword)s">'
- '<span class="badge1">&#9679;</span>'
- '<span class="badgecount">%(gold)s</span>'
- '</span>')
- if silver > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgeword)s">'
- '<span class="badge2">&#9679;</span>'
- '<span class="badgecount">%(silver)s</span>'
- '</span>')
- if bronze > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgeword)s">'
- '<span class="badge3">&#9679;</span>'
- '<span class="badgecount">%(bronze)s</span>'
- '</span>')
- BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
- return mark_safe(BADGE_TEMPLATE % {
- 'reputation' : rep,
- 'gold' : gold,
- 'silver' : silver,
- 'bronze' : bronze,
- 'repword' : _('reputation points'),
- 'badgeword' : _('badges'),
- })
-
-@register.simple_tag
-def get_user_vote_image(dic, key, arrow):
- if dic.has_key(key):
- if int(dic[key]) == int(arrow):
- return '-on'
- return ''
-
-@register.simple_tag
-def get_age(birthday):
- current_time = datetime.datetime(*time.localtime()[0:6])
- year = birthday.year
- month = birthday.month
- day = birthday.day
- diff = current_time - datetime.datetime(year,month,day,0,0,0)
- return diff.days / 365
-
-@register.simple_tag
-def get_total_count(up_count, down_count):
- return up_count + down_count
-
-@register.simple_tag
-def format_number(value):
- strValue = str(value)
- if len(strValue) <= 3:
- return strValue
- result = ''
- first = ''
- pattern = re.compile('(-?\d+)(\d{3})')
- m = re.match(pattern, strValue)
- while m != None:
- first = m.group(1)
- second = m.group(2)
- result = ',' + second + result
- strValue = first + ',' + second
- m = re.match(pattern, strValue)
- return first + result
-
-@register.simple_tag
-def convert2tagname_list(question):
- question['tagnames'] = [name for name in question['tagnames'].split(u' ')]
- return ''
-
-@register.simple_tag
-def diff_date(date, limen=2):
- now = datetime.datetime.now()#datetime(*time.localtime()[0:6])#???
- diff = now - date
- days = diff.days
- hours = int(diff.seconds/3600)
- minutes = int(diff.seconds/60)
-
- if days > 2:
- if date.year == now.year:
- return date.strftime("%b %d at %H:%M")
- else:
- return date.strftime("%b %d '%y at %H:%M")
- elif days == 2:
- return _('2 days ago')
- elif days == 1:
- return _('yesterday')
- elif minutes >= 60:
- return ungettext('%(hr)d hour ago','%(hr)d hours ago',hours) % {'hr':hours}
- else:
- return ungettext('%(min)d min ago','%(min)d mins ago',minutes) % {'min':minutes}
-
-@register.simple_tag
-def get_latest_changed_timestamp():
- try:
- from time import localtime, strftime
- from os import path
- root = settings.SITE_SRC_ROOT
- dir = (
- root,
- '%s/forum' % root,
- '%s/templates' % root,
- )
- stamp = (path.getmtime(d) for d in dir)
- latest = max(stamp)
- timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest))
- except:
- timestr = ''
- return timestr
-
-@register.simple_tag
-def href(url):
- url = '///' + settings.FORUM_SCRIPT_ALIAS + '/' + url
- return posixpath.normpath(url) + '?v=%d' % settings.RESOURCE_REVISION
-
-class ItemSeparatorNode(template.Node):
- def __init__(self,separator):
- sep = separator.strip()
- if sep[0] == sep[-1] and sep[0] in ('\'','"'):
- sep = sep[1:-1]
- else:
- raise template.TemplateSyntaxError('separator in joinitems tag must be quoted')
- self.content = sep
- def render(self,context):
- return self.content
-
-class JoinItemListNode(template.Node):
- def __init__(self,separator=ItemSeparatorNode("''"), items=()):
- self.separator = separator
- self.items = items
- def render(self,context):
- out = []
- empty_re = re.compile(r'^\s*$')
- for item in self.items:
- bit = item.render(context)
- if not empty_re.search(bit):
- out.append(bit)
- return self.separator.render(context).join(out)
-
-@register.tag(name="joinitems")
-def joinitems(parser,token):
- try:
- tagname,junk,sep_token = token.split_contents()
- except ValueError:
- raise template.TemplateSyntaxError("joinitems tag requires 'using \"separator html\"' parameters")
- if junk == 'using':
- sep_node = ItemSeparatorNode(sep_token)
- else:
- raise template.TemplateSyntaxError("joinitems tag requires 'using \"separator html\"' parameters")
- nodelist = []
- while True:
- nodelist.append(parser.parse(('separator','endjoinitems')))
- next = parser.next_token()
- if next.contents == 'endjoinitems':
- break
-
- return JoinItemListNode(separator=sep_node,items=nodelist)
-
-class BlockResourceNode(template.Node):
- def __init__(self,nodelist):
- self.items = nodelist
- def render(self,context):
- out = '///' + settings.FORUM_SCRIPT_ALIAS
- if self.items:
- out += '/'
- for item in self.items:
- bit = item.render(context)
- out += bit
- out = os.path.normpath(out) + '?v=%d' % settings.RESOURCE_REVISION
- return out.replace(' ','')
-
-@register.tag(name='blockresource')
-def blockresource(parser,token):
- try:
- tagname = token.split_contents()
- except ValueError:
- raise template.TemplateSyntaxError("blockresource tag does not use arguments")
- nodelist = []
- while True:
- nodelist.append(parser.parse(('endblockresource')))
- next = parser.next_token()
- if next.contents == 'endblockresource':
- break
- return BlockResourceNode(nodelist)
+import time
+import os
+import posixpath
+import datetime
+import math
+import re
+import logging
+from django import template
+from django.utils.encoding import smart_unicode
+from django.utils.safestring import mark_safe
+from forum.const import *
+from forum.models import Question, Answer, QuestionRevision, AnswerRevision
+from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext
+from django.conf import settings
+
+register = template.Library()
+
+GRAVATAR_TEMPLATE = ('<img class="gravatar" width="%(size)s" height="%(size)s" '
+ 'src="http://www.gravatar.com/avatar/%(gravatar_hash)s'
+ '?s=%(size)s&amp;d=identicon&amp;r=PG" '
+ 'alt="%(username)s\'s gravatar image" />')
+
+@register.simple_tag
+def gravatar(user, size):
+ """
+ Creates an ``<img>`` for a user's Gravatar with a given size.
+
+ This tag can accept a User object, or a dict containing the
+ appropriate values.
+ """
+ try:
+ gravatar = user['gravatar']
+ username = user['username']
+ except (TypeError, AttributeError, KeyError):
+ gravatar = user.gravatar
+ username = user.username
+ return mark_safe(GRAVATAR_TEMPLATE % {
+ 'size': size,
+ 'gravatar_hash': gravatar,
+ 'username': template.defaultfilters.urlencode(username),
+ })
+
+MAX_FONTSIZE = 18
+MIN_FONTSIZE = 12
+@register.simple_tag
+def tag_font_size(max_size, min_size, current_size):
+ """
+ do a logarithmic mapping calcuation for a proper size for tagging cloud
+ Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/
+ """
+ #avoid invalid calculation
+ if current_size == 0:
+ current_size = 1
+ try:
+ weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size))
+ except:
+ weight = 0
+ return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight)
+
+
+LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5
+LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 4
+NUM_PAGES_OUTSIDE_RANGE = 1
+ADJACENT_PAGES = 2
+@register.inclusion_tag("paginator.html")
+def cnprog_paginator(context):
+ """
+ custom paginator tag
+ Inspired from http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/
+ """
+ if (context["is_paginated"]):
+ " Initialize variables "
+ in_leading_range = in_trailing_range = False
+ pages_outside_leading_range = pages_outside_trailing_range = range(0)
+
+ if (context["pages"] <= LEADING_PAGE_RANGE_DISPLAYED):
+ in_leading_range = in_trailing_range = True
+ page_numbers = [n for n in range(1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
+ elif (context["page"] <= LEADING_PAGE_RANGE):
+ in_leading_range = True
+ page_numbers = [n for n in range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) if n > 0 and n <= context["pages"]]
+ pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
+ elif (context["page"] > context["pages"] - TRAILING_PAGE_RANGE):
+ in_trailing_range = True
+ page_numbers = [n for n in range(context["pages"] - TRAILING_PAGE_RANGE_DISPLAYED + 1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
+ pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
+ else:
+ page_numbers = [n for n in range(context["page"] - ADJACENT_PAGES, context["page"] + ADJACENT_PAGES + 1) if n > 0 and n <= context["pages"]]
+ pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
+ pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
+
+ extend_url = context.get('extend_url', '')
+ return {
+ "base_url": context["base_url"],
+ "is_paginated": context["is_paginated"],
+ "previous": context["previous"],
+ "has_previous": context["has_previous"],
+ "next": context["next"],
+ "has_next": context["has_next"],
+ "page": context["page"],
+ "pages": context["pages"],
+ "page_numbers": page_numbers,
+ "in_leading_range" : in_leading_range,
+ "in_trailing_range" : in_trailing_range,
+ "pages_outside_leading_range": pages_outside_leading_range,
+ "pages_outside_trailing_range": pages_outside_trailing_range,
+ "extend_url" : extend_url
+ }
+
+@register.inclusion_tag("pagesize.html")
+def cnprog_pagesize(context):
+ """
+ display the pagesize selection boxes for paginator
+ """
+ if (context["is_paginated"]):
+ return {
+ "base_url": context["base_url"],
+ "pagesize" : context["pagesize"],
+ "is_paginated": context["is_paginated"]
+ }
+
+@register.inclusion_tag("post_contributor_info.html")
+def post_contributor_info(post,contributor_type='original_author'):
+ """contributor_type: original_author|last_updater
+ """
+ if isinstance(post,Question):
+ post_type = 'question'
+ elif isinstance(post,Answer):
+ post_type = 'answer'
+ elif isinstance(post,AnswerRevision) or isinstance(post,QuestionRevision):
+ post_type = 'revision'
+ return {
+ 'post':post,
+ 'post_type':post_type,
+ 'wiki_on':settings.WIKI_ON,
+ 'contributor_type':contributor_type
+ }
+
+@register.simple_tag
+def get_score_badge(user):
+ BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(reputation)s</span>'
+ if user.gold > 0 :
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">'
+ '<span class="badge1">&#9679;</span>'
+ '<span class="badgecount">%(gold)s</span>'
+ '</span>')
+ if user.silver > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">'
+ '<span class="silver">&#9679;</span>'
+ '<span class="badgecount">%(silver)s</span>'
+ '</span>')
+ if user.bronze > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">'
+ '<span class="bronze">&#9679;</span>'
+ '<span class="badgecount">%(bronze)s</span>'
+ '</span>')
+ BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
+ return mark_safe(BADGE_TEMPLATE % {
+ 'reputation' : user.reputation,
+ 'gold' : user.gold,
+ 'silver' : user.silver,
+ 'bronze' : user.bronze,
+ 'badgesword' : _('badges'),
+ 'reputationword' : _('reputation points'),
+ })
+
+@register.simple_tag
+def get_score_badge_by_details(rep, gold, silver, bronze):
+ BADGE_TEMPLATE = '<span class="reputation-score" title="%(reputation)s %(repword)s">%(reputation)s</span>'
+ if gold > 0 :
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgeword)s">'
+ '<span class="badge1">&#9679;</span>'
+ '<span class="badgecount">%(gold)s</span>'
+ '</span>')
+ if silver > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgeword)s">'
+ '<span class="badge2">&#9679;</span>'
+ '<span class="badgecount">%(silver)s</span>'
+ '</span>')
+ if bronze > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgeword)s">'
+ '<span class="badge3">&#9679;</span>'
+ '<span class="badgecount">%(bronze)s</span>'
+ '</span>')
+ BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
+ return mark_safe(BADGE_TEMPLATE % {
+ 'reputation' : rep,
+ 'gold' : gold,
+ 'silver' : silver,
+ 'bronze' : bronze,
+ 'repword' : _('reputation points'),
+ 'badgeword' : _('badges'),
+ })
+
+@register.simple_tag
+def get_user_vote_image(dic, key, arrow):
+ if dic.has_key(key):
+ if int(dic[key]) == int(arrow):
+ return '-on'
+ return ''
+
+@register.simple_tag
+def get_age(birthday):
+ current_time = datetime.datetime(*time.localtime()[0:6])
+ year = birthday.year
+ month = birthday.month
+ day = birthday.day
+ diff = current_time - datetime.datetime(year,month,day,0,0,0)
+ return diff.days / 365
+
+@register.simple_tag
+def get_total_count(up_count, down_count):
+ return up_count + down_count
+
+@register.simple_tag
+def format_number(value):
+ strValue = str(value)
+ if len(strValue) <= 3:
+ return strValue
+ result = ''
+ first = ''
+ pattern = re.compile('(-?\d+)(\d{3})')
+ m = re.match(pattern, strValue)
+ while m != None:
+ first = m.group(1)
+ second = m.group(2)
+ result = ',' + second + result
+ strValue = first + ',' + second
+ m = re.match(pattern, strValue)
+ return first + result
+
+@register.simple_tag
+def convert2tagname_list(question):
+ question['tagnames'] = [name for name in question['tagnames'].split(u' ')]
+ return ''
+
+@register.simple_tag
+def diff_date(date, limen=2):
+ now = datetime.datetime.now()#datetime(*time.localtime()[0:6])#???
+ diff = now - date
+ days = diff.days
+ hours = int(diff.seconds/3600)
+ minutes = int(diff.seconds/60)
+
+ if days > 2:
+ if date.year == now.year:
+ return date.strftime("%b %d at %H:%M")
+ else:
+ return date.strftime("%b %d '%y at %H:%M")
+ elif days == 2:
+ return _('2 days ago')
+ elif days == 1:
+ return _('yesterday')
+ elif minutes >= 60:
+ return ungettext('%(hr)d hour ago','%(hr)d hours ago',hours) % {'hr':hours}
+ else:
+ return ungettext('%(min)d min ago','%(min)d mins ago',minutes) % {'min':minutes}
+
+@register.simple_tag
+def get_latest_changed_timestamp():
+ try:
+ from time import localtime, strftime
+ from os import path
+ root = settings.SITE_SRC_ROOT
+ dir = (
+ root,
+ '%s/forum' % root,
+ '%s/templates' % root,
+ )
+ stamp = (path.getmtime(d) for d in dir)
+ latest = max(stamp)
+ timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest))
+ except:
+ timestr = ''
+ return timestr
+
+@register.simple_tag
+def href(url):
+ url = '///' + settings.FORUM_SCRIPT_ALIAS + '/' + url
+ return posixpath.normpath(url) + '?v=%d' % settings.RESOURCE_REVISION
+
+class ItemSeparatorNode(template.Node):
+ def __init__(self,separator):
+ sep = separator.strip()
+ if sep[0] == sep[-1] and sep[0] in ('\'','"'):
+ sep = sep[1:-1]
+ else:
+ raise template.TemplateSyntaxError('separator in joinitems tag must be quoted')
+ self.content = sep
+ def render(self,context):
+ return self.content
+
+class JoinItemListNode(template.Node):
+ def __init__(self,separator=ItemSeparatorNode("''"), items=()):
+ self.separator = separator
+ self.items = items
+ def render(self,context):
+ out = []
+ empty_re = re.compile(r'^\s*$')
+ for item in self.items:
+ bit = item.render(context)
+ if not empty_re.search(bit):
+ out.append(bit)
+ return self.separator.render(context).join(out)
+
+@register.tag(name="joinitems")
+def joinitems(parser,token):
+ try:
+ tagname,junk,sep_token = token.split_contents()
+ except ValueError:
+ raise template.TemplateSyntaxError("joinitems tag requires 'using \"separator html\"' parameters")
+ if junk == 'using':
+ sep_node = ItemSeparatorNode(sep_token)
+ else:
+ raise template.TemplateSyntaxError("joinitems tag requires 'using \"separator html\"' parameters")
+ nodelist = []
+ while True:
+ nodelist.append(parser.parse(('separator','endjoinitems')))
+ next = parser.next_token()
+ if next.contents == 'endjoinitems':
+ break
+
+ return JoinItemListNode(separator=sep_node,items=nodelist)
+
+class BlockResourceNode(template.Node):
+ def __init__(self,nodelist):
+ self.items = nodelist
+ def render(self,context):
+ out = '///' + settings.FORUM_SCRIPT_ALIAS
+ if self.items:
+ out += '/'
+ for item in self.items:
+ bit = item.render(context)
+ out += bit
+ out = os.path.normpath(out) + '?v=%d' % settings.RESOURCE_REVISION
+ return out.replace(' ','')
+
+@register.tag(name='blockresource')
+def blockresource(parser,token):
+ try:
+ tagname = token.split_contents()
+ except ValueError:
+ raise template.TemplateSyntaxError("blockresource tag does not use arguments")
+ nodelist = []
+ while True:
+ nodelist.append(parser.parse(('endblockresource')))
+ next = parser.next_token()
+ if next.contents == 'endblockresource':
+ break
+ return BlockResourceNode(nodelist)
diff --git a/forum/templatetags/smart_if.py b/forum/templatetags/smart_if.py
index a8fc1944..ca3b43fe 100644
--- a/forum/templatetags/smart_if.py
+++ b/forum/templatetags/smart_if.py
@@ -1,401 +1,401 @@
-"""
-A smarter {% if %} tag for django templates.
-
-While retaining current Django functionality, it also handles equality,
-greater than and less than operators. Some common case examples::
-
- {% if articles|length >= 5 %}...{% endif %}
- {% if "ifnotequal tag" != "beautiful" %}...{% endif %}
-"""
-import unittest
-from django import template
-
-
-register = template.Library()
-
-
-#==============================================================================
-# Calculation objects
-#==============================================================================
-
-class BaseCalc(object):
- def __init__(self, var1, var2=None, negate=False):
- self.var1 = var1
- self.var2 = var2
- self.negate = negate
-
- def resolve(self, context):
- try:
- var1, var2 = self.resolve_vars(context)
- outcome = self.calculate(var1, var2)
- except:
- outcome = False
- if self.negate:
- return not outcome
- return outcome
-
- def resolve_vars(self, context):
- var2 = self.var2 and self.var2.resolve(context)
- return self.var1.resolve(context), var2
-
- def calculate(self, var1, var2):
- raise NotImplementedError()
-
-
-class Or(BaseCalc):
- def calculate(self, var1, var2):
- return var1 or var2
-
-
-class And(BaseCalc):
- def calculate(self, var1, var2):
- return var1 and var2
-
-
-class Equals(BaseCalc):
- def calculate(self, var1, var2):
- return var1 == var2
-
-
-class Greater(BaseCalc):
- def calculate(self, var1, var2):
- return var1 > var2
-
-
-class GreaterOrEqual(BaseCalc):
- def calculate(self, var1, var2):
- return var1 >= var2
-
-
-class In(BaseCalc):
- def calculate(self, var1, var2):
- return var1 in var2
-
-
-#==============================================================================
-# Tests
-#==============================================================================
-
-class TestVar(object):
- """
- A basic self-resolvable object similar to a Django template variable. Used
- to assist with tests.
- """
- def __init__(self, value):
- self.value = value
-
- def resolve(self, context):
- return self.value
-
-
-class SmartIfTests(unittest.TestCase):
- def setUp(self):
- self.true = TestVar(True)
- self.false = TestVar(False)
- self.high = TestVar(9000)
- self.low = TestVar(1)
-
- def assertCalc(self, calc, context=None):
- """
- Test a calculation is True, also checking the inverse "negate" case.
- """
- context = context or {}
- self.assert_(calc.resolve(context))
- calc.negate = not calc.negate
- self.assertFalse(calc.resolve(context))
-
- def assertCalcFalse(self, calc, context=None):
- """
- Test a calculation is False, also checking the inverse "negate" case.
- """
- context = context or {}
- self.assertFalse(calc.resolve(context))
- calc.negate = not calc.negate
- self.assert_(calc.resolve(context))
-
- def test_or(self):
- self.assertCalc(Or(self.true))
- self.assertCalcFalse(Or(self.false))
- self.assertCalc(Or(self.true, self.true))
- self.assertCalc(Or(self.true, self.false))
- self.assertCalc(Or(self.false, self.true))
- self.assertCalcFalse(Or(self.false, self.false))
-
- def test_and(self):
- self.assertCalc(And(self.true, self.true))
- self.assertCalcFalse(And(self.true, self.false))
- self.assertCalcFalse(And(self.false, self.true))
- self.assertCalcFalse(And(self.false, self.false))
-
- def test_equals(self):
- self.assertCalc(Equals(self.low, self.low))
- self.assertCalcFalse(Equals(self.low, self.high))
-
- def test_greater(self):
- self.assertCalc(Greater(self.high, self.low))
- self.assertCalcFalse(Greater(self.low, self.low))
- self.assertCalcFalse(Greater(self.low, self.high))
-
- def test_greater_or_equal(self):
- self.assertCalc(GreaterOrEqual(self.high, self.low))
- self.assertCalc(GreaterOrEqual(self.low, self.low))
- self.assertCalcFalse(GreaterOrEqual(self.low, self.high))
-
- def test_in(self):
- list_ = TestVar([1,2,3])
- invalid_list = TestVar(None)
- self.assertCalc(In(self.low, list_))
- self.assertCalcFalse(In(self.low, invalid_list))
-
- def test_parse_bits(self):
- var = IfParser([True]).parse()
- self.assert_(var.resolve({}))
- var = IfParser([False]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser([False, 'or', True]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([False, 'and', True]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser(['not', False, 'and', 'not', False]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser(['not', 'not', True]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([1, '=', 1]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([1, 'not', '=', 1]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser([1, 'not', 'not', '=', 1]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([1, '!=', 1]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser([3, '>', 2]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([1, '<', 2]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([2, 'not', 'in', [2, 3]]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser([1, 'or', 1, '=', 2]).parse()
- self.assert_(var.resolve({}))
-
- def test_boolean(self):
- var = IfParser([True, 'and', True, 'and', True]).parse()
- self.assert_(var.resolve({}))
- var = IfParser([False, 'or', False, 'or', True]).parse()
- self.assert_(var.resolve({}))
- var = IfParser([True, 'and', False, 'or', True]).parse()
- self.assert_(var.resolve({}))
- var = IfParser([False, 'or', True, 'and', True]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([True, 'and', True, 'and', False]).parse()
- self.assertFalse(var.resolve({}))
- var = IfParser([False, 'or', False, 'or', False]).parse()
- self.assertFalse(var.resolve({}))
- var = IfParser([False, 'or', True, 'and', False]).parse()
- self.assertFalse(var.resolve({}))
- var = IfParser([False, 'and', True, 'or', False]).parse()
- self.assertFalse(var.resolve({}))
-
- def test_invalid(self):
- self.assertRaises(ValueError, IfParser(['not']).parse)
- self.assertRaises(ValueError, IfParser(['==']).parse)
- self.assertRaises(ValueError, IfParser([1, 'in']).parse)
- self.assertRaises(ValueError, IfParser([1, '>', 'in']).parse)
- self.assertRaises(ValueError, IfParser([1, '==', 'not', 'not']).parse)
- self.assertRaises(ValueError, IfParser([1, 2]).parse)
-
-
-OPERATORS = {
- '=': (Equals, True),
- '==': (Equals, True),
- '!=': (Equals, False),
- '>': (Greater, True),
- '>=': (GreaterOrEqual, True),
- '<=': (Greater, False),
- '<': (GreaterOrEqual, False),
- 'or': (Or, True),
- 'and': (And, True),
- 'in': (In, True),
-}
-BOOL_OPERATORS = ('or', 'and')
-
-
-class IfParser(object):
- error_class = ValueError
-
- def __init__(self, tokens):
- self.tokens = tokens
-
- def _get_tokens(self):
- return self._tokens
-
- def _set_tokens(self, tokens):
- self._tokens = tokens
- self.len = len(tokens)
- self.pos = 0
-
- tokens = property(_get_tokens, _set_tokens)
-
- def parse(self):
- if self.at_end():
- raise self.error_class('No variables provided.')
- var1 = self.get_bool_var()
- while not self.at_end():
- op, negate = self.get_operator()
- var2 = self.get_bool_var()
- var1 = op(var1, var2, negate=negate)
- return var1
-
- def get_token(self, eof_message=None, lookahead=False):
- negate = True
- token = None
- pos = self.pos
- while token is None or token == 'not':
- if pos >= self.len:
- if eof_message is None:
- raise self.error_class()
- raise self.error_class(eof_message)
- token = self.tokens[pos]
- negate = not negate
- pos += 1
- if not lookahead:
- self.pos = pos
- return token, negate
-
- def at_end(self):
- return self.pos >= self.len
-
- def create_var(self, value):
- return TestVar(value)
-
- def get_bool_var(self):
- """
- Returns either a variable by itself or a non-boolean operation (such as
- ``x == 0`` or ``x < 0``).
-
- This is needed to keep correct precedence for boolean operations (i.e.
- ``x or x == 0`` should be ``x or (x == 0)``, not ``(x or x) == 0``).
- """
- var = self.get_var()
- if not self.at_end():
- op_token = self.get_token(lookahead=True)[0]
- if isinstance(op_token, basestring) and (op_token not in
- BOOL_OPERATORS):
- op, negate = self.get_operator()
- return op(var, self.get_var(), negate=negate)
- return var
-
- def get_var(self):
- token, negate = self.get_token('Reached end of statement, still '
- 'expecting a variable.')
- if isinstance(token, basestring) and token in OPERATORS:
- raise self.error_class('Expected variable, got operator (%s).' %
- token)
- var = self.create_var(token)
- if negate:
- return Or(var, negate=True)
- return var
-
- def get_operator(self):
- token, negate = self.get_token('Reached end of statement, still '
- 'expecting an operator.')
- if not isinstance(token, basestring) or token not in OPERATORS:
- raise self.error_class('%s is not a valid operator.' % token)
- if self.at_end():
- raise self.error_class('No variable provided after "%s".' % token)
- op, true = OPERATORS[token]
- if not true:
- negate = not negate
- return op, negate
-
-
-#==============================================================================
-# Actual templatetag code.
-#==============================================================================
-
-class TemplateIfParser(IfParser):
- error_class = template.TemplateSyntaxError
-
- def __init__(self, parser, *args, **kwargs):
- self.template_parser = parser
- return super(TemplateIfParser, self).__init__(*args, **kwargs)
-
- def create_var(self, value):
- return self.template_parser.compile_filter(value)
-
-
-class SmartIfNode(template.Node):
- def __init__(self, var, nodelist_true, nodelist_false=None):
- self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
- self.var = var
-
- def render(self, context):
- if self.var.resolve(context):
- return self.nodelist_true.render(context)
- if self.nodelist_false:
- return self.nodelist_false.render(context)
- return ''
-
- def __repr__(self):
- return "<Smart If node>"
-
- def __iter__(self):
- for node in self.nodelist_true:
- yield node
- if self.nodelist_false:
- for node in self.nodelist_false:
- yield node
-
- def get_nodes_by_type(self, nodetype):
- nodes = []
- if isinstance(self, nodetype):
- nodes.append(self)
- nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
- if self.nodelist_false:
- nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
- return nodes
-
-
-@register.tag('if')
-def smart_if(parser, token):
- """
- A smarter {% if %} tag for django templates.
-
- While retaining current Django functionality, it also handles equality,
- greater than and less than operators. Some common case examples::
-
- {% if articles|length >= 5 %}...{% endif %}
- {% if "ifnotequal tag" != "beautiful" %}...{% endif %}
-
- Arguments and operators _must_ have a space between them, so
- ``{% if 1>2 %}`` is not a valid smart if tag.
-
- All supported operators are: ``or``, ``and``, ``in``, ``=`` (or ``==``),
- ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
- """
- bits = token.split_contents()[1:]
- var = TemplateIfParser(parser, bits).parse()
- nodelist_true = parser.parse(('else', 'endif'))
- token = parser.next_token()
- if token.contents == 'else':
- nodelist_false = parser.parse(('endif',))
- parser.delete_first_token()
- else:
- nodelist_false = None
- return SmartIfNode(var, nodelist_true, nodelist_false)
-
-
-if __name__ == '__main__':
- unittest.main()
+"""
+A smarter {% if %} tag for django templates.
+
+While retaining current Django functionality, it also handles equality,
+greater than and less than operators. Some common case examples::
+
+ {% if articles|length >= 5 %}...{% endif %}
+ {% if "ifnotequal tag" != "beautiful" %}...{% endif %}
+"""
+import unittest
+from django import template
+
+
+register = template.Library()
+
+
+#==============================================================================
+# Calculation objects
+#==============================================================================
+
+class BaseCalc(object):
+ def __init__(self, var1, var2=None, negate=False):
+ self.var1 = var1
+ self.var2 = var2
+ self.negate = negate
+
+ def resolve(self, context):
+ try:
+ var1, var2 = self.resolve_vars(context)
+ outcome = self.calculate(var1, var2)
+ except:
+ outcome = False
+ if self.negate:
+ return not outcome
+ return outcome
+
+ def resolve_vars(self, context):
+ var2 = self.var2 and self.var2.resolve(context)
+ return self.var1.resolve(context), var2
+
+ def calculate(self, var1, var2):
+ raise NotImplementedError()
+
+
+class Or(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 or var2
+
+
+class And(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 and var2
+
+
+class Equals(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 == var2
+
+
+class Greater(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 > var2
+
+
+class GreaterOrEqual(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 >= var2
+
+
+class In(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 in var2
+
+
+#==============================================================================
+# Tests
+#==============================================================================
+
+class TestVar(object):
+ """
+ A basic self-resolvable object similar to a Django template variable. Used
+ to assist with tests.
+ """
+ def __init__(self, value):
+ self.value = value
+
+ def resolve(self, context):
+ return self.value
+
+
+class SmartIfTests(unittest.TestCase):
+ def setUp(self):
+ self.true = TestVar(True)
+ self.false = TestVar(False)
+ self.high = TestVar(9000)
+ self.low = TestVar(1)
+
+ def assertCalc(self, calc, context=None):
+ """
+ Test a calculation is True, also checking the inverse "negate" case.
+ """
+ context = context or {}
+ self.assert_(calc.resolve(context))
+ calc.negate = not calc.negate
+ self.assertFalse(calc.resolve(context))
+
+ def assertCalcFalse(self, calc, context=None):
+ """
+ Test a calculation is False, also checking the inverse "negate" case.
+ """
+ context = context or {}
+ self.assertFalse(calc.resolve(context))
+ calc.negate = not calc.negate
+ self.assert_(calc.resolve(context))
+
+ def test_or(self):
+ self.assertCalc(Or(self.true))
+ self.assertCalcFalse(Or(self.false))
+ self.assertCalc(Or(self.true, self.true))
+ self.assertCalc(Or(self.true, self.false))
+ self.assertCalc(Or(self.false, self.true))
+ self.assertCalcFalse(Or(self.false, self.false))
+
+ def test_and(self):
+ self.assertCalc(And(self.true, self.true))
+ self.assertCalcFalse(And(self.true, self.false))
+ self.assertCalcFalse(And(self.false, self.true))
+ self.assertCalcFalse(And(self.false, self.false))
+
+ def test_equals(self):
+ self.assertCalc(Equals(self.low, self.low))
+ self.assertCalcFalse(Equals(self.low, self.high))
+
+ def test_greater(self):
+ self.assertCalc(Greater(self.high, self.low))
+ self.assertCalcFalse(Greater(self.low, self.low))
+ self.assertCalcFalse(Greater(self.low, self.high))
+
+ def test_greater_or_equal(self):
+ self.assertCalc(GreaterOrEqual(self.high, self.low))
+ self.assertCalc(GreaterOrEqual(self.low, self.low))
+ self.assertCalcFalse(GreaterOrEqual(self.low, self.high))
+
+ def test_in(self):
+ list_ = TestVar([1,2,3])
+ invalid_list = TestVar(None)
+ self.assertCalc(In(self.low, list_))
+ self.assertCalcFalse(In(self.low, invalid_list))
+
+ def test_parse_bits(self):
+ var = IfParser([True]).parse()
+ self.assert_(var.resolve({}))
+ var = IfParser([False]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser([False, 'or', True]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([False, 'and', True]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser(['not', False, 'and', 'not', False]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser(['not', 'not', True]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([1, '=', 1]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([1, 'not', '=', 1]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser([1, 'not', 'not', '=', 1]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([1, '!=', 1]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser([3, '>', 2]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([1, '<', 2]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([2, 'not', 'in', [2, 3]]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser([1, 'or', 1, '=', 2]).parse()
+ self.assert_(var.resolve({}))
+
+ def test_boolean(self):
+ var = IfParser([True, 'and', True, 'and', True]).parse()
+ self.assert_(var.resolve({}))
+ var = IfParser([False, 'or', False, 'or', True]).parse()
+ self.assert_(var.resolve({}))
+ var = IfParser([True, 'and', False, 'or', True]).parse()
+ self.assert_(var.resolve({}))
+ var = IfParser([False, 'or', True, 'and', True]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([True, 'and', True, 'and', False]).parse()
+ self.assertFalse(var.resolve({}))
+ var = IfParser([False, 'or', False, 'or', False]).parse()
+ self.assertFalse(var.resolve({}))
+ var = IfParser([False, 'or', True, 'and', False]).parse()
+ self.assertFalse(var.resolve({}))
+ var = IfParser([False, 'and', True, 'or', False]).parse()
+ self.assertFalse(var.resolve({}))
+
+ def test_invalid(self):
+ self.assertRaises(ValueError, IfParser(['not']).parse)
+ self.assertRaises(ValueError, IfParser(['==']).parse)
+ self.assertRaises(ValueError, IfParser([1, 'in']).parse)
+ self.assertRaises(ValueError, IfParser([1, '>', 'in']).parse)
+ self.assertRaises(ValueError, IfParser([1, '==', 'not', 'not']).parse)
+ self.assertRaises(ValueError, IfParser([1, 2]).parse)
+
+
+OPERATORS = {
+ '=': (Equals, True),
+ '==': (Equals, True),
+ '!=': (Equals, False),
+ '>': (Greater, True),
+ '>=': (GreaterOrEqual, True),
+ '<=': (Greater, False),
+ '<': (GreaterOrEqual, False),
+ 'or': (Or, True),
+ 'and': (And, True),
+ 'in': (In, True),
+}
+BOOL_OPERATORS = ('or', 'and')
+
+
+class IfParser(object):
+ error_class = ValueError
+
+ def __init__(self, tokens):
+ self.tokens = tokens
+
+ def _get_tokens(self):
+ return self._tokens
+
+ def _set_tokens(self, tokens):
+ self._tokens = tokens
+ self.len = len(tokens)
+ self.pos = 0
+
+ tokens = property(_get_tokens, _set_tokens)
+
+ def parse(self):
+ if self.at_end():
+ raise self.error_class('No variables provided.')
+ var1 = self.get_bool_var()
+ while not self.at_end():
+ op, negate = self.get_operator()
+ var2 = self.get_bool_var()
+ var1 = op(var1, var2, negate=negate)
+ return var1
+
+ def get_token(self, eof_message=None, lookahead=False):
+ negate = True
+ token = None
+ pos = self.pos
+ while token is None or token == 'not':
+ if pos >= self.len:
+ if eof_message is None:
+ raise self.error_class()
+ raise self.error_class(eof_message)
+ token = self.tokens[pos]
+ negate = not negate
+ pos += 1
+ if not lookahead:
+ self.pos = pos
+ return token, negate
+
+ def at_end(self):
+ return self.pos >= self.len
+
+ def create_var(self, value):
+ return TestVar(value)
+
+ def get_bool_var(self):
+ """
+ Returns either a variable by itself or a non-boolean operation (such as
+ ``x == 0`` or ``x < 0``).
+
+ This is needed to keep correct precedence for boolean operations (i.e.
+ ``x or x == 0`` should be ``x or (x == 0)``, not ``(x or x) == 0``).
+ """
+ var = self.get_var()
+ if not self.at_end():
+ op_token = self.get_token(lookahead=True)[0]
+ if isinstance(op_token, basestring) and (op_token not in
+ BOOL_OPERATORS):
+ op, negate = self.get_operator()
+ return op(var, self.get_var(), negate=negate)
+ return var
+
+ def get_var(self):
+ token, negate = self.get_token('Reached end of statement, still '
+ 'expecting a variable.')
+ if isinstance(token, basestring) and token in OPERATORS:
+ raise self.error_class('Expected variable, got operator (%s).' %
+ token)
+ var = self.create_var(token)
+ if negate:
+ return Or(var, negate=True)
+ return var
+
+ def get_operator(self):
+ token, negate = self.get_token('Reached end of statement, still '
+ 'expecting an operator.')
+ if not isinstance(token, basestring) or token not in OPERATORS:
+ raise self.error_class('%s is not a valid operator.' % token)
+ if self.at_end():
+ raise self.error_class('No variable provided after "%s".' % token)
+ op, true = OPERATORS[token]
+ if not true:
+ negate = not negate
+ return op, negate
+
+
+#==============================================================================
+# Actual templatetag code.
+#==============================================================================
+
+class TemplateIfParser(IfParser):
+ error_class = template.TemplateSyntaxError
+
+ def __init__(self, parser, *args, **kwargs):
+ self.template_parser = parser
+ return super(TemplateIfParser, self).__init__(*args, **kwargs)
+
+ def create_var(self, value):
+ return self.template_parser.compile_filter(value)
+
+
+class SmartIfNode(template.Node):
+ def __init__(self, var, nodelist_true, nodelist_false=None):
+ self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+ self.var = var
+
+ def render(self, context):
+ if self.var.resolve(context):
+ return self.nodelist_true.render(context)
+ if self.nodelist_false:
+ return self.nodelist_false.render(context)
+ return ''
+
+ def __repr__(self):
+ return "<Smart If node>"
+
+ def __iter__(self):
+ for node in self.nodelist_true:
+ yield node
+ if self.nodelist_false:
+ for node in self.nodelist_false:
+ yield node
+
+ def get_nodes_by_type(self, nodetype):
+ nodes = []
+ if isinstance(self, nodetype):
+ nodes.append(self)
+ nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
+ if self.nodelist_false:
+ nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
+ return nodes
+
+
+@register.tag('if')
+def smart_if(parser, token):
+ """
+ A smarter {% if %} tag for django templates.
+
+ While retaining current Django functionality, it also handles equality,
+ greater than and less than operators. Some common case examples::
+
+ {% if articles|length >= 5 %}...{% endif %}
+ {% if "ifnotequal tag" != "beautiful" %}...{% endif %}
+
+ Arguments and operators _must_ have a space between them, so
+ ``{% if 1>2 %}`` is not a valid smart if tag.
+
+ All supported operators are: ``or``, ``and``, ``in``, ``=`` (or ``==``),
+ ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
+ """
+ bits = token.split_contents()[1:]
+ var = TemplateIfParser(parser, bits).parse()
+ nodelist_true = parser.parse(('else', 'endif'))
+ token = parser.next_token()
+ if token.contents == 'else':
+ nodelist_false = parser.parse(('endif',))
+ parser.delete_first_token()
+ else:
+ nodelist_false = None
+ return SmartIfNode(var, nodelist_true, nodelist_false)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/forum/views.py b/forum/views.py
index 04d9d497..c4514912 100644
--- a/forum/views.py
+++ b/forum/views.py
@@ -1,2387 +1,2387 @@
-# encoding:utf-8
-import os.path
-import time, datetime, calendar, random
-import logging
-from urllib import quote, unquote
-from django.conf import settings
-from django.core.files.storage import default_storage
-from django.shortcuts import render_to_response, get_object_or_404
-from django.contrib.auth.decorators import login_required
-from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
-from django.core.paginator import Paginator, EmptyPage, InvalidPage
-from django.template import RequestContext, loader
-from django.utils.html import *
-from django.utils import simplejson
-from django.core import serializers
-from django.core.mail import mail_admins
-from django.db import transaction
-from django.db.models import Count, Q
-from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import ugettext as _
-from django.utils.datastructures import SortedDict
-from django.template.defaultfilters import slugify
-from django.core.exceptions import PermissionDenied
-
-from utils.html import sanitize_html
-from utils.decorators import ajax_method, ajax_login_required
-from markdown2 import Markdown
-#from lxml.html.diff import htmldiff
-from forum.diff import textDiff as htmldiff
-from forum.forms import *
-from forum.models import *
-from forum.auth import *
-from forum.const import *
-from forum.user import *
-from forum import auth
-from django_authopenid.util import get_next_url
-
-# used in index page
-INDEX_PAGE_SIZE = 20
-INDEX_AWARD_SIZE = 15
-INDEX_TAGS_SIZE = 100
-# used in tags list
-DEFAULT_PAGE_SIZE = 60
-# used in questions
-QUESTIONS_PAGE_SIZE = 10
-# used in users
-USERS_PAGE_SIZE = 35
-# used in answers
-ANSWERS_PAGE_SIZE = 10
-markdowner = Markdown(html4tags=True)
-question_type = ContentType.objects.get_for_model(Question)
-answer_type = ContentType.objects.get_for_model(Answer)
-comment_type = ContentType.objects.get_for_model(Comment)
-question_revision_type = ContentType.objects.get_for_model(QuestionRevision)
-answer_revision_type = ContentType.objects.get_for_model(AnswerRevision)
-repute_type = ContentType.objects.get_for_model(Repute)
-question_type_id = question_type.id
-answer_type_id = answer_type.id
-comment_type_id = comment_type.id
-question_revision_type_id = question_revision_type.id
-answer_revision_type_id = answer_revision_type.id
-repute_type_id = repute_type.id
-def _get_tags_cache_json():
- tags = Tag.objects.filter(deleted=False).all()
- tags_list = []
- for tag in tags:
- dic = {'n': tag.name, 'c': tag.used_count}
- tags_list.append(dic)
- tags = simplejson.dumps(tags_list)
- return tags
-
-def _get_and_remember_questions_sort_method(request, view_dic, default):
- if default not in view_dic:
- raise Exception('default value must be in view_dic')
-
- q_sort_method = request.REQUEST.get('sort', None)
- if q_sort_method == None:
- q_sort_method = request.session.get('questions_sort_method', default)
-
- if q_sort_method not in view_dic:
- q_sort_method = default
- request.session['questions_sort_method'] = q_sort_method
- return q_sort_method, view_dic[q_sort_method]
-
-def index(request):
- view_dic = {
- "latest":"-last_activity_at",
- "hottest":"-answer_count",
- "mostvoted":"-score",
- }
- view_id, orderby = _get_and_remember_questions_sort_method(request, view_dic, 'latest')
-
- page_size = request.session.get('pagesize', QUESTIONS_PAGE_SIZE)
- questions = Question.objects.exclude(deleted=True).order_by(orderby)[:page_size]
- # RISK - inner join queries
- questions = questions.select_related()
- tags = Tag.objects.get_valid_tags(INDEX_TAGS_SIZE)
-
- awards = Award.objects.get_recent_awards()
-
- (interesting_tag_names, ignored_tag_names) = (None, None)
- if request.user.is_authenticated():
- pt = MarkedTag.objects.filter(user=request.user)
- interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True)
- ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True)
-
- tags_autocomplete = _get_tags_cache_json()
-
- return render_to_response('index.html', {
- 'interesting_tag_names': interesting_tag_names,
- 'tags_autocomplete': tags_autocomplete,
- 'ignored_tag_names': ignored_tag_names,
- "questions" : questions,
- "tab_id" : view_id,
- "tags" : tags,
- "awards" : awards[:INDEX_AWARD_SIZE],
- }, context_instance=RequestContext(request))
-
-def about(request):
- return render_to_response('about.html', context_instance=RequestContext(request))
-
-def faq(request):
- data = {
- 'gravatar_faq_url': reverse('faq') + '#gravatar',
- 'send_email_key_url': reverse('send_email_key'),
- 'ask_question_url': reverse('ask'),
- }
- return render_to_response('faq.html', data, context_instance=RequestContext(request))
-
-def feedback(request):
- data = {}
- form = None
- if request.method == "POST":
- form = FeedbackForm(request.POST)
- if form.is_valid():
- if not request.user.is_authenticated:
- data['email'] = form.cleaned_data.get('email',None)
- data['message'] = form.cleaned_data['message']
- data['name'] = form.cleaned_data.get('name',None)
- message = render_to_response('feedback_email.txt',data,context_instance=RequestContext(request))
- mail_admins(_('Q&A forum feedback'), message)
- msg = _('Thanks for the feedback!')
- request.user.message_set.create(message=msg)
- return HttpResponseRedirect(get_next_url(request))
- else:
- form = FeedbackForm(initial={'next':get_next_url(request)})
-
- data['form'] = form
- return render_to_response('feedback.html', data, context_instance=RequestContext(request))
-feedback.CANCEL_MESSAGE=_('We look forward to hearing your feedback! Please, give it next time :)')
-
-def privacy(request):
- return render_to_response('privacy.html', context_instance=RequestContext(request))
-
-def unanswered(request):
- return questions(request, unanswered=True)
-
-def questions(request, tagname=None, unanswered=False):
- """
- List of Questions, Tagged questions, and Unanswered questions.
- """
- # template file
- # "questions.html" or maybe index.html in the future
- template_file = "questions.html"
- # Set flag to False by default. If it is equal to True, then need to be saved.
- pagesize_changed = False
- # get pagesize from session, if failed then get default value
- pagesize = request.session.get("pagesize",10)
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
- view_id, orderby = _get_and_remember_questions_sort_method(request,view_dic,'latest')
-
- # check if request is from tagged questions
- qs = Question.objects.exclude(deleted=True)
-
- if tagname is not None:
- qs = qs.filter(tags__name = unquote(tagname))
-
- if unanswered:
- qs = qs.exclude(answer_accepted=True)
-
- author_name = None
- #user contributed questions & answers
- if 'user' in request.GET:
- try:
- author_name = request.GET['user']
- u = User.objects.get(username=author_name)
- qs = qs.filter(Q(author=u) | Q(answers__author=u))
- except User.DoesNotExist:
- author_name = None
-
- if request.user.is_authenticated():
- uid_str = str(request.user.id)
- qs = qs.extra(
- select = SortedDict([
- (
- 'interesting_score',
- 'SELECT COUNT(1) FROM forum_markedtag, question_tags '
- + 'WHERE forum_markedtag.user_id = %s '
- + 'AND forum_markedtag.tag_id = question_tags.tag_id '
- + 'AND forum_markedtag.reason = "good" '
- + 'AND question_tags.question_id = question.id'
- ),
- ]),
- select_params = (uid_str,),
- )
- if request.user.hide_ignored_questions:
- ignored_tags = Tag.objects.filter(user_selections__reason='bad',
- user_selections__user = request.user)
- qs = qs.exclude(tags__in=ignored_tags)
- else:
- qs = qs.extra(
- select = SortedDict([
- (
- 'ignored_score',
- 'SELECT COUNT(1) FROM forum_markedtag, question_tags '
- + 'WHERE forum_markedtag.user_id = %s '
- + 'AND forum_markedtag.tag_id = question_tags.tag_id '
- + 'AND forum_markedtag.reason = "bad" '
- + 'AND question_tags.question_id = question.id'
- )
- ]),
- select_params = (uid_str, )
- )
-
- qs = qs.select_related(depth=1).order_by(orderby)
-
- objects_list = Paginator(qs, pagesize)
- questions = objects_list.page(page)
-
- # Get related tags from this page objects
- if questions.object_list.count() > 0:
- related_tags = Tag.objects.get_tags_by_questions(questions.object_list)
- else:
- related_tags = None
- tags_autocomplete = _get_tags_cache_json()
-
- # get the list of interesting and ignored tags
- (interesting_tag_names, ignored_tag_names) = (None, None)
- if request.user.is_authenticated():
- pt = MarkedTag.objects.filter(user=request.user)
- interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True)
- ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True)
-
- return render_to_response(template_file, {
- "questions" : questions,
- "author_name" : author_name,
- "tab_id" : view_id,
- "questions_count" : objects_list.count,
- "tags" : related_tags,
- "tags_autocomplete" : tags_autocomplete,
- "searchtag" : tagname,
- "is_unanswered" : unanswered,
- "interesting_tag_names": interesting_tag_names,
- 'ignored_tag_names': ignored_tag_names,
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url' : request.path + '?sort=%s&' % view_id,
- 'pagesize' : pagesize
- }}, context_instance=RequestContext(request))
-
-def create_new_answer( question=None, author=None,\
- added_at=None, wiki=False,\
- text='', email_notify=False):
-
- html = sanitize_html(markdowner.convert(text))
-
- #create answer
- answer = Answer(
- question = question,
- author = author,
- added_at = added_at,
- wiki = wiki,
- html = html
- )
- if answer.wiki:
- answer.last_edited_by = answer.author
- answer.last_edited_at = added_at
- answer.wikified_at = added_at
-
- answer.save()
-
- #update question data
- question.last_activity_at = added_at
- question.last_activity_by = author
- question.save()
- Question.objects.update_answer_count(question)
-
- #update revision
- AnswerRevision.objects.create(
- answer = answer,
- revision = 1,
- author = author,
- revised_at = added_at,
- summary = CONST['default_version'],
- text = text
- )
-
- #set notification/delete
- if email_notify:
- if author not in question.followed_by.all():
- question.followed_by.add(author)
- else:
- #not sure if this is necessary. ajax should take care of this...
- try:
- question.followed_by.remove(author)
- except:
- pass
-
-def create_new_question(title=None,author=None,added_at=None,
- wiki=False,tagnames=None,summary=None,
- text=None):
- """this is not a view
- and maybe should become one of the methods on Question object?
- """
- html = sanitize_html(markdowner.convert(text))
- question = Question(
- title = title,
- author = author,
- added_at = added_at,
- last_activity_at = added_at,
- last_activity_by = author,
- wiki = wiki,
- tagnames = tagnames,
- html = html,
- summary = summary
- )
- if question.wiki:
- question.last_edited_by = question.author
- question.last_edited_at = added_at
- question.wikified_at = added_at
-
- question.save()
-
- # create the first revision
- QuestionRevision.objects.create(
- question = question,
- revision = 1,
- title = question.title,
- author = author,
- revised_at = added_at,
- tagnames = question.tagnames,
- summary = CONST['default_version'],
- text = text
- )
- return question
-
-#TODO: allow anynomus user to ask question by providing email and username.
-#@login_required
-def ask(request):
- if request.method == "POST":
- form = AskForm(request.POST)
- if form.is_valid():
-
- added_at = datetime.datetime.now()
- title = strip_tags(form.cleaned_data['title'].strip())
- wiki = form.cleaned_data['wiki']
- tagnames = form.cleaned_data['tags'].strip()
- text = form.cleaned_data['text']
- html = sanitize_html(markdowner.convert(text))
- summary = strip_tags(html)[:120]
-
- if request.user.is_authenticated():
- author = request.user
-
- question = create_new_question(
- title = title,
- author = author,
- added_at = added_at,
- wiki = wiki,
- tagnames = tagnames,
- summary = summary,
- text = text
- )
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- request.session.flush()
- session_key = request.session.session_key
- question = AnonymousQuestion(
- session_key = session_key,
- title = title,
- tagnames = tagnames,
- wiki = wiki,
- text = text,
- summary = summary,
- added_at = added_at,
- ip_addr = request.META['REMOTE_ADDR'],
- )
- question.save()
- return HttpResponseRedirect(reverse('user_signin_new_question'))
- else:
- form = AskForm()
-
- tags = _get_tags_cache_json()
- return render_to_response('ask.html', {
- 'form' : form,
- 'tags' : tags,
- 'email_validation_faq_url':reverse('faq') + '#validate',
- }, context_instance=RequestContext(request))
-
-def question(request, id):
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "oldest":"added_at", "votes":"-score" }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- qsm = request.session.get('questions_sort_method',None)
- if qsm in ('mostvoted','latest'):
- logging.debug('loaded from session ' + qsm)
- if qsm == 'mostvoted':
- view_id = 'votes'
- orderby = '-score'
- else:
- view_id = 'latest'
- orderby = '-added_at'
- else:
- view_id = "votes"
- orderby = "-score"
-
- logging.debug('view_id=' + str(view_id))
-
- question = get_object_or_404(Question, id=id)
- if question.deleted and not can_view_deleted_post(request.user, question):
- raise Http404
- answer_form = AnswerForm(question,request.user)
- answers = Answer.objects.get_answers_from_question(question, request.user)
- answers = answers.select_related(depth=1)
-
- favorited = question.has_favorite_by_user(request.user)
- if request.user.is_authenticated():
- question_vote = question.votes.select_related().filter(user=request.user)
- else:
- question_vote = None #is this correct?
- if question_vote is not None and question_vote.count() > 0:
- question_vote = question_vote[0]
-
- user_answer_votes = {}
- for answer in answers:
- vote = answer.get_user_vote(request.user)
- if vote is not None and not user_answer_votes.has_key(answer.id):
- vote_value = -1
- if vote.is_upvote():
- vote_value = 1
- user_answer_votes[answer.id] = vote_value
-
- if answers is not None:
- answers = answers.order_by("-accepted", orderby)
-
- filtered_answers = []
- for answer in answers:
- if answer.deleted == True:
- if answer.author_id == request.user.id:
- filtered_answers.append(answer)
- else:
- filtered_answers.append(answer)
-
- objects_list = Paginator(filtered_answers, ANSWERS_PAGE_SIZE)
- page_objects = objects_list.page(page)
-
- #todo: merge view counts per user and per session
- #1) view count per session
- update_view_count = False
- if 'question_view_times' not in request.session:
- request.session['question_view_times'] = {}
-
- last_seen = request.session['question_view_times'].get(question.id,None)
- updated_when, updated_who = question.get_last_update_info()
-
- if updated_who != request.user:
- if last_seen:
- if last_seen < updated_when:
- update_view_count = True
- else:
- update_view_count = True
-
- request.session['question_view_times'][question.id] = datetime.datetime.now()
-
- if update_view_count:
- question.view_count += 1
- question.save()
-
- #2) question view count per user
- if request.user.is_authenticated():
- try:
- question_view = QuestionView.objects.get(who=request.user, question=question)
- except QuestionView.DoesNotExist:
- question_view = QuestionView(who=request.user, question=question)
- question_view.when = datetime.datetime.now()
- question_view.save()
-
- return render_to_response('question.html', {
- "question" : question,
- "question_vote" : question_vote,
- "question_comment_count":question.comments.count(),
- "answer" : answer_form,
- "answers" : page_objects.object_list,
- "user_answer_votes": user_answer_votes,
- "tags" : question.tags.all(),
- "tab_id" : view_id,
- "favorited" : favorited,
- "similar_questions" : Question.objects.get_similar_questions(question),
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': page_objects.has_previous(),
- 'has_next': page_objects.has_next(),
- 'previous': page_objects.previous_page_number(),
- 'next': page_objects.next_page_number(),
- 'base_url' : request.path + '?sort=%s&' % view_id,
- 'extend_url' : "#sort-top"
- }
- }, context_instance=RequestContext(request))
-
-@login_required
-def close(request, id):
- question = get_object_or_404(Question, id=id)
- if not can_close_question(request.user, question):
- return HttpResponse('Permission denied.')
- if request.method == 'POST':
- form = CloseForm(request.POST)
- if form.is_valid():
- reason = form.cleaned_data['reason']
- question.closed = True
- question.closed_by = request.user
- question.closed_at = datetime.datetime.now()
- question.close_reason = reason
- question.save()
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- form = CloseForm()
- return render_to_response('close.html', {
- 'form' : form,
- 'question' : question,
- }, context_instance=RequestContext(request))
-
-@login_required
-def reopen(request, id):
- question = get_object_or_404(Question, id=id)
- # open question
- if not can_reopen_question(request.user, question):
- return HttpResponse('Permission denied.')
- if request.method == 'POST' :
- Question.objects.filter(id=question.id).update(closed=False,
- closed_by=None, closed_at=None, close_reason=None)
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- return render_to_response('reopen.html', {
- 'question' : question,
- }, context_instance=RequestContext(request))
-
-@login_required
-def edit_question(request, id):
- question = get_object_or_404(Question, id=id)
- if question.deleted and not can_view_deleted_post(request.user, question):
- raise Http404
- if can_edit_post(request.user, question):
- return _edit_question(request, question)
- elif can_retag_questions(request.user):
- return _retag_question(request, question)
- else:
- raise Http404
-
-def _retag_question(request, question):
- if request.method == 'POST':
- form = RetagQuestionForm(question, request.POST)
- if form.is_valid():
- if form.has_changed():
- latest_revision = question.get_latest_revision()
- retagged_at = datetime.datetime.now()
- # Update the Question itself
- Question.objects.filter(id=question.id).update(
- tagnames = form.cleaned_data['tags'],
- last_edited_at = retagged_at,
- last_edited_by = request.user,
- last_activity_at = retagged_at,
- last_activity_by = request.user
- )
- # Update the Question's tag associations
- tags_updated = Question.objects.update_tags(question,
- form.cleaned_data['tags'], request.user)
- # Create a new revision
- QuestionRevision.objects.create(
- question = question,
- title = latest_revision.title,
- author = request.user,
- revised_at = retagged_at,
- tagnames = form.cleaned_data['tags'],
- summary = CONST['retagged'],
- text = latest_revision.text
- )
- # send tags updated singal
- tags_updated.send(sender=question.__class__, question=question)
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- form = RetagQuestionForm(question)
- return render_to_response('question_retag.html', {
- 'question': question,
- 'form' : form,
- 'tags' : _get_tags_cache_json(),
- }, context_instance=RequestContext(request))
-
-def _edit_question(request, question):
- latest_revision = question.get_latest_revision()
- revision_form = None
- if request.method == 'POST':
- if 'select_revision' in request.POST:
- # user has changed revistion number
- revision_form = RevisionForm(question, latest_revision, request.POST)
- if revision_form.is_valid():
- # Replace with those from the selected revision
- form = EditQuestionForm(question,
- QuestionRevision.objects.get(question=question,
- revision=revision_form.cleaned_data['revision']))
- else:
- form = EditQuestionForm(question, latest_revision, request.POST)
- else:
- # Always check modifications against the latest revision
- form = EditQuestionForm(question, latest_revision, request.POST)
- if form.is_valid():
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- if form.has_changed():
- edited_at = datetime.datetime.now()
- tags_changed = (latest_revision.tagnames !=
- form.cleaned_data['tags'])
- tags_updated = False
- # Update the Question itself
- updated_fields = {
- 'title': form.cleaned_data['title'],
- 'last_edited_at': edited_at,
- 'last_edited_by': request.user,
- 'last_activity_at': edited_at,
- 'last_activity_by': request.user,
- 'tagnames': form.cleaned_data['tags'],
- 'summary': strip_tags(html)[:120],
- 'html': html,
- }
-
- # only save when it's checked
- # because wiki doesn't allow to be edited if last version has been enabled already
- # and we make sure this in forms.
- if ('wiki' in form.cleaned_data and
- form.cleaned_data['wiki']):
- updated_fields['wiki'] = True
- updated_fields['wikified_at'] = edited_at
-
- Question.objects.filter(
- id=question.id).update(**updated_fields)
- # Update the Question's tag associations
- if tags_changed:
- tags_updated = Question.objects.update_tags(
- question, form.cleaned_data['tags'], request.user)
- # Create a new revision
- revision = QuestionRevision(
- question = question,
- title = form.cleaned_data['title'],
- author = request.user,
- revised_at = edited_at,
- tagnames = form.cleaned_data['tags'],
- text = form.cleaned_data['text'],
- )
- if form.cleaned_data['summary']:
- revision.summary = form.cleaned_data['summary']
- else:
- revision.summary = 'No.%s Revision' % latest_revision.revision
- revision.save()
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
-
- revision_form = RevisionForm(question, latest_revision)
- form = EditQuestionForm(question, latest_revision)
- return render_to_response('question_edit.html', {
- 'question': question,
- 'revision_form': revision_form,
- 'form' : form,
- 'tags' : _get_tags_cache_json()
- }, context_instance=RequestContext(request))
-
-
-@login_required
-def edit_answer(request, id):
- answer = get_object_or_404(Answer, id=id)
- if answer.deleted and not can_view_deleted_post(request.user, answer):
- raise Http404
- elif not can_edit_post(request.user, answer):
- raise Http404
- else:
- latest_revision = answer.get_latest_revision()
- if request.method == "POST":
- if 'select_revision' in request.POST:
- # user has changed revistion number
- revision_form = RevisionForm(answer, latest_revision, request.POST)
- if revision_form.is_valid():
- # Replace with those from the selected revision
- form = EditAnswerForm(answer,
- AnswerRevision.objects.get(answer=answer,
- revision=revision_form.cleaned_data['revision']))
- else:
- form = EditAnswerForm(answer, latest_revision, request.POST)
- else:
- form = EditAnswerForm(answer, latest_revision, request.POST)
- if form.is_valid():
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- if form.has_changed():
- edited_at = datetime.datetime.now()
- updated_fields = {
- 'last_edited_at': edited_at,
- 'last_edited_by': request.user,
- 'html': html,
- }
- Answer.objects.filter(id=answer.id).update(**updated_fields)
-
- revision = AnswerRevision(
- answer=answer,
- author=request.user,
- revised_at=edited_at,
- text=form.cleaned_data['text']
- )
-
- if form.cleaned_data['summary']:
- revision.summary = form.cleaned_data['summary']
- else:
- revision.summary = 'No.%s Revision' % latest_revision.revision
- revision.save()
-
- answer.question.last_activity_at = edited_at
- answer.question.last_activity_by = request.user
- answer.question.save()
-
- return HttpResponseRedirect(answer.get_absolute_url())
- else:
- revision_form = RevisionForm(answer, latest_revision)
- form = EditAnswerForm(answer, latest_revision)
- return render_to_response('answer_edit.html', {
- 'answer': answer,
- 'revision_form': revision_form,
- 'form': form,
- }, context_instance=RequestContext(request))
-
-QUESTION_REVISION_TEMPLATE = ('<h1>%(title)s</h1>\n'
- '<div class="text">%(html)s</div>\n'
- '<div class="tags">%(tags)s</div>')
-def question_revisions(request, id):
- post = get_object_or_404(Question, id=id)
- revisions = list(post.revisions.all())
- revisions.reverse()
- for i, revision in enumerate(revisions):
- revision.html = QUESTION_REVISION_TEMPLATE % {
- 'title': revision.title,
- 'html': sanitize_html(markdowner.convert(revision.text)),
- 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
- for tag in revision.tagnames.split(' ')]),
- }
- if i > 0:
- revisions[i].diff = htmldiff(revisions[i-1].html, revision.html)
- else:
- revisions[i].diff = QUESTION_REVISION_TEMPLATE % {
- 'title': revisions[0].title,
- 'html': sanitize_html(markdowner.convert(revisions[0].text)),
- 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
- for tag in revisions[0].tagnames.split(' ')]),
- }
- revisions[i].summary = _('initial version')
- return render_to_response('revisions_question.html', {
- 'post': post,
- 'revisions': revisions,
- }, context_instance=RequestContext(request))
-
-ANSWER_REVISION_TEMPLATE = ('<div class="text">%(html)s</div>')
-def answer_revisions(request, id):
- post = get_object_or_404(Answer, id=id)
- revisions = list(post.revisions.all())
- revisions.reverse()
- for i, revision in enumerate(revisions):
- revision.html = ANSWER_REVISION_TEMPLATE % {
- 'html': sanitize_html(markdowner.convert(revision.text))
- }
- if i > 0:
- revisions[i].diff = htmldiff(revisions[i-1].html, revision.html)
- else:
- revisions[i].diff = revisions[i].text
- revisions[i].summary = _('initial version')
- return render_to_response('revisions_answer.html', {
- 'post': post,
- 'revisions': revisions,
- }, context_instance=RequestContext(request))
-
-def answer(request, id):
- question = get_object_or_404(Question, id=id)
- if request.method == "POST":
- form = AnswerForm(question, request.user, request.POST)
- if form.is_valid():
- wiki = form.cleaned_data['wiki']
- text = form.cleaned_data['text']
- update_time = datetime.datetime.now()
-
- if request.user.is_authenticated():
- create_new_answer(
- question=question,
- author=request.user,
- added_at=update_time,
- wiki=wiki,
- text=text,
- email_notify=form.cleaned_data['email_notify']
- )
- else:
- request.session.flush()
- html = sanitize_html(markdowner.convert(text))
- summary = strip_tags(html)[:120]
- anon = AnonymousAnswer(
- question=question,
- wiki=wiki,
- text=text,
- summary=summary,
- session_key=request.session.session_key,
- ip_addr=request.META['REMOTE_ADDR'],
- )
- anon.save()
- return HttpResponseRedirect(reverse('user_signin_new_answer'))
-
- return HttpResponseRedirect(question.get_absolute_url())
-
-def tags(request):
- stag = ""
- is_paginated = True
- sortby = request.GET.get('sort', 'used')
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- if request.method == "GET":
- stag = request.GET.get("q", "").strip()
- if stag != '':
- objects_list = Paginator(Tag.objects.filter(deleted=False).exclude(used_count=0).extra(where=['name like %s'], params=['%' + stag + '%']), DEFAULT_PAGE_SIZE)
- else:
- if sortby == "name":
- objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("name"), DEFAULT_PAGE_SIZE)
- else:
- objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-used_count"), DEFAULT_PAGE_SIZE)
-
- try:
- tags = objects_list.page(page)
- except (EmptyPage, InvalidPage):
- tags = objects_list.page(objects_list.num_pages)
-
- return render_to_response('tags.html', {
- "tags" : tags,
- "stag" : stag,
- "tab_id" : sortby,
- "keywords" : stag,
- "context" : {
- 'is_paginated' : is_paginated,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': tags.has_previous(),
- 'has_next': tags.has_next(),
- 'previous': tags.previous_page_number(),
- 'next': tags.next_page_number(),
- 'base_url' : reverse('tags') + '?sort=%s&' % sortby
- }
- }, context_instance=RequestContext(request))
-
-def tag(request, tag):
- return questions(request, tagname=tag)
-
-def vote(request, id):
- """
- vote_type:
- acceptAnswer : 0,
- questionUpVote : 1,
- questionDownVote : 2,
- favorite : 4,
- answerUpVote: 5,
- answerDownVote:6,
- offensiveQuestion : 7,
- offensiveAnswer:8,
- removeQuestion: 9,
- removeAnswer:10
- questionSubscribeUpdates:11
-
- accept answer code:
- response_data['allowed'] = -1, Accept his own answer 0, no allowed - Anonymous 1, Allowed - by default
- response_data['success'] = 0, failed 1, Success - by default
- response_data['status'] = 0, By default 1, Answer has been accepted already(Cancel)
-
- vote code:
- allowed = -3, Don't have enough votes left
- -2, Don't have enough reputation score
- -1, Vote his own post
- 0, no allowed - Anonymous
- 1, Allowed - by default
- status = 0, By default
- 1, Cancel
- 2, Vote is too old to be canceled
-
- offensive code:
- allowed = -3, Don't have enough flags left
- -2, Don't have enough reputation score to do this
- 0, not allowed
- 1, allowed
- status = 0, by default
- 1, can't do it again
- """
- response_data = {
- "allowed": 1,
- "success": 1,
- "status" : 0,
- "count" : 0,
- "message" : ''
- }
-
- def can_vote(vote_score, user):
- if vote_score == 1:
- return can_vote_up(request.user)
- else:
- return can_vote_down(request.user)
-
- try:
- if not request.user.is_authenticated():
- response_data['allowed'] = 0
- response_data['success'] = 0
-
- elif request.is_ajax():
- question = get_object_or_404(Question, id=id)
- vote_type = request.POST.get('type')
-
- #accept answer
- if vote_type == '0':
- answer_id = request.POST.get('postId')
- answer = get_object_or_404(Answer, id=answer_id)
- # make sure question author is current user
- if question.author == request.user:
- # answer user who is also question author is not allow to accept answer
- if answer.author == question.author:
- response_data['success'] = 0
- response_data['allowed'] = -1
- # check if answer has been accepted already
- elif answer.accepted:
- onAnswerAcceptCanceled(answer, request.user)
- response_data['status'] = 1
- else:
- # set other answers in this question not accepted first
- for answer_of_question in Answer.objects.get_answers_from_question(question, request.user):
- if answer_of_question != answer and answer_of_question.accepted:
- onAnswerAcceptCanceled(answer_of_question, request.user)
-
- #make sure retrieve data again after above author changes, they may have related data
- answer = get_object_or_404(Answer, id=answer_id)
- onAnswerAccept(answer, request.user)
- else:
- response_data['allowed'] = 0
- response_data['success'] = 0
- # favorite
- elif vote_type == '4':
- has_favorited = False
- fav_questions = FavoriteQuestion.objects.filter(question=question)
- # if the same question has been favorited before, then delete it
- if fav_questions is not None:
- for item in fav_questions:
- if item.user == request.user:
- item.delete()
- response_data['status'] = 1
- response_data['count'] = len(fav_questions) - 1
- if response_data['count'] < 0:
- response_data['count'] = 0
- has_favorited = True
- # if above deletion has not been executed, just insert a new favorite question
- if not has_favorited:
- new_item = FavoriteQuestion(question=question, user=request.user)
- new_item.save()
- response_data['count'] = FavoriteQuestion.objects.filter(question=question).count()
- Question.objects.update_favorite_count(question)
-
- elif vote_type in ['1', '2', '5', '6']:
- post_id = id
- post = question
- vote_score = 1
- if vote_type in ['5', '6']:
- answer_id = request.POST.get('postId')
- answer = get_object_or_404(Answer, id=answer_id)
- post_id = answer_id
- post = answer
- if vote_type in ['2', '6']:
- vote_score = -1
-
- if post.author == request.user:
- response_data['allowed'] = -1
- elif not can_vote(vote_score, request.user):
- response_data['allowed'] = -2
- elif post.votes.filter(user=request.user).count() > 0:
- vote = post.votes.filter(user=request.user)[0]
- # unvote should be less than certain time
- if (datetime.datetime.now().day - vote.voted_at.day) >= VOTE_RULES['scope_deny_unvote_days']:
- response_data['status'] = 2
- else:
- voted = vote.vote
- if voted > 0:
- # cancel upvote
- onUpVotedCanceled(vote, post, request.user)
-
- else:
- # cancel downvote
- onDownVotedCanceled(vote, post, request.user)
-
- response_data['status'] = 1
- response_data['count'] = post.score
- elif Vote.objects.get_votes_count_today_from_user(request.user) >= VOTE_RULES['scope_votes_per_user_per_day']:
- response_data['allowed'] = -3
- else:
- vote = Vote(user=request.user, content_object=post, vote=vote_score, voted_at=datetime.datetime.now())
- if vote_score > 0:
- # upvote
- onUpVoted(vote, post, request.user)
- else:
- # downvote
- onDownVoted(vote, post, request.user)
-
- votes_left = VOTE_RULES['scope_votes_per_user_per_day'] - Vote.objects.get_votes_count_today_from_user(request.user)
- if votes_left <= VOTE_RULES['scope_warn_votes_left']:
- response_data['message'] = u'%s votes left' % votes_left
- response_data['count'] = post.score
- elif vote_type in ['7', '8']:
- post = question
- post_id = id
- if vote_type == '8':
- post_id = request.POST.get('postId')
- post = get_object_or_404(Answer, id=post_id)
-
- if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= VOTE_RULES['scope_flags_per_user_per_day']:
- response_data['allowed'] = -3
- elif not can_flag_offensive(request.user):
- response_data['allowed'] = -2
- elif post.flagged_items.filter(user=request.user).count() > 0:
- response_data['status'] = 1
- else:
- item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now())
- onFlaggedItem(item, post, request.user)
- response_data['count'] = post.offensive_flag_count
- # send signal when question or answer be marked offensive
- mark_offensive.send(sender=post.__class__, instance=post, mark_by=request.user)
- elif vote_type in ['9', '10']:
- post = question
- post_id = id
- if vote_type == '10':
- post_id = request.POST.get('postId')
- post = get_object_or_404(Answer, id=post_id)
-
- if not can_delete_post(request.user, post):
- response_data['allowed'] = -2
- elif post.deleted == True:
- logging.debug('debug restoring post in view')
- onDeleteCanceled(post, request.user)
- response_data['status'] = 1
- else:
- onDeleted(post, request.user)
- delete_post_or_answer.send(sender=post.__class__, instance=post, delete_by=request.user)
- elif vote_type == '11':#subscribe q updates
- user = request.user
- if user.is_authenticated():
- if user not in question.followed_by.all():
- question.followed_by.add(user)
- if settings.EMAIL_VALIDATION == 'on' and user.email_isvalid == False:
- response_data['message'] = \
- _('subscription saved, %(email)s needs validation, see %(details_url)s') \
- % {'email':user.email,'details_url':reverse('faq') + '#validate'}
- feed_setting = EmailFeedSetting.objects.get(subscriber=user,feed_type='q_sel')
- if feed_setting.frequency == 'n':
- feed_setting.frequency = 'd'
- feed_setting.save()
- if 'message' in response_data:
- response_data['message'] += '<br/>'
- response_data['message'] = _('email update frequency has been set to daily')
- #response_data['status'] = 1
- #responst_data['allowed'] = 1
- else:
- pass
- #response_data['status'] = 0
- #response_data['allowed'] = 0
- elif vote_type == '12':#unsubscribe q updates
- user = request.user
- if user.is_authenticated():
- if user in question.followed_by.all():
- question.followed_by.remove(user)
- else:
- response_data['success'] = 0
- response_data['message'] = u'Request mode is not supported. Please try again.'
-
- data = simplejson.dumps(response_data)
-
- except Exception, e:
- response_data['message'] = str(e)
- data = simplejson.dumps(response_data)
- return HttpResponse(data, mimetype="application/json")
-
-@ajax_login_required
-def mark_tag(request, tag=None, **kwargs):
- action = kwargs['action']
- ts = MarkedTag.objects.filter(user=request.user, tag__name=tag)
- if action == 'remove':
- logging.debug('deleting tag %s' % tag)
- ts.delete()
- else:
- reason = kwargs['reason']
- if len(ts) == 0:
- try:
- t = Tag.objects.get(name=tag)
- mt = MarkedTag(user=request.user, reason=reason, tag=t)
- mt.save()
- except:
- pass
- else:
- ts.update(reason=reason)
- return HttpResponse(simplejson.dumps(''), mimetype="application/json")
-
-@ajax_login_required
-def ajax_toggle_ignored_questions(request):
- if request.user.hide_ignored_questions:
- new_hide_setting = False
- else:
- new_hide_setting = True
- request.user.hide_ignored_questions = new_hide_setting
- request.user.save()
-
-@ajax_method
-def ajax_command(request):
- if 'command' not in request.POST:
- return HttpResponseForbidden(mimetype="application/json")
- if request.POST['command'] == 'toggle-ignored-questions':
- return ajax_toggle_ignored_questions(request)
-
-def users(request):
- is_paginated = True
- sortby = request.GET.get('sort', 'reputation')
- suser = request.REQUEST.get('q', "")
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- if suser == "":
- if sortby == "newest":
- objects_list = Paginator(User.objects.all().order_by('-date_joined'), USERS_PAGE_SIZE)
- elif sortby == "last":
- objects_list = Paginator(User.objects.all().order_by('date_joined'), USERS_PAGE_SIZE)
- elif sortby == "user":
- objects_list = Paginator(User.objects.all().order_by('username'), USERS_PAGE_SIZE)
- # default
- else:
- objects_list = Paginator(User.objects.all().order_by('-reputation'), USERS_PAGE_SIZE)
- base_url = reverse('users') + '?sort=%s&' % sortby
- else:
- sortby = "reputation"
- objects_list = Paginator(User.objects.extra(where=['username like %s'], params=['%' + suser + '%']).order_by('-reputation'), USERS_PAGE_SIZE)
- base_url = reverse('users') + '?name=%s&sort=%s&' % (suser, sortby)
-
- try:
- users = objects_list.page(page)
- except (EmptyPage, InvalidPage):
- users = objects_list.page(objects_list.num_pages)
-
- return render_to_response('users.html', {
- "users" : users,
- "suser" : suser,
- "keywords" : suser,
- "tab_id" : sortby,
- "context" : {
- 'is_paginated' : is_paginated,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': users.has_previous(),
- 'has_next': users.has_next(),
- 'previous': users.previous_page_number(),
- 'next': users.next_page_number(),
- 'base_url' : base_url
- }
-
- }, context_instance=RequestContext(request))
-
-def user(request, id):
- sort = request.GET.get('sort', 'stats')
- user_view = dict((v.id, v) for v in USER_TEMPLATE_VIEWS).get(sort, USER_TEMPLATE_VIEWS[0])
- from forum import views
- func = getattr(views, user_view.view_name)
- return func(request, id, user_view)
-
-@login_required
-def moderate_user(request, id):
- """ajax handler of user moderation
- """
- if not auth.can_moderate_users(request.user) or request.method != 'POST':
- raise Http404
- if not request.is_ajax():
- return HttpResponseForbidden(mimetype="application/json")
-
- user = get_object_or_404(User, id=id)
- form = ModerateUserForm(request.POST, instance=user)
-
- if form.is_valid():
- form.save()
- logging.debug('data saved')
- response = HttpResponse(simplejson.dumps(''), mimetype="application/json")
- else:
- response = HttpResponseForbidden(mimetype="application/json")
- return response
-
-@login_required
-def edit_user(request, id):
- user = get_object_or_404(User, id=id)
- if request.user != user:
- raise Http404
- if request.method == "POST":
- form = EditUserForm(user, request.POST)
- if form.is_valid():
- new_email = sanitize_html(form.cleaned_data['email'])
-
- from django_authopenid.views import set_new_email
- set_new_email(user, new_email)
-
- #user.username = sanitize_html(form.cleaned_data['username'])
- user.real_name = sanitize_html(form.cleaned_data['realname'])
- user.website = sanitize_html(form.cleaned_data['website'])
- user.location = sanitize_html(form.cleaned_data['city'])
- user.date_of_birth = sanitize_html(form.cleaned_data['birthday'])
- if len(user.date_of_birth) == 0:
- user.date_of_birth = '1900-01-01'
- user.about = sanitize_html(form.cleaned_data['about'])
-
- user.save()
- # send user updated singal if full fields have been updated
- if user.email and user.real_name and user.website and user.location and \
- user.date_of_birth and user.about:
- user_updated.send(sender=user.__class__, instance=user, updated_by=user)
- return HttpResponseRedirect(user.get_profile_url())
- else:
- form = EditUserForm(user)
- return render_to_response('user_edit.html', {
- 'form' : form,
- 'gravatar_faq_url' : reverse('faq') + '#gravatar',
- }, context_instance=RequestContext(request))
-
-def user_stats(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- questions = Question.objects.extra(
- select={
- 'vote_count' : 'question.score',
- 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s AND f.question_id = question.id',
- 'la_user_id' : 'auth_user.id',
- 'la_username' : 'auth_user.username',
- 'la_user_gold' : 'auth_user.gold',
- 'la_user_silver' : 'auth_user.silver',
- 'la_user_bronze' : 'auth_user.bronze',
- 'la_user_reputation' : 'auth_user.reputation'
- },
- select_params=[user_id],
- tables=['question', 'auth_user'],
- where=['question.deleted = 0 AND question.author_id=%s AND question.last_activity_by_id = auth_user.id'],
- params=[user_id],
- order_by=['-vote_count', '-last_activity_at']
- ).values('vote_count',
- 'favorited_myself',
- 'id',
- 'title',
- 'author_id',
- 'added_at',
- 'answer_accepted',
- 'answer_count',
- 'comment_count',
- 'view_count',
- 'favourite_count',
- 'summary',
- 'tagnames',
- 'vote_up_count',
- 'vote_down_count',
- 'last_activity_at',
- 'la_user_id',
- 'la_username',
- 'la_user_gold',
- 'la_user_silver',
- 'la_user_bronze',
- 'la_user_reputation')[:100]
-
- answered_questions = Question.objects.extra(
- select={
- 'vote_up_count' : 'answer.vote_up_count',
- 'vote_down_count' : 'answer.vote_down_count',
- 'answer_id' : 'answer.id',
- 'accepted' : 'answer.accepted',
- 'vote_count' : 'answer.score',
- 'comment_count' : 'answer.comment_count'
- },
- tables=['question', 'answer'],
- where=['answer.deleted=0 AND question.deleted=0 AND answer.author_id=%s AND answer.question_id=question.id'],
- params=[user_id],
- order_by=['-vote_count', '-answer_id'],
- select_params=[user_id]
- ).distinct().values('comment_count',
- 'id',
- 'answer_id',
- 'title',
- 'author_id',
- 'accepted',
- 'vote_count',
- 'answer_count',
- 'vote_up_count',
- 'vote_down_count')[:100]
-
- up_votes = Vote.objects.get_up_vote_count_from_user(user)
- down_votes = Vote.objects.get_down_vote_count_from_user(user)
- votes_today = Vote.objects.get_votes_count_today_from_user(user)
- votes_total = VOTE_RULES['scope_votes_per_user_per_day']
-
- question_id_set = set(map(lambda v: v['id'], list(questions))) \
- | set(map(lambda v: v['id'], list(answered_questions)))
-
- user_tags = Tag.objects.filter(questions__id__in = question_id_set)
- try:
- from django.db.models import Count
- awards = Award.objects.extra(
- select={'id': 'badge.id',
- 'name':'badge.name',
- 'description': 'badge.description',
- 'type': 'badge.type'},
- tables=['award', 'badge'],
- order_by=['-awarded_at'],
- where=['user_id=%s AND badge_id=badge.id'],
- params=[user.id]
- ).values('id', 'name', 'description', 'type')
- total_awards = awards.count()
- awards = awards.annotate(count = Count('badge__id'))
- user_tags = user_tags.annotate(user_tag_usage_count=Count('name'))
-
- except ImportError:
- awards = Award.objects.extra(
- select={'id': 'badge.id',
- 'count': 'count(badge_id)',
- 'name':'badge.name',
- 'description': 'badge.description',
- 'type': 'badge.type'},
- tables=['award', 'badge'],
- order_by=['-awarded_at'],
- where=['user_id=%s AND badge_id=badge.id'],
- params=[user.id]
- ).values('id', 'count', 'name', 'description', 'type')
- total_awards = awards.count()
- awards.query.group_by = ['badge_id']
-
- user_tags = user_tags.extra(
- select={'user_tag_usage_count': 'COUNT(1)',},
- order_by=['-user_tag_usage_count'],
- )
- user_tags.query.group_by = ['name']
-
- if auth.can_moderate_users(request.user):
- moderate_user_form = ModerateUserForm(instance=user)
- else:
- moderate_user_form = None
-
- return render_to_response(user_view.template_file,{
- 'moderate_user_form': moderate_user_form,
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "view_user" : user,
- "questions" : questions,
- "answered_questions" : answered_questions,
- "up_votes" : up_votes,
- "down_votes" : down_votes,
- "total_votes": up_votes + down_votes,
- "votes_today_left": votes_total-votes_today,
- "votes_total_per_day": votes_total,
- "user_tags" : user_tags[:50],
- "tags" : tags,
- "awards": awards,
- "total_awards" : total_awards,
- }, context_instance=RequestContext(request))
-
-def user_recent(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- def get_type_name(type_id):
- for item in TYPE_ACTIVITY:
- if type_id in item:
- return item[1]
-
- class Event:
- def __init__(self, time, type, title, summary, answer_id, question_id):
- self.time = time
- self.type = get_type_name(type)
- self.type_id = type
- self.title = title
- self.summary = summary
- slug_title = slugify(title)
- self.title_link = reverse('question', kwargs={'id':question_id}) + u'%s' % slug_title
- if int(answer_id) > 0:
- self.title_link += '#%s' % answer_id
-
- class AwardEvent:
- def __init__(self, time, type, id):
- self.time = time
- self.type = get_type_name(type)
- self.type_id = type
- self.badge = get_object_or_404(Badge, id=id)
-
- activities = []
- # ask questions
- questions = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'active_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'question'],
- where=['activity.content_type_id = %s AND activity.object_id = ' +
- 'question.id AND question.deleted=0 AND activity.user_id = %s AND activity.activity_type = %s'],
- params=[question_type_id, user_id, TYPE_ACTIVITY_ASK_QUESTION],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'active_at',
- 'activity_type'
- )
- if len(questions) > 0:
- questions = [(Event(q['active_at'], q['activity_type'], q['title'], '', '0', \
- q['question_id'])) for q in questions]
- activities.extend(questions)
-
- # answers
- answers = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'active_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'answer', 'question'],
- where=['activity.content_type_id = %s AND activity.object_id = answer.id AND ' +
- 'answer.question_id=question.id AND answer.deleted=0 AND activity.user_id=%s AND '+
- 'activity.activity_type=%s AND question.deleted=0'],
- params=[answer_type_id, user_id, TYPE_ACTIVITY_ANSWER],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'active_at',
- 'activity_type'
- )
- if len(answers) > 0:
- answers = [(Event(q['active_at'], q['activity_type'], q['title'], '', q['answer_id'], \
- q['question_id'])) for q in answers]
- activities.extend(answers)
-
- # question comments
- comments = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'comment.object_id',
- 'added_at' : 'comment.added_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'question', 'comment'],
-
- where=['activity.content_type_id = %s AND activity.object_id = comment.id AND '+
- 'activity.user_id = comment.user_id AND comment.object_id=question.id AND '+
- 'comment.content_type_id=%s AND activity.user_id = %s AND activity.activity_type=%s AND ' +
- 'question.deleted=0'],
- params=[comment_type_id, question_type_id, user_id, TYPE_ACTIVITY_COMMENT_QUESTION],
- order_by=['-comment.added_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'activity_type'
- )
-
- if len(comments) > 0:
- comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \
- q['question_id'])) for q in comments]
- activities.extend(comments)
-
- # answer comments
- comments = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'comment.added_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'question', 'answer', 'comment'],
-
- where=['activity.content_type_id = %s AND activity.object_id = comment.id AND '+
- 'activity.user_id = comment.user_id AND comment.object_id=answer.id AND '+
- 'comment.content_type_id=%s AND question.id = answer.question_id AND '+
- 'activity.user_id = %s AND activity.activity_type=%s AND '+
- 'answer.deleted=0 AND question.deleted=0'],
- params=[comment_type_id, answer_type_id, user_id, TYPE_ACTIVITY_COMMENT_ANSWER],
- order_by=['-comment.added_at']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'activity_type'
- )
-
- if len(comments) > 0:
- comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', q['answer_id'], \
- q['question_id'])) for q in comments]
- activities.extend(comments)
-
- # question revisions
- revisions = Activity.objects.extra(
- select={
- 'title' : 'question_revision.title',
- 'question_id' : 'question_revision.question_id',
- 'added_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type',
- 'summary' : 'question_revision.summary'
- },
- tables=['activity', 'question_revision', 'question'],
- where=['activity.content_type_id = %s AND activity.object_id = question_revision.id AND '+
- 'question_revision.id=question.id AND question.deleted=0 AND '+
- 'activity.user_id = question_revision.author_id AND activity.user_id = %s AND '+
- 'activity.activity_type=%s'],
- params=[question_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_QUESTION],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'activity_type',
- 'summary'
- )
-
- if len(revisions) > 0:
- revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], '0', \
- q['question_id'])) for q in revisions]
- activities.extend(revisions)
-
- # answer revisions
- revisions = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type',
- 'summary' : 'answer_revision.summary'
- },
- tables=['activity', 'answer_revision', 'question', 'answer'],
-
- where=['activity.content_type_id = %s AND activity.object_id = answer_revision.id AND '+
- 'activity.user_id = answer_revision.author_id AND activity.user_id = %s AND '+
- 'answer_revision.answer_id=answer.id AND answer.question_id = question.id AND '+
- 'question.deleted=0 AND answer.deleted=0 AND '+
- 'activity.activity_type=%s'],
- params=[answer_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_ANSWER],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'answer_id',
- 'activity_type',
- 'summary'
- )
-
- if len(revisions) > 0:
- revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], \
- q['answer_id'], q['question_id'])) for q in revisions]
- activities.extend(revisions)
-
- # accepted answers
- accept_answers = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'added_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type',
- },
- tables=['activity', 'answer', 'question'],
- where=['activity.content_type_id = %s AND activity.object_id = answer.id AND '+
- 'activity.user_id = question.author_id AND activity.user_id = %s AND '+
- 'answer.deleted=0 AND question.deleted=0 AND '+
- 'answer.question_id=question.id AND activity.activity_type=%s'],
- params=[answer_type_id, user_id, TYPE_ACTIVITY_MARK_ANSWER],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'activity_type',
- )
- if len(accept_answers) > 0:
- accept_answers = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \
- q['question_id'])) for q in accept_answers]
- activities.extend(accept_answers)
- #award history
- awards = Activity.objects.extra(
- select={
- 'badge_id' : 'badge.id',
- 'awarded_at': 'award.awarded_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'award', 'badge'],
- where=['activity.user_id = award.user_id AND activity.user_id = %s AND '+
- 'award.badge_id=badge.id AND activity.object_id=award.id AND activity.activity_type=%s'],
- params=[user_id, TYPE_ACTIVITY_PRIZE],
- order_by=['-activity.active_at']
- ).values(
- 'badge_id',
- 'awarded_at',
- 'activity_type'
- )
- if len(awards) > 0:
- awards = [(AwardEvent(q['awarded_at'], q['activity_type'], q['badge_id'])) for q in awards]
- activities.extend(awards)
-
- activities.sort(lambda x,y: cmp(y.time, x.time))
-
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "view_user" : user,
- "activities" : activities[:user_view.data_size]
- }, context_instance=RequestContext(request))
-
-def user_responses(request, user_id, user_view):
- """
- We list answers for question, comments, and answer accepted by others for this user.
- """
- class Response:
- def __init__(self, type, title, question_id, answer_id, time, username, user_id, content):
- self.type = type
- self.title = title
- self.titlelink = reverse('question', args=[question_id]) + u'%s#%s' % (slugify(title), answer_id)
- self.time = time
- self.userlink = reverse('users') + u'%s/%s/' % (user_id, username)
- self.username = username
- self.content = u'%s ...' % strip_tags(content)[:300]
-
- def __unicode__(self):
- return u'%s %s' % (self.type, self.titlelink)
-
- user = get_object_or_404(User, id=user_id)
- responses = []
- answers = Answer.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'answer.added_at',
- 'html' : 'answer.html',
- 'username' : 'auth_user.username',
- 'user_id' : 'auth_user.id'
- },
- select_params=[user_id],
- tables=['answer', 'question', 'auth_user'],
- where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+
- 'question.author_id = %s AND answer.author_id <> %s AND answer.author_id=auth_user.id'],
- params=[user_id, user_id],
- order_by=['-answer.id']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'html',
- 'username',
- 'user_id'
- )
- if len(answers) > 0:
- answers = [(Response(TYPE_RESPONSE['QUESTION_ANSWERED'], a['title'], a['question_id'],
- a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
- responses.extend(answers)
-
-
- # question comments
- comments = Comment.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'comment.object_id',
- 'added_at' : 'comment.added_at',
- 'comment' : 'comment.comment',
- 'username' : 'auth_user.username',
- 'user_id' : 'auth_user.id'
- },
- tables=['question', 'auth_user', 'comment'],
- where=['question.deleted = 0 AND question.author_id = %s AND comment.object_id=question.id AND '+
- 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id'],
- params=[user_id, question_type_id, user_id],
- order_by=['-comment.added_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'comment',
- 'username',
- 'user_id'
- )
-
- if len(comments) > 0:
- comments = [(Response(TYPE_RESPONSE['QUESTION_COMMENTED'], c['title'], c['question_id'],
- '', c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments]
- responses.extend(comments)
-
- # answer comments
- comments = Comment.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'comment.added_at',
- 'comment' : 'comment.comment',
- 'username' : 'auth_user.username',
- 'user_id' : 'auth_user.id'
- },
- tables=['answer', 'auth_user', 'comment', 'question'],
- where=['answer.deleted = 0 AND answer.author_id = %s AND comment.object_id=answer.id AND '+
- 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id '+
- 'AND question.id = answer.question_id'],
- params=[user_id, answer_type_id, user_id],
- order_by=['-comment.added_at']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'comment',
- 'username',
- 'user_id'
- )
-
- if len(comments) > 0:
- comments = [(Response(TYPE_RESPONSE['ANSWER_COMMENTED'], c['title'], c['question_id'],
- c['answer_id'], c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments]
- responses.extend(comments)
-
- # answer has been accepted
- answers = Answer.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'answer.accepted_at',
- 'html' : 'answer.html',
- 'username' : 'auth_user.username',
- 'user_id' : 'auth_user.id'
- },
- select_params=[user_id],
- tables=['answer', 'question', 'auth_user'],
- where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+
- 'answer.author_id = %s AND answer.accepted=1 AND question.author_id=auth_user.id'],
- params=[user_id],
- order_by=['-answer.id']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'html',
- 'username',
- 'user_id'
- )
- if len(answers) > 0:
- answers = [(Response(TYPE_RESPONSE['ANSWER_ACCEPTED'], a['title'], a['question_id'],
- a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
- responses.extend(answers)
-
- # sort posts by time
- responses.sort(lambda x,y: cmp(y.time, x.time))
-
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "view_user" : user,
- "responses" : responses[:user_view.data_size],
-
- }, context_instance=RequestContext(request))
-
-def user_votes(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- if not can_view_user_votes(request.user, user):
- raise Http404
- votes = []
- question_votes = Vote.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 0,
- 'voted_at' : 'vote.voted_at',
- 'vote' : 'vote',
- },
- select_params=[user_id],
- tables=['vote', 'question', 'auth_user'],
- where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = question.id '+
- 'AND vote.user_id=auth_user.id'],
- params=[question_type_id, user_id],
- order_by=['-vote.id']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'voted_at',
- 'vote',
- )
- if(len(question_votes) > 0):
- votes.extend(question_votes)
-
- answer_votes = Vote.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'voted_at' : 'vote.voted_at',
- 'vote' : 'vote',
- },
- select_params=[user_id],
- tables=['vote', 'answer', 'question', 'auth_user'],
- where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = answer.id '+
- 'AND answer.question_id = question.id AND vote.user_id=auth_user.id'],
- params=[answer_type_id, user_id],
- order_by=['-vote.id']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'voted_at',
- 'vote',
- )
- if(len(answer_votes) > 0):
- votes.extend(answer_votes)
- votes.sort(lambda x,y: cmp(y['voted_at'], x['voted_at']))
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "view_user" : user,
- "votes" : votes[:user_view.data_size]
-
- }, context_instance=RequestContext(request))
-
-def user_reputation(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- try:
- from django.db.models import Sum
- reputation = Repute.objects.extra(
- select={'question_id':'question_id',
- 'title': 'question.title'},
- tables=['repute', 'question'],
- order_by=['-reputed_at'],
- where=['user_id=%s AND question_id=question.id'],
- params=[user.id]
- ).values('question_id', 'title', 'reputed_at', 'reputation')
- reputation = reputation.annotate(positive=Sum("positive"), negative=Sum("negative"))
- except ImportError:
- reputation = Repute.objects.extra(
- select={'positive':'sum(positive)', 'negative':'sum(negative)', 'question_id':'question_id',
- 'title': 'question.title'},
- tables=['repute', 'question'],
- order_by=['-reputed_at'],
- where=['user_id=%s AND question_id=question.id'],
- params=[user.id]
- ).values('positive', 'negative', 'question_id', 'title', 'reputed_at', 'reputation')
- reputation.query.group_by = ['question_id']
-
- rep_list = []
- for rep in Repute.objects.filter(user=user).order_by('reputed_at'):
- dic = '[%s,%s]' % (calendar.timegm(rep.reputed_at.timetuple()) * 1000, rep.reputation)
- rep_list.append(dic)
- reps = ','.join(rep_list)
- reps = '[%s]' % reps
-
- return render_to_response(user_view.template_file, {
- "tab_name": user_view.id,
- "tab_description": user_view.tab_description,
- "page_title": user_view.page_title,
- "view_user": user,
- "reputation": reputation,
- "reps": reps
- }, context_instance=RequestContext(request))
-
-def user_favorites(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- questions = Question.objects.extra(
- select={
- 'vote_count' : 'question.vote_up_count + question.vote_down_count',
- 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s '+
- 'AND f.question_id = question.id',
- 'la_user_id' : 'auth_user.id',
- 'la_username' : 'auth_user.username',
- 'la_user_gold' : 'auth_user.gold',
- 'la_user_silver' : 'auth_user.silver',
- 'la_user_bronze' : 'auth_user.bronze',
- 'la_user_reputation' : 'auth_user.reputation'
- },
- select_params=[user_id],
- tables=['question', 'auth_user', 'favorite_question'],
- where=['question.deleted = 0 AND question.last_activity_by_id = auth_user.id '+
- 'AND favorite_question.question_id = question.id AND favorite_question.user_id = %s'],
- params=[user_id],
- order_by=['-vote_count', '-question.id']
- ).values('vote_count',
- 'favorited_myself',
- 'id',
- 'title',
- 'author_id',
- 'added_at',
- 'answer_accepted',
- 'answer_count',
- 'comment_count',
- 'view_count',
- 'favourite_count',
- 'summary',
- 'tagnames',
- 'vote_up_count',
- 'vote_down_count',
- 'last_activity_at',
- 'la_user_id',
- 'la_username',
- 'la_user_gold',
- 'la_user_silver',
- 'la_user_bronze',
- 'la_user_reputation')
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "questions" : questions[:user_view.data_size],
- "view_user" : user
- }, context_instance=RequestContext(request))
-
-def user_email_subscriptions(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- if request.method == 'POST':
- email_feeds_form = EditUserEmailFeedsForm(request.POST)
- tag_filter_form = TagFilterSelectionForm(request.POST, instance=user)
- if email_feeds_form.is_valid() and tag_filter_form.is_valid():
-
- action_status = None
- tag_filter_saved = tag_filter_form.save()
- if tag_filter_saved:
- action_status = _('changes saved')
- if 'save' in request.POST:
- feeds_saved = email_feeds_form.save(user)
- if feeds_saved:
- action_status = _('changes saved')
- elif 'stop_email' in request.POST:
- email_stopped = email_feeds_form.reset().save(user)
- initial_values = EditUserEmailFeedsForm.NO_EMAIL_INITIAL
- email_feeds_form = EditUserEmailFeedsForm(initial=initial_values)
- if email_stopped:
- action_status = _('email updates canceled')
- else:
- email_feeds_form = EditUserEmailFeedsForm()
- email_feeds_form.set_initial_values(user)
- tag_filter_form = TagFilterSelectionForm(instance=user)
- action_status = None
- return render_to_response(user_view.template_file,{
- 'tab_name':user_view.id,
- 'tab_description':user_view.tab_description,
- 'page_title':user_view.page_title,
- 'view_user':user,
- 'email_feeds_form':email_feeds_form,
- 'tag_filter_selection_form':tag_filter_form,
- 'action_status':action_status,
- }, context_instance=RequestContext(request))
-
-def question_comments(request, id):
- question = get_object_or_404(Question, id=id)
- user = request.user
- return __comments(request, question, 'question')
-
-def answer_comments(request, id):
- answer = get_object_or_404(Answer, id=id)
- user = request.user
- return __comments(request, answer, 'answer')
-
-def __comments(request, obj, type):
- # only support get comments by ajax now
- user = request.user
- if request.is_ajax():
- if request.method == "GET":
- response = __generate_comments_json(obj, type, user)
- elif request.method == "POST":
- if auth.can_add_comments(user,obj):
- comment_data = request.POST.get('comment')
- comment = Comment(content_object=obj, comment=comment_data, user=request.user)
- comment.save()
- obj.comment_count = obj.comment_count + 1
- obj.save()
- response = __generate_comments_json(obj, type, user)
- else:
- response = HttpResponseForbidden(mimetype="application/json")
- return response
-
-def __generate_comments_json(obj, type, user):
- comments = obj.comments.all().order_by('id')
- # {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
- json_comments = []
- from forum.templatetags.extra_tags import diff_date
- for comment in comments:
- comment_user = comment.user
- delete_url = ""
- if user != None and auth.can_delete_comment(user, comment):
- #/posts/392845/comments/219852/delete
- #todo translate this url
- delete_url = reverse(index) + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id)
- json_comments.append({"id" : comment.id,
- "object_id" : obj.id,
- "comment_age" : diff_date(comment.added_at),
- "text" : comment.comment,
- "user_display_name" : comment_user.username,
- "user_url" : comment_user.get_profile_url(),
- "delete_url" : delete_url
- })
-
- data = simplejson.dumps(json_comments)
- return HttpResponse(data, mimetype="application/json")
-
-def delete_comment(request, object_id='', comment_id='', commented_object_type=None):
- response = None
- commented_object = None
- if commented_object_type == 'question':
- commented_object = Question
- elif commented_object_type == 'answer':
- commented_object = Answer
-
- if request.is_ajax():
- comment = get_object_or_404(Comment, id=comment_id)
- if auth.can_delete_comment(request.user, comment):
- obj = get_object_or_404(commented_object, id=object_id)
- obj.comments.remove(comment)
- obj.comment_count = obj.comment_count - 1
- obj.save()
- user = request.user
- return __generate_comments_json(obj, commented_object_type, user)
- raise PermissionDenied()
-
-def logout(request):
- return render_to_response('logout.html', {
- 'next' : get_next_url(request),
- }, context_instance=RequestContext(request))
-
-def badges(request):
- badges = Badge.objects.all().order_by('type')
- my_badges = []
- if request.user.is_authenticated():
- my_badges = Award.objects.filter(user=request.user)
- my_badges.query.group_by = ['badge_id']
-
- return render_to_response('badges.html', {
- 'badges' : badges,
- 'mybadges' : my_badges,
- 'feedback_faq_url' : reverse('feedback'),
- }, context_instance=RequestContext(request))
-
-def badge(request, id):
- badge = get_object_or_404(Badge, id=id)
- awards = Award.objects.extra(
- select={'id': 'auth_user.id',
- 'name': 'auth_user.username',
- 'rep':'auth_user.reputation',
- 'gold': 'auth_user.gold',
- 'silver': 'auth_user.silver',
- 'bronze': 'auth_user.bronze'},
- tables=['award', 'auth_user'],
- where=['badge_id=%s AND user_id=auth_user.id'],
- params=[id]
- ).distinct('id')
-
- return render_to_response('badge.html', {
- 'awards' : awards,
- 'badge' : badge,
- }, context_instance=RequestContext(request))
-
-def read_message(request):
- if request.method == "POST":
- if request.POST['formdata'] == 'required':
- request.session['message_silent'] = 1
- if request.user.is_authenticated():
- request.user.delete_messages()
- return HttpResponse('')
-
-def upload(request):
- class FileTypeNotAllow(Exception):
- pass
- class FileSizeNotAllow(Exception):
- pass
- class UploadPermissionNotAuthorized(Exception):
- pass
-
- #<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>
- xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>"
-
- try:
- f = request.FILES['file-upload']
- # check upload permission
- if not can_upload_files(request.user):
- raise UploadPermissionNotAuthorized
-
- # check file type
- file_name_suffix = os.path.splitext(f.name)[1].lower()
- if not file_name_suffix in settings.ALLOW_FILE_TYPES:
- raise FileTypeNotAllow
-
- # generate new file name
- new_file_name = str(time.time()).replace('.', str(random.randint(0,100000))) + file_name_suffix
- # use default storage to store file
- default_storage.save(new_file_name, f)
- # check file size
- # byte
- size = default_storage.size(new_file_name)
- if size > settings.ALLOW_MAX_FILE_SIZE:
- default_storage.delete(new_file_name)
- raise FileSizeNotAllow
-
- result = xml_template % ('Good', '', default_storage.url(new_file_name))
- except UploadPermissionNotAuthorized:
- result = xml_template % ('', _('uploading images is limited to users with >60 reputation points'), '')
- except FileTypeNotAllow:
- result = xml_template % ('', _("allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"), '')
- except FileSizeNotAllow:
- result = xml_template % ('', _("maximum upload file size is %sK") % settings.ALLOW_MAX_FILE_SIZE / 1024, '')
- except Exception:
- result = xml_template % ('', _('Error uploading file. Please contact the site administrator. Thank you. %s' % Exception), '')
-
- return HttpResponse(result, mimetype="application/xml")
-
-def books(request):
- return HttpResponseRedirect(reverse('books') + '/mysql-zhaoyang')
-
-def book(request, short_name, unanswered=False):
- """
- 1. questions list
- 2. book info
- 3. author info and blog rss items
- """
- """
- List of Questions, Tagged questions, and Unanswered questions.
- """
- books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
- match_count = len(books)
- if match_count == 0:
- raise Http404
- else:
- # the book info
- book = books[0]
- # get author info
- author_info = BookAuthorInfo.objects.get(book=book)
- # get author rss info
- author_rss = BookAuthorRss.objects.filter(book=book)
-
- # get pagesize from session, if failed then get default value
- user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
- # set pagesize equal to logon user specified value in database
- if request.user.is_authenticated() and request.user.questions_per_page > 0:
- user_page_size = request.user.questions_per_page
-
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- view_id = "latest"
- orderby = "-added_at"
-
- # check if request is from tagged questions
- if unanswered:
- # check if request is from unanswered questions
- # Article.objects.filter(publications__id__exact=1)
- objects = Question.objects.filter(book__id__exact=book.id, deleted=False, answer_count=0).order_by(orderby)
- else:
- objects = Question.objects.filter(book__id__exact=book.id, deleted=False).order_by(orderby)
-
- # RISK - inner join queries
- objects = objects.select_related();
- objects_list = Paginator(objects, user_page_size)
- questions = objects_list.page(page)
-
- return render_to_response('book.html', {
- "book" : book,
- "author_info" : author_info,
- "author_rss" : author_rss,
- "questions" : questions,
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url' : request.path + '?sort=%s&' % view_id,
- 'pagesize' : user_page_size
- }
- }, context_instance=RequestContext(request))
-
-@login_required
-def ask_book(request, short_name):
- if request.method == "POST":
- form = AskForm(request.POST)
- if form.is_valid():
- added_at = datetime.datetime.now()
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- question = Question(
- title = strip_tags(form.cleaned_data['title']),
- author = request.user,
- added_at = added_at,
- last_activity_at = added_at,
- last_activity_by = request.user,
- wiki = form.cleaned_data['wiki'],
- tagnames = form.cleaned_data['tags'].strip(),
- html = html,
- summary = strip_tags(html)[:120]
- )
- if question.wiki:
- question.last_edited_by = question.author
- question.last_edited_at = added_at
- question.wikified_at = added_at
-
- question.save()
-
- # create the first revision
- QuestionRevision.objects.create(
- question = question,
- revision = 1,
- title = question.title,
- author = request.user,
- revised_at = added_at,
- tagnames = question.tagnames,
- summary = CONST['default_version'],
- text = form.cleaned_data['text']
- )
-
- books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
- match_count = len(books)
- if match_count == 1:
- # the book info
- book = books[0]
- book.questions.add(question)
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- form = AskForm()
-
- tags = _get_tags_cache_json()
- return render_to_response('ask.html', {
- 'form' : form,
- 'tags' : tags,
- 'email_validation_faq_url': reverse('faq') + '#validate',
- }, context_instance=RequestContext(request))
-
-def search(request):
- """
- Search by question, user and tag keywords.
- For questions now we only search keywords in question title.
- """
- if request.method == "GET":
- keywords = request.GET.get("q")
- search_type = request.GET.get("t")
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
- if keywords is None:
- return HttpResponseRedirect(reverse(index))
- if search_type == 'tag':
- return HttpResponseRedirect(reverse('tags') + '?q=%s&page=%s' % (keywords.strip(), page))
- elif search_type == "user":
- return HttpResponseRedirect(reverse('users') + '?q=%s&page=%s' % (keywords.strip(), page))
- elif search_type == "question":
-
- template_file = "questions.html"
- # Set flag to False by default. If it is equal to True, then need to be saved.
- pagesize_changed = False
- # get pagesize from session, if failed then get default value
- user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
- # set pagesize equal to logon user specified value in database
- if request.user.is_authenticated() and request.user.questions_per_page > 0:
- user_page_size = request.user.questions_per_page
-
- try:
- page = int(request.GET.get('page', '1'))
- # get new pagesize from UI selection
- pagesize = int(request.GET.get('pagesize', user_page_size))
- if pagesize <> user_page_size:
- pagesize_changed = True
-
- except ValueError:
- page = 1
- pagesize = user_page_size
-
- # save this pagesize to user database
- if pagesize_changed:
- request.session["pagesize"] = pagesize
- if request.user.is_authenticated():
- user = request.user
- user.questions_per_page = pagesize
- user.save()
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- view_id = "latest"
- orderby = "-added_at"
-
- if settings.USE_SPHINX_SEARCH == True:
- #search index is now free of delete questions and answers
- #so there is not "antideleted" filtering here
- objects = Question.search.query(keywords)
- #no related selection either because we're relying on full text search here
- else:
- objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby)
- # RISK - inner join queries
- objects = objects.select_related();
-
- objects_list = Paginator(objects, pagesize)
- questions = objects_list.page(page)
-
- # Get related tags from this page objects
- related_tags = []
- for question in questions.object_list:
- tags = list(question.tags.all())
- for tag in tags:
- if tag not in related_tags:
- related_tags.append(tag)
-
- #if is_search is true in the context, prepend this string to soting tabs urls
- search_uri = "?q=%s&page=%d&t=question" % ("+".join(keywords.split()), page)
-
- return render_to_response(template_file, {
- "questions" : questions,
- "tab_id" : view_id,
- "questions_count" : objects_list.count,
- "tags" : related_tags,
- "searchtag" : None,
- "searchtitle" : keywords,
- "keywords" : keywords,
- "is_unanswered" : False,
- "is_search": True,
- "search_uri": search_uri,
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url' : request.path + '?t=question&q=%s&sort=%s&' % (keywords, view_id),
- 'pagesize' : pagesize
- }}, context_instance=RequestContext(request))
-
- else:
- raise Http404
+# encoding:utf-8
+import os.path
+import time, datetime, calendar, random
+import logging
+from urllib import quote, unquote
+from django.conf import settings
+from django.core.files.storage import default_storage
+from django.shortcuts import render_to_response, get_object_or_404
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
+from django.core.paginator import Paginator, EmptyPage, InvalidPage
+from django.template import RequestContext, loader
+from django.utils.html import *
+from django.utils import simplejson
+from django.core import serializers
+from django.core.mail import mail_admins
+from django.db import transaction
+from django.db.models import Count, Q
+from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import ugettext as _
+from django.utils.datastructures import SortedDict
+from django.template.defaultfilters import slugify
+from django.core.exceptions import PermissionDenied
+
+from utils.html import sanitize_html
+from utils.decorators import ajax_method, ajax_login_required
+from markdown2 import Markdown
+#from lxml.html.diff import htmldiff
+from forum.diff import textDiff as htmldiff
+from forum.forms import *
+from forum.models import *
+from forum.auth import *
+from forum.const import *
+from forum.user import *
+from forum import auth
+from django_authopenid.util import get_next_url
+
+# used in index page
+INDEX_PAGE_SIZE = 20
+INDEX_AWARD_SIZE = 15
+INDEX_TAGS_SIZE = 100
+# used in tags list
+DEFAULT_PAGE_SIZE = 60
+# used in questions
+QUESTIONS_PAGE_SIZE = 10
+# used in users
+USERS_PAGE_SIZE = 35
+# used in answers
+ANSWERS_PAGE_SIZE = 10
+markdowner = Markdown(html4tags=True)
+question_type = ContentType.objects.get_for_model(Question)
+answer_type = ContentType.objects.get_for_model(Answer)
+comment_type = ContentType.objects.get_for_model(Comment)
+question_revision_type = ContentType.objects.get_for_model(QuestionRevision)
+answer_revision_type = ContentType.objects.get_for_model(AnswerRevision)
+repute_type = ContentType.objects.get_for_model(Repute)
+question_type_id = question_type.id
+answer_type_id = answer_type.id
+comment_type_id = comment_type.id
+question_revision_type_id = question_revision_type.id
+answer_revision_type_id = answer_revision_type.id
+repute_type_id = repute_type.id
+def _get_tags_cache_json():
+ tags = Tag.objects.filter(deleted=False).all()
+ tags_list = []
+ for tag in tags:
+ dic = {'n': tag.name, 'c': tag.used_count}
+ tags_list.append(dic)
+ tags = simplejson.dumps(tags_list)
+ return tags
+
+def _get_and_remember_questions_sort_method(request, view_dic, default):
+ if default not in view_dic:
+ raise Exception('default value must be in view_dic')
+
+ q_sort_method = request.REQUEST.get('sort', None)
+ if q_sort_method == None:
+ q_sort_method = request.session.get('questions_sort_method', default)
+
+ if q_sort_method not in view_dic:
+ q_sort_method = default
+ request.session['questions_sort_method'] = q_sort_method
+ return q_sort_method, view_dic[q_sort_method]
+
+def index(request):
+ view_dic = {
+ "latest":"-last_activity_at",
+ "hottest":"-answer_count",
+ "mostvoted":"-score",
+ }
+ view_id, orderby = _get_and_remember_questions_sort_method(request, view_dic, 'latest')
+
+ page_size = request.session.get('pagesize', QUESTIONS_PAGE_SIZE)
+ questions = Question.objects.exclude(deleted=True).order_by(orderby)[:page_size]
+ # RISK - inner join queries
+ questions = questions.select_related()
+ tags = Tag.objects.get_valid_tags(INDEX_TAGS_SIZE)
+
+ awards = Award.objects.get_recent_awards()
+
+ (interesting_tag_names, ignored_tag_names) = (None, None)
+ if request.user.is_authenticated():
+ pt = MarkedTag.objects.filter(user=request.user)
+ interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True)
+ ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True)
+
+ tags_autocomplete = _get_tags_cache_json()
+
+ return render_to_response('index.html', {
+ 'interesting_tag_names': interesting_tag_names,
+ 'tags_autocomplete': tags_autocomplete,
+ 'ignored_tag_names': ignored_tag_names,
+ "questions" : questions,
+ "tab_id" : view_id,
+ "tags" : tags,
+ "awards" : awards[:INDEX_AWARD_SIZE],
+ }, context_instance=RequestContext(request))
+
+def about(request):
+ return render_to_response('about.html', context_instance=RequestContext(request))
+
+def faq(request):
+ data = {
+ 'gravatar_faq_url': reverse('faq') + '#gravatar',
+ 'send_email_key_url': reverse('send_email_key'),
+ 'ask_question_url': reverse('ask'),
+ }
+ return render_to_response('faq.html', data, context_instance=RequestContext(request))
+
+def feedback(request):
+ data = {}
+ form = None
+ if request.method == "POST":
+ form = FeedbackForm(request.POST)
+ if form.is_valid():
+ if not request.user.is_authenticated:
+ data['email'] = form.cleaned_data.get('email',None)
+ data['message'] = form.cleaned_data['message']
+ data['name'] = form.cleaned_data.get('name',None)
+ message = render_to_response('feedback_email.txt',data,context_instance=RequestContext(request))
+ mail_admins(_('Q&A forum feedback'), message)
+ msg = _('Thanks for the feedback!')
+ request.user.message_set.create(message=msg)
+ return HttpResponseRedirect(get_next_url(request))
+ else:
+ form = FeedbackForm(initial={'next':get_next_url(request)})
+
+ data['form'] = form
+ return render_to_response('feedback.html', data, context_instance=RequestContext(request))
+feedback.CANCEL_MESSAGE=_('We look forward to hearing your feedback! Please, give it next time :)')
+
+def privacy(request):
+ return render_to_response('privacy.html', context_instance=RequestContext(request))
+
+def unanswered(request):
+ return questions(request, unanswered=True)
+
+def questions(request, tagname=None, unanswered=False):
+ """
+ List of Questions, Tagged questions, and Unanswered questions.
+ """
+ # template file
+ # "questions.html" or maybe index.html in the future
+ template_file = "questions.html"
+ # Set flag to False by default. If it is equal to True, then need to be saved.
+ pagesize_changed = False
+ # get pagesize from session, if failed then get default value
+ pagesize = request.session.get("pagesize",10)
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+ view_id, orderby = _get_and_remember_questions_sort_method(request,view_dic,'latest')
+
+ # check if request is from tagged questions
+ qs = Question.objects.exclude(deleted=True)
+
+ if tagname is not None:
+ qs = qs.filter(tags__name = unquote(tagname))
+
+ if unanswered:
+ qs = qs.exclude(answer_accepted=True)
+
+ author_name = None
+ #user contributed questions & answers
+ if 'user' in request.GET:
+ try:
+ author_name = request.GET['user']
+ u = User.objects.get(username=author_name)
+ qs = qs.filter(Q(author=u) | Q(answers__author=u))
+ except User.DoesNotExist:
+ author_name = None
+
+ if request.user.is_authenticated():
+ uid_str = str(request.user.id)
+ qs = qs.extra(
+ select = SortedDict([
+ (
+ 'interesting_score',
+ 'SELECT COUNT(1) FROM forum_markedtag, question_tags '
+ + 'WHERE forum_markedtag.user_id = %s '
+ + 'AND forum_markedtag.tag_id = question_tags.tag_id '
+ + 'AND forum_markedtag.reason = "good" '
+ + 'AND question_tags.question_id = question.id'
+ ),
+ ]),
+ select_params = (uid_str,),
+ )
+ if request.user.hide_ignored_questions:
+ ignored_tags = Tag.objects.filter(user_selections__reason='bad',
+ user_selections__user = request.user)
+ qs = qs.exclude(tags__in=ignored_tags)
+ else:
+ qs = qs.extra(
+ select = SortedDict([
+ (
+ 'ignored_score',
+ 'SELECT COUNT(1) FROM forum_markedtag, question_tags '
+ + 'WHERE forum_markedtag.user_id = %s '
+ + 'AND forum_markedtag.tag_id = question_tags.tag_id '
+ + 'AND forum_markedtag.reason = "bad" '
+ + 'AND question_tags.question_id = question.id'
+ )
+ ]),
+ select_params = (uid_str, )
+ )
+
+ qs = qs.select_related(depth=1).order_by(orderby)
+
+ objects_list = Paginator(qs, pagesize)
+ questions = objects_list.page(page)
+
+ # Get related tags from this page objects
+ if questions.object_list.count() > 0:
+ related_tags = Tag.objects.get_tags_by_questions(questions.object_list)
+ else:
+ related_tags = None
+ tags_autocomplete = _get_tags_cache_json()
+
+ # get the list of interesting and ignored tags
+ (interesting_tag_names, ignored_tag_names) = (None, None)
+ if request.user.is_authenticated():
+ pt = MarkedTag.objects.filter(user=request.user)
+ interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True)
+ ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True)
+
+ return render_to_response(template_file, {
+ "questions" : questions,
+ "author_name" : author_name,
+ "tab_id" : view_id,
+ "questions_count" : objects_list.count,
+ "tags" : related_tags,
+ "tags_autocomplete" : tags_autocomplete,
+ "searchtag" : tagname,
+ "is_unanswered" : unanswered,
+ "interesting_tag_names": interesting_tag_names,
+ 'ignored_tag_names': ignored_tag_names,
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?sort=%s&' % view_id,
+ 'pagesize' : pagesize
+ }}, context_instance=RequestContext(request))
+
+def create_new_answer( question=None, author=None,\
+ added_at=None, wiki=False,\
+ text='', email_notify=False):
+
+ html = sanitize_html(markdowner.convert(text))
+
+ #create answer
+ answer = Answer(
+ question = question,
+ author = author,
+ added_at = added_at,
+ wiki = wiki,
+ html = html
+ )
+ if answer.wiki:
+ answer.last_edited_by = answer.author
+ answer.last_edited_at = added_at
+ answer.wikified_at = added_at
+
+ answer.save()
+
+ #update question data
+ question.last_activity_at = added_at
+ question.last_activity_by = author
+ question.save()
+ Question.objects.update_answer_count(question)
+
+ #update revision
+ AnswerRevision.objects.create(
+ answer = answer,
+ revision = 1,
+ author = author,
+ revised_at = added_at,
+ summary = CONST['default_version'],
+ text = text
+ )
+
+ #set notification/delete
+ if email_notify:
+ if author not in question.followed_by.all():
+ question.followed_by.add(author)
+ else:
+ #not sure if this is necessary. ajax should take care of this...
+ try:
+ question.followed_by.remove(author)
+ except:
+ pass
+
+def create_new_question(title=None,author=None,added_at=None,
+ wiki=False,tagnames=None,summary=None,
+ text=None):
+ """this is not a view
+ and maybe should become one of the methods on Question object?
+ """
+ html = sanitize_html(markdowner.convert(text))
+ question = Question(
+ title = title,
+ author = author,
+ added_at = added_at,
+ last_activity_at = added_at,
+ last_activity_by = author,
+ wiki = wiki,
+ tagnames = tagnames,
+ html = html,
+ summary = summary
+ )
+ if question.wiki:
+ question.last_edited_by = question.author
+ question.last_edited_at = added_at
+ question.wikified_at = added_at
+
+ question.save()
+
+ # create the first revision
+ QuestionRevision.objects.create(
+ question = question,
+ revision = 1,
+ title = question.title,
+ author = author,
+ revised_at = added_at,
+ tagnames = question.tagnames,
+ summary = CONST['default_version'],
+ text = text
+ )
+ return question
+
+#TODO: allow anynomus user to ask question by providing email and username.
+#@login_required
+def ask(request):
+ if request.method == "POST":
+ form = AskForm(request.POST)
+ if form.is_valid():
+
+ added_at = datetime.datetime.now()
+ title = strip_tags(form.cleaned_data['title'].strip())
+ wiki = form.cleaned_data['wiki']
+ tagnames = form.cleaned_data['tags'].strip()
+ text = form.cleaned_data['text']
+ html = sanitize_html(markdowner.convert(text))
+ summary = strip_tags(html)[:120]
+
+ if request.user.is_authenticated():
+ author = request.user
+
+ question = create_new_question(
+ title = title,
+ author = author,
+ added_at = added_at,
+ wiki = wiki,
+ tagnames = tagnames,
+ summary = summary,
+ text = text
+ )
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ request.session.flush()
+ session_key = request.session.session_key
+ question = AnonymousQuestion(
+ session_key = session_key,
+ title = title,
+ tagnames = tagnames,
+ wiki = wiki,
+ text = text,
+ summary = summary,
+ added_at = added_at,
+ ip_addr = request.META['REMOTE_ADDR'],
+ )
+ question.save()
+ return HttpResponseRedirect(reverse('user_signin_new_question'))
+ else:
+ form = AskForm()
+
+ tags = _get_tags_cache_json()
+ return render_to_response('ask.html', {
+ 'form' : form,
+ 'tags' : tags,
+ 'email_validation_faq_url':reverse('faq') + '#validate',
+ }, context_instance=RequestContext(request))
+
+def question(request, id):
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ view_id = request.GET.get('sort', None)
+ view_dic = {"latest":"-added_at", "oldest":"added_at", "votes":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ qsm = request.session.get('questions_sort_method',None)
+ if qsm in ('mostvoted','latest'):
+ logging.debug('loaded from session ' + qsm)
+ if qsm == 'mostvoted':
+ view_id = 'votes'
+ orderby = '-score'
+ else:
+ view_id = 'latest'
+ orderby = '-added_at'
+ else:
+ view_id = "votes"
+ orderby = "-score"
+
+ logging.debug('view_id=' + str(view_id))
+
+ question = get_object_or_404(Question, id=id)
+ if question.deleted and not can_view_deleted_post(request.user, question):
+ raise Http404
+ answer_form = AnswerForm(question,request.user)
+ answers = Answer.objects.get_answers_from_question(question, request.user)
+ answers = answers.select_related(depth=1)
+
+ favorited = question.has_favorite_by_user(request.user)
+ if request.user.is_authenticated():
+ question_vote = question.votes.select_related().filter(user=request.user)
+ else:
+ question_vote = None #is this correct?
+ if question_vote is not None and question_vote.count() > 0:
+ question_vote = question_vote[0]
+
+ user_answer_votes = {}
+ for answer in answers:
+ vote = answer.get_user_vote(request.user)
+ if vote is not None and not user_answer_votes.has_key(answer.id):
+ vote_value = -1
+ if vote.is_upvote():
+ vote_value = 1
+ user_answer_votes[answer.id] = vote_value
+
+ if answers is not None:
+ answers = answers.order_by("-accepted", orderby)
+
+ filtered_answers = []
+ for answer in answers:
+ if answer.deleted == True:
+ if answer.author_id == request.user.id:
+ filtered_answers.append(answer)
+ else:
+ filtered_answers.append(answer)
+
+ objects_list = Paginator(filtered_answers, ANSWERS_PAGE_SIZE)
+ page_objects = objects_list.page(page)
+
+ #todo: merge view counts per user and per session
+ #1) view count per session
+ update_view_count = False
+ if 'question_view_times' not in request.session:
+ request.session['question_view_times'] = {}
+
+ last_seen = request.session['question_view_times'].get(question.id,None)
+ updated_when, updated_who = question.get_last_update_info()
+
+ if updated_who != request.user:
+ if last_seen:
+ if last_seen < updated_when:
+ update_view_count = True
+ else:
+ update_view_count = True
+
+ request.session['question_view_times'][question.id] = datetime.datetime.now()
+
+ if update_view_count:
+ question.view_count += 1
+ question.save()
+
+ #2) question view count per user
+ if request.user.is_authenticated():
+ try:
+ question_view = QuestionView.objects.get(who=request.user, question=question)
+ except QuestionView.DoesNotExist:
+ question_view = QuestionView(who=request.user, question=question)
+ question_view.when = datetime.datetime.now()
+ question_view.save()
+
+ return render_to_response('question.html', {
+ "question" : question,
+ "question_vote" : question_vote,
+ "question_comment_count":question.comments.count(),
+ "answer" : answer_form,
+ "answers" : page_objects.object_list,
+ "user_answer_votes": user_answer_votes,
+ "tags" : question.tags.all(),
+ "tab_id" : view_id,
+ "favorited" : favorited,
+ "similar_questions" : Question.objects.get_similar_questions(question),
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': page_objects.has_previous(),
+ 'has_next': page_objects.has_next(),
+ 'previous': page_objects.previous_page_number(),
+ 'next': page_objects.next_page_number(),
+ 'base_url' : request.path + '?sort=%s&' % view_id,
+ 'extend_url' : "#sort-top"
+ }
+ }, context_instance=RequestContext(request))
+
+@login_required
+def close(request, id):
+ question = get_object_or_404(Question, id=id)
+ if not can_close_question(request.user, question):
+ return HttpResponse('Permission denied.')
+ if request.method == 'POST':
+ form = CloseForm(request.POST)
+ if form.is_valid():
+ reason = form.cleaned_data['reason']
+ question.closed = True
+ question.closed_by = request.user
+ question.closed_at = datetime.datetime.now()
+ question.close_reason = reason
+ question.save()
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ form = CloseForm()
+ return render_to_response('close.html', {
+ 'form' : form,
+ 'question' : question,
+ }, context_instance=RequestContext(request))
+
+@login_required
+def reopen(request, id):
+ question = get_object_or_404(Question, id=id)
+ # open question
+ if not can_reopen_question(request.user, question):
+ return HttpResponse('Permission denied.')
+ if request.method == 'POST' :
+ Question.objects.filter(id=question.id).update(closed=False,
+ closed_by=None, closed_at=None, close_reason=None)
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ return render_to_response('reopen.html', {
+ 'question' : question,
+ }, context_instance=RequestContext(request))
+
+@login_required
+def edit_question(request, id):
+ question = get_object_or_404(Question, id=id)
+ if question.deleted and not can_view_deleted_post(request.user, question):
+ raise Http404
+ if can_edit_post(request.user, question):
+ return _edit_question(request, question)
+ elif can_retag_questions(request.user):
+ return _retag_question(request, question)
+ else:
+ raise Http404
+
+def _retag_question(request, question):
+ if request.method == 'POST':
+ form = RetagQuestionForm(question, request.POST)
+ if form.is_valid():
+ if form.has_changed():
+ latest_revision = question.get_latest_revision()
+ retagged_at = datetime.datetime.now()
+ # Update the Question itself
+ Question.objects.filter(id=question.id).update(
+ tagnames = form.cleaned_data['tags'],
+ last_edited_at = retagged_at,
+ last_edited_by = request.user,
+ last_activity_at = retagged_at,
+ last_activity_by = request.user
+ )
+ # Update the Question's tag associations
+ tags_updated = Question.objects.update_tags(question,
+ form.cleaned_data['tags'], request.user)
+ # Create a new revision
+ QuestionRevision.objects.create(
+ question = question,
+ title = latest_revision.title,
+ author = request.user,
+ revised_at = retagged_at,
+ tagnames = form.cleaned_data['tags'],
+ summary = CONST['retagged'],
+ text = latest_revision.text
+ )
+ # send tags updated singal
+ tags_updated.send(sender=question.__class__, question=question)
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ form = RetagQuestionForm(question)
+ return render_to_response('question_retag.html', {
+ 'question': question,
+ 'form' : form,
+ 'tags' : _get_tags_cache_json(),
+ }, context_instance=RequestContext(request))
+
+def _edit_question(request, question):
+ latest_revision = question.get_latest_revision()
+ revision_form = None
+ if request.method == 'POST':
+ if 'select_revision' in request.POST:
+ # user has changed revistion number
+ revision_form = RevisionForm(question, latest_revision, request.POST)
+ if revision_form.is_valid():
+ # Replace with those from the selected revision
+ form = EditQuestionForm(question,
+ QuestionRevision.objects.get(question=question,
+ revision=revision_form.cleaned_data['revision']))
+ else:
+ form = EditQuestionForm(question, latest_revision, request.POST)
+ else:
+ # Always check modifications against the latest revision
+ form = EditQuestionForm(question, latest_revision, request.POST)
+ if form.is_valid():
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
+ if form.has_changed():
+ edited_at = datetime.datetime.now()
+ tags_changed = (latest_revision.tagnames !=
+ form.cleaned_data['tags'])
+ tags_updated = False
+ # Update the Question itself
+ updated_fields = {
+ 'title': form.cleaned_data['title'],
+ 'last_edited_at': edited_at,
+ 'last_edited_by': request.user,
+ 'last_activity_at': edited_at,
+ 'last_activity_by': request.user,
+ 'tagnames': form.cleaned_data['tags'],
+ 'summary': strip_tags(html)[:120],
+ 'html': html,
+ }
+
+ # only save when it's checked
+ # because wiki doesn't allow to be edited if last version has been enabled already
+ # and we make sure this in forms.
+ if ('wiki' in form.cleaned_data and
+ form.cleaned_data['wiki']):
+ updated_fields['wiki'] = True
+ updated_fields['wikified_at'] = edited_at
+
+ Question.objects.filter(
+ id=question.id).update(**updated_fields)
+ # Update the Question's tag associations
+ if tags_changed:
+ tags_updated = Question.objects.update_tags(
+ question, form.cleaned_data['tags'], request.user)
+ # Create a new revision
+ revision = QuestionRevision(
+ question = question,
+ title = form.cleaned_data['title'],
+ author = request.user,
+ revised_at = edited_at,
+ tagnames = form.cleaned_data['tags'],
+ text = form.cleaned_data['text'],
+ )
+ if form.cleaned_data['summary']:
+ revision.summary = form.cleaned_data['summary']
+ else:
+ revision.summary = 'No.%s Revision' % latest_revision.revision
+ revision.save()
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+
+ revision_form = RevisionForm(question, latest_revision)
+ form = EditQuestionForm(question, latest_revision)
+ return render_to_response('question_edit.html', {
+ 'question': question,
+ 'revision_form': revision_form,
+ 'form' : form,
+ 'tags' : _get_tags_cache_json()
+ }, context_instance=RequestContext(request))
+
+
+@login_required
+def edit_answer(request, id):
+ answer = get_object_or_404(Answer, id=id)
+ if answer.deleted and not can_view_deleted_post(request.user, answer):
+ raise Http404
+ elif not can_edit_post(request.user, answer):
+ raise Http404
+ else:
+ latest_revision = answer.get_latest_revision()
+ if request.method == "POST":
+ if 'select_revision' in request.POST:
+ # user has changed revistion number
+ revision_form = RevisionForm(answer, latest_revision, request.POST)
+ if revision_form.is_valid():
+ # Replace with those from the selected revision
+ form = EditAnswerForm(answer,
+ AnswerRevision.objects.get(answer=answer,
+ revision=revision_form.cleaned_data['revision']))
+ else:
+ form = EditAnswerForm(answer, latest_revision, request.POST)
+ else:
+ form = EditAnswerForm(answer, latest_revision, request.POST)
+ if form.is_valid():
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
+ if form.has_changed():
+ edited_at = datetime.datetime.now()
+ updated_fields = {
+ 'last_edited_at': edited_at,
+ 'last_edited_by': request.user,
+ 'html': html,
+ }
+ Answer.objects.filter(id=answer.id).update(**updated_fields)
+
+ revision = AnswerRevision(
+ answer=answer,
+ author=request.user,
+ revised_at=edited_at,
+ text=form.cleaned_data['text']
+ )
+
+ if form.cleaned_data['summary']:
+ revision.summary = form.cleaned_data['summary']
+ else:
+ revision.summary = 'No.%s Revision' % latest_revision.revision
+ revision.save()
+
+ answer.question.last_activity_at = edited_at
+ answer.question.last_activity_by = request.user
+ answer.question.save()
+
+ return HttpResponseRedirect(answer.get_absolute_url())
+ else:
+ revision_form = RevisionForm(answer, latest_revision)
+ form = EditAnswerForm(answer, latest_revision)
+ return render_to_response('answer_edit.html', {
+ 'answer': answer,
+ 'revision_form': revision_form,
+ 'form': form,
+ }, context_instance=RequestContext(request))
+
+QUESTION_REVISION_TEMPLATE = ('<h1>%(title)s</h1>\n'
+ '<div class="text">%(html)s</div>\n'
+ '<div class="tags">%(tags)s</div>')
+def question_revisions(request, id):
+ post = get_object_or_404(Question, id=id)
+ revisions = list(post.revisions.all())
+ revisions.reverse()
+ for i, revision in enumerate(revisions):
+ revision.html = QUESTION_REVISION_TEMPLATE % {
+ 'title': revision.title,
+ 'html': sanitize_html(markdowner.convert(revision.text)),
+ 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
+ for tag in revision.tagnames.split(' ')]),
+ }
+ if i > 0:
+ revisions[i].diff = htmldiff(revisions[i-1].html, revision.html)
+ else:
+ revisions[i].diff = QUESTION_REVISION_TEMPLATE % {
+ 'title': revisions[0].title,
+ 'html': sanitize_html(markdowner.convert(revisions[0].text)),
+ 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
+ for tag in revisions[0].tagnames.split(' ')]),
+ }
+ revisions[i].summary = _('initial version')
+ return render_to_response('revisions_question.html', {
+ 'post': post,
+ 'revisions': revisions,
+ }, context_instance=RequestContext(request))
+
+ANSWER_REVISION_TEMPLATE = ('<div class="text">%(html)s</div>')
+def answer_revisions(request, id):
+ post = get_object_or_404(Answer, id=id)
+ revisions = list(post.revisions.all())
+ revisions.reverse()
+ for i, revision in enumerate(revisions):
+ revision.html = ANSWER_REVISION_TEMPLATE % {
+ 'html': sanitize_html(markdowner.convert(revision.text))
+ }
+ if i > 0:
+ revisions[i].diff = htmldiff(revisions[i-1].html, revision.html)
+ else:
+ revisions[i].diff = revisions[i].text
+ revisions[i].summary = _('initial version')
+ return render_to_response('revisions_answer.html', {
+ 'post': post,
+ 'revisions': revisions,
+ }, context_instance=RequestContext(request))
+
+def answer(request, id):
+ question = get_object_or_404(Question, id=id)
+ if request.method == "POST":
+ form = AnswerForm(question, request.user, request.POST)
+ if form.is_valid():
+ wiki = form.cleaned_data['wiki']
+ text = form.cleaned_data['text']
+ update_time = datetime.datetime.now()
+
+ if request.user.is_authenticated():
+ create_new_answer(
+ question=question,
+ author=request.user,
+ added_at=update_time,
+ wiki=wiki,
+ text=text,
+ email_notify=form.cleaned_data['email_notify']
+ )
+ else:
+ request.session.flush()
+ html = sanitize_html(markdowner.convert(text))
+ summary = strip_tags(html)[:120]
+ anon = AnonymousAnswer(
+ question=question,
+ wiki=wiki,
+ text=text,
+ summary=summary,
+ session_key=request.session.session_key,
+ ip_addr=request.META['REMOTE_ADDR'],
+ )
+ anon.save()
+ return HttpResponseRedirect(reverse('user_signin_new_answer'))
+
+ return HttpResponseRedirect(question.get_absolute_url())
+
+def tags(request):
+ stag = ""
+ is_paginated = True
+ sortby = request.GET.get('sort', 'used')
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ if request.method == "GET":
+ stag = request.GET.get("q", "").strip()
+ if stag != '':
+ objects_list = Paginator(Tag.objects.filter(deleted=False).exclude(used_count=0).extra(where=['name like %s'], params=['%' + stag + '%']), DEFAULT_PAGE_SIZE)
+ else:
+ if sortby == "name":
+ objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("name"), DEFAULT_PAGE_SIZE)
+ else:
+ objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-used_count"), DEFAULT_PAGE_SIZE)
+
+ try:
+ tags = objects_list.page(page)
+ except (EmptyPage, InvalidPage):
+ tags = objects_list.page(objects_list.num_pages)
+
+ return render_to_response('tags.html', {
+ "tags" : tags,
+ "stag" : stag,
+ "tab_id" : sortby,
+ "keywords" : stag,
+ "context" : {
+ 'is_paginated' : is_paginated,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': tags.has_previous(),
+ 'has_next': tags.has_next(),
+ 'previous': tags.previous_page_number(),
+ 'next': tags.next_page_number(),
+ 'base_url' : reverse('tags') + '?sort=%s&' % sortby
+ }
+ }, context_instance=RequestContext(request))
+
+def tag(request, tag):
+ return questions(request, tagname=tag)
+
+def vote(request, id):
+ """
+ vote_type:
+ acceptAnswer : 0,
+ questionUpVote : 1,
+ questionDownVote : 2,
+ favorite : 4,
+ answerUpVote: 5,
+ answerDownVote:6,
+ offensiveQuestion : 7,
+ offensiveAnswer:8,
+ removeQuestion: 9,
+ removeAnswer:10
+ questionSubscribeUpdates:11
+
+ accept answer code:
+ response_data['allowed'] = -1, Accept his own answer 0, no allowed - Anonymous 1, Allowed - by default
+ response_data['success'] = 0, failed 1, Success - by default
+ response_data['status'] = 0, By default 1, Answer has been accepted already(Cancel)
+
+ vote code:
+ allowed = -3, Don't have enough votes left
+ -2, Don't have enough reputation score
+ -1, Vote his own post
+ 0, no allowed - Anonymous
+ 1, Allowed - by default
+ status = 0, By default
+ 1, Cancel
+ 2, Vote is too old to be canceled
+
+ offensive code:
+ allowed = -3, Don't have enough flags left
+ -2, Don't have enough reputation score to do this
+ 0, not allowed
+ 1, allowed
+ status = 0, by default
+ 1, can't do it again
+ """
+ response_data = {
+ "allowed": 1,
+ "success": 1,
+ "status" : 0,
+ "count" : 0,
+ "message" : ''
+ }
+
+ def can_vote(vote_score, user):
+ if vote_score == 1:
+ return can_vote_up(request.user)
+ else:
+ return can_vote_down(request.user)
+
+ try:
+ if not request.user.is_authenticated():
+ response_data['allowed'] = 0
+ response_data['success'] = 0
+
+ elif request.is_ajax():
+ question = get_object_or_404(Question, id=id)
+ vote_type = request.POST.get('type')
+
+ #accept answer
+ if vote_type == '0':
+ answer_id = request.POST.get('postId')
+ answer = get_object_or_404(Answer, id=answer_id)
+ # make sure question author is current user
+ if question.author == request.user:
+ # answer user who is also question author is not allow to accept answer
+ if answer.author == question.author:
+ response_data['success'] = 0
+ response_data['allowed'] = -1
+ # check if answer has been accepted already
+ elif answer.accepted:
+ onAnswerAcceptCanceled(answer, request.user)
+ response_data['status'] = 1
+ else:
+ # set other answers in this question not accepted first
+ for answer_of_question in Answer.objects.get_answers_from_question(question, request.user):
+ if answer_of_question != answer and answer_of_question.accepted:
+ onAnswerAcceptCanceled(answer_of_question, request.user)
+
+ #make sure retrieve data again after above author changes, they may have related data
+ answer = get_object_or_404(Answer, id=answer_id)
+ onAnswerAccept(answer, request.user)
+ else:
+ response_data['allowed'] = 0
+ response_data['success'] = 0
+ # favorite
+ elif vote_type == '4':
+ has_favorited = False
+ fav_questions = FavoriteQuestion.objects.filter(question=question)
+ # if the same question has been favorited before, then delete it
+ if fav_questions is not None:
+ for item in fav_questions:
+ if item.user == request.user:
+ item.delete()
+ response_data['status'] = 1
+ response_data['count'] = len(fav_questions) - 1
+ if response_data['count'] < 0:
+ response_data['count'] = 0
+ has_favorited = True
+ # if above deletion has not been executed, just insert a new favorite question
+ if not has_favorited:
+ new_item = FavoriteQuestion(question=question, user=request.user)
+ new_item.save()
+ response_data['count'] = FavoriteQuestion.objects.filter(question=question).count()
+ Question.objects.update_favorite_count(question)
+
+ elif vote_type in ['1', '2', '5', '6']:
+ post_id = id
+ post = question
+ vote_score = 1
+ if vote_type in ['5', '6']:
+ answer_id = request.POST.get('postId')
+ answer = get_object_or_404(Answer, id=answer_id)
+ post_id = answer_id
+ post = answer
+ if vote_type in ['2', '6']:
+ vote_score = -1
+
+ if post.author == request.user:
+ response_data['allowed'] = -1
+ elif not can_vote(vote_score, request.user):
+ response_data['allowed'] = -2
+ elif post.votes.filter(user=request.user).count() > 0:
+ vote = post.votes.filter(user=request.user)[0]
+ # unvote should be less than certain time
+ if (datetime.datetime.now().day - vote.voted_at.day) >= VOTE_RULES['scope_deny_unvote_days']:
+ response_data['status'] = 2
+ else:
+ voted = vote.vote
+ if voted > 0:
+ # cancel upvote
+ onUpVotedCanceled(vote, post, request.user)
+
+ else:
+ # cancel downvote
+ onDownVotedCanceled(vote, post, request.user)
+
+ response_data['status'] = 1
+ response_data['count'] = post.score
+ elif Vote.objects.get_votes_count_today_from_user(request.user) >= VOTE_RULES['scope_votes_per_user_per_day']:
+ response_data['allowed'] = -3
+ else:
+ vote = Vote(user=request.user, content_object=post, vote=vote_score, voted_at=datetime.datetime.now())
+ if vote_score > 0:
+ # upvote
+ onUpVoted(vote, post, request.user)
+ else:
+ # downvote
+ onDownVoted(vote, post, request.user)
+
+ votes_left = VOTE_RULES['scope_votes_per_user_per_day'] - Vote.objects.get_votes_count_today_from_user(request.user)
+ if votes_left <= VOTE_RULES['scope_warn_votes_left']:
+ response_data['message'] = u'%s votes left' % votes_left
+ response_data['count'] = post.score
+ elif vote_type in ['7', '8']:
+ post = question
+ post_id = id
+ if vote_type == '8':
+ post_id = request.POST.get('postId')
+ post = get_object_or_404(Answer, id=post_id)
+
+ if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= VOTE_RULES['scope_flags_per_user_per_day']:
+ response_data['allowed'] = -3
+ elif not can_flag_offensive(request.user):
+ response_data['allowed'] = -2
+ elif post.flagged_items.filter(user=request.user).count() > 0:
+ response_data['status'] = 1
+ else:
+ item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now())
+ onFlaggedItem(item, post, request.user)
+ response_data['count'] = post.offensive_flag_count
+ # send signal when question or answer be marked offensive
+ mark_offensive.send(sender=post.__class__, instance=post, mark_by=request.user)
+ elif vote_type in ['9', '10']:
+ post = question
+ post_id = id
+ if vote_type == '10':
+ post_id = request.POST.get('postId')
+ post = get_object_or_404(Answer, id=post_id)
+
+ if not can_delete_post(request.user, post):
+ response_data['allowed'] = -2
+ elif post.deleted == True:
+ logging.debug('debug restoring post in view')
+ onDeleteCanceled(post, request.user)
+ response_data['status'] = 1
+ else:
+ onDeleted(post, request.user)
+ delete_post_or_answer.send(sender=post.__class__, instance=post, delete_by=request.user)
+ elif vote_type == '11':#subscribe q updates
+ user = request.user
+ if user.is_authenticated():
+ if user not in question.followed_by.all():
+ question.followed_by.add(user)
+ if settings.EMAIL_VALIDATION == 'on' and user.email_isvalid == False:
+ response_data['message'] = \
+ _('subscription saved, %(email)s needs validation, see %(details_url)s') \
+ % {'email':user.email,'details_url':reverse('faq') + '#validate'}
+ feed_setting = EmailFeedSetting.objects.get(subscriber=user,feed_type='q_sel')
+ if feed_setting.frequency == 'n':
+ feed_setting.frequency = 'd'
+ feed_setting.save()
+ if 'message' in response_data:
+ response_data['message'] += '<br/>'
+ response_data['message'] = _('email update frequency has been set to daily')
+ #response_data['status'] = 1
+ #responst_data['allowed'] = 1
+ else:
+ pass
+ #response_data['status'] = 0
+ #response_data['allowed'] = 0
+ elif vote_type == '12':#unsubscribe q updates
+ user = request.user
+ if user.is_authenticated():
+ if user in question.followed_by.all():
+ question.followed_by.remove(user)
+ else:
+ response_data['success'] = 0
+ response_data['message'] = u'Request mode is not supported. Please try again.'
+
+ data = simplejson.dumps(response_data)
+
+ except Exception, e:
+ response_data['message'] = str(e)
+ data = simplejson.dumps(response_data)
+ return HttpResponse(data, mimetype="application/json")
+
+@ajax_login_required
+def mark_tag(request, tag=None, **kwargs):
+ action = kwargs['action']
+ ts = MarkedTag.objects.filter(user=request.user, tag__name=tag)
+ if action == 'remove':
+ logging.debug('deleting tag %s' % tag)
+ ts.delete()
+ else:
+ reason = kwargs['reason']
+ if len(ts) == 0:
+ try:
+ t = Tag.objects.get(name=tag)
+ mt = MarkedTag(user=request.user, reason=reason, tag=t)
+ mt.save()
+ except:
+ pass
+ else:
+ ts.update(reason=reason)
+ return HttpResponse(simplejson.dumps(''), mimetype="application/json")
+
+@ajax_login_required
+def ajax_toggle_ignored_questions(request):
+ if request.user.hide_ignored_questions:
+ new_hide_setting = False
+ else:
+ new_hide_setting = True
+ request.user.hide_ignored_questions = new_hide_setting
+ request.user.save()
+
+@ajax_method
+def ajax_command(request):
+ if 'command' not in request.POST:
+ return HttpResponseForbidden(mimetype="application/json")
+ if request.POST['command'] == 'toggle-ignored-questions':
+ return ajax_toggle_ignored_questions(request)
+
+def users(request):
+ is_paginated = True
+ sortby = request.GET.get('sort', 'reputation')
+ suser = request.REQUEST.get('q', "")
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ if suser == "":
+ if sortby == "newest":
+ objects_list = Paginator(User.objects.all().order_by('-date_joined'), USERS_PAGE_SIZE)
+ elif sortby == "last":
+ objects_list = Paginator(User.objects.all().order_by('date_joined'), USERS_PAGE_SIZE)
+ elif sortby == "user":
+ objects_list = Paginator(User.objects.all().order_by('username'), USERS_PAGE_SIZE)
+ # default
+ else:
+ objects_list = Paginator(User.objects.all().order_by('-reputation'), USERS_PAGE_SIZE)
+ base_url = reverse('users') + '?sort=%s&' % sortby
+ else:
+ sortby = "reputation"
+ objects_list = Paginator(User.objects.extra(where=['username like %s'], params=['%' + suser + '%']).order_by('-reputation'), USERS_PAGE_SIZE)
+ base_url = reverse('users') + '?name=%s&sort=%s&' % (suser, sortby)
+
+ try:
+ users = objects_list.page(page)
+ except (EmptyPage, InvalidPage):
+ users = objects_list.page(objects_list.num_pages)
+
+ return render_to_response('users.html', {
+ "users" : users,
+ "suser" : suser,
+ "keywords" : suser,
+ "tab_id" : sortby,
+ "context" : {
+ 'is_paginated' : is_paginated,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': users.has_previous(),
+ 'has_next': users.has_next(),
+ 'previous': users.previous_page_number(),
+ 'next': users.next_page_number(),
+ 'base_url' : base_url
+ }
+
+ }, context_instance=RequestContext(request))
+
+def user(request, id):
+ sort = request.GET.get('sort', 'stats')
+ user_view = dict((v.id, v) for v in USER_TEMPLATE_VIEWS).get(sort, USER_TEMPLATE_VIEWS[0])
+ from forum import views
+ func = getattr(views, user_view.view_name)
+ return func(request, id, user_view)
+
+@login_required
+def moderate_user(request, id):
+ """ajax handler of user moderation
+ """
+ if not auth.can_moderate_users(request.user) or request.method != 'POST':
+ raise Http404
+ if not request.is_ajax():
+ return HttpResponseForbidden(mimetype="application/json")
+
+ user = get_object_or_404(User, id=id)
+ form = ModerateUserForm(request.POST, instance=user)
+
+ if form.is_valid():
+ form.save()
+ logging.debug('data saved')
+ response = HttpResponse(simplejson.dumps(''), mimetype="application/json")
+ else:
+ response = HttpResponseForbidden(mimetype="application/json")
+ return response
+
+@login_required
+def edit_user(request, id):
+ user = get_object_or_404(User, id=id)
+ if request.user != user:
+ raise Http404
+ if request.method == "POST":
+ form = EditUserForm(user, request.POST)
+ if form.is_valid():
+ new_email = sanitize_html(form.cleaned_data['email'])
+
+ from django_authopenid.views import set_new_email
+ set_new_email(user, new_email)
+
+ #user.username = sanitize_html(form.cleaned_data['username'])
+ user.real_name = sanitize_html(form.cleaned_data['realname'])
+ user.website = sanitize_html(form.cleaned_data['website'])
+ user.location = sanitize_html(form.cleaned_data['city'])
+ user.date_of_birth = sanitize_html(form.cleaned_data['birthday'])
+ if len(user.date_of_birth) == 0:
+ user.date_of_birth = '1900-01-01'
+ user.about = sanitize_html(form.cleaned_data['about'])
+
+ user.save()
+ # send user updated singal if full fields have been updated
+ if user.email and user.real_name and user.website and user.location and \
+ user.date_of_birth and user.about:
+ user_updated.send(sender=user.__class__, instance=user, updated_by=user)
+ return HttpResponseRedirect(user.get_profile_url())
+ else:
+ form = EditUserForm(user)
+ return render_to_response('user_edit.html', {
+ 'form' : form,
+ 'gravatar_faq_url' : reverse('faq') + '#gravatar',
+ }, context_instance=RequestContext(request))
+
+def user_stats(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ questions = Question.objects.extra(
+ select={
+ 'vote_count' : 'question.score',
+ 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s AND f.question_id = question.id',
+ 'la_user_id' : 'auth_user.id',
+ 'la_username' : 'auth_user.username',
+ 'la_user_gold' : 'auth_user.gold',
+ 'la_user_silver' : 'auth_user.silver',
+ 'la_user_bronze' : 'auth_user.bronze',
+ 'la_user_reputation' : 'auth_user.reputation'
+ },
+ select_params=[user_id],
+ tables=['question', 'auth_user'],
+ where=['question.deleted = 0 AND question.author_id=%s AND question.last_activity_by_id = auth_user.id'],
+ params=[user_id],
+ order_by=['-vote_count', '-last_activity_at']
+ ).values('vote_count',
+ 'favorited_myself',
+ 'id',
+ 'title',
+ 'author_id',
+ 'added_at',
+ 'answer_accepted',
+ 'answer_count',
+ 'comment_count',
+ 'view_count',
+ 'favourite_count',
+ 'summary',
+ 'tagnames',
+ 'vote_up_count',
+ 'vote_down_count',
+ 'last_activity_at',
+ 'la_user_id',
+ 'la_username',
+ 'la_user_gold',
+ 'la_user_silver',
+ 'la_user_bronze',
+ 'la_user_reputation')[:100]
+
+ answered_questions = Question.objects.extra(
+ select={
+ 'vote_up_count' : 'answer.vote_up_count',
+ 'vote_down_count' : 'answer.vote_down_count',
+ 'answer_id' : 'answer.id',
+ 'accepted' : 'answer.accepted',
+ 'vote_count' : 'answer.score',
+ 'comment_count' : 'answer.comment_count'
+ },
+ tables=['question', 'answer'],
+ where=['answer.deleted=0 AND question.deleted=0 AND answer.author_id=%s AND answer.question_id=question.id'],
+ params=[user_id],
+ order_by=['-vote_count', '-answer_id'],
+ select_params=[user_id]
+ ).distinct().values('comment_count',
+ 'id',
+ 'answer_id',
+ 'title',
+ 'author_id',
+ 'accepted',
+ 'vote_count',
+ 'answer_count',
+ 'vote_up_count',
+ 'vote_down_count')[:100]
+
+ up_votes = Vote.objects.get_up_vote_count_from_user(user)
+ down_votes = Vote.objects.get_down_vote_count_from_user(user)
+ votes_today = Vote.objects.get_votes_count_today_from_user(user)
+ votes_total = VOTE_RULES['scope_votes_per_user_per_day']
+
+ question_id_set = set(map(lambda v: v['id'], list(questions))) \
+ | set(map(lambda v: v['id'], list(answered_questions)))
+
+ user_tags = Tag.objects.filter(questions__id__in = question_id_set)
+ try:
+ from django.db.models import Count
+ awards = Award.objects.extra(
+ select={'id': 'badge.id',
+ 'name':'badge.name',
+ 'description': 'badge.description',
+ 'type': 'badge.type'},
+ tables=['award', 'badge'],
+ order_by=['-awarded_at'],
+ where=['user_id=%s AND badge_id=badge.id'],
+ params=[user.id]
+ ).values('id', 'name', 'description', 'type')
+ total_awards = awards.count()
+ awards = awards.annotate(count = Count('badge__id'))
+ user_tags = user_tags.annotate(user_tag_usage_count=Count('name'))
+
+ except ImportError:
+ awards = Award.objects.extra(
+ select={'id': 'badge.id',
+ 'count': 'count(badge_id)',
+ 'name':'badge.name',
+ 'description': 'badge.description',
+ 'type': 'badge.type'},
+ tables=['award', 'badge'],
+ order_by=['-awarded_at'],
+ where=['user_id=%s AND badge_id=badge.id'],
+ params=[user.id]
+ ).values('id', 'count', 'name', 'description', 'type')
+ total_awards = awards.count()
+ awards.query.group_by = ['badge_id']
+
+ user_tags = user_tags.extra(
+ select={'user_tag_usage_count': 'COUNT(1)',},
+ order_by=['-user_tag_usage_count'],
+ )
+ user_tags.query.group_by = ['name']
+
+ if auth.can_moderate_users(request.user):
+ moderate_user_form = ModerateUserForm(instance=user)
+ else:
+ moderate_user_form = None
+
+ return render_to_response(user_view.template_file,{
+ 'moderate_user_form': moderate_user_form,
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "questions" : questions,
+ "answered_questions" : answered_questions,
+ "up_votes" : up_votes,
+ "down_votes" : down_votes,
+ "total_votes": up_votes + down_votes,
+ "votes_today_left": votes_total-votes_today,
+ "votes_total_per_day": votes_total,
+ "user_tags" : user_tags[:50],
+ "tags" : tags,
+ "awards": awards,
+ "total_awards" : total_awards,
+ }, context_instance=RequestContext(request))
+
+def user_recent(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ def get_type_name(type_id):
+ for item in TYPE_ACTIVITY:
+ if type_id in item:
+ return item[1]
+
+ class Event:
+ def __init__(self, time, type, title, summary, answer_id, question_id):
+ self.time = time
+ self.type = get_type_name(type)
+ self.type_id = type
+ self.title = title
+ self.summary = summary
+ slug_title = slugify(title)
+ self.title_link = reverse('question', kwargs={'id':question_id}) + u'%s' % slug_title
+ if int(answer_id) > 0:
+ self.title_link += '#%s' % answer_id
+
+ class AwardEvent:
+ def __init__(self, time, type, id):
+ self.time = time
+ self.type = get_type_name(type)
+ self.type_id = type
+ self.badge = get_object_or_404(Badge, id=id)
+
+ activities = []
+ # ask questions
+ questions = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'active_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'question'],
+ where=['activity.content_type_id = %s AND activity.object_id = ' +
+ 'question.id AND question.deleted=0 AND activity.user_id = %s AND activity.activity_type = %s'],
+ params=[question_type_id, user_id, TYPE_ACTIVITY_ASK_QUESTION],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'active_at',
+ 'activity_type'
+ )
+ if len(questions) > 0:
+ questions = [(Event(q['active_at'], q['activity_type'], q['title'], '', '0', \
+ q['question_id'])) for q in questions]
+ activities.extend(questions)
+
+ # answers
+ answers = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'active_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'answer', 'question'],
+ where=['activity.content_type_id = %s AND activity.object_id = answer.id AND ' +
+ 'answer.question_id=question.id AND answer.deleted=0 AND activity.user_id=%s AND '+
+ 'activity.activity_type=%s AND question.deleted=0'],
+ params=[answer_type_id, user_id, TYPE_ACTIVITY_ANSWER],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'active_at',
+ 'activity_type'
+ )
+ if len(answers) > 0:
+ answers = [(Event(q['active_at'], q['activity_type'], q['title'], '', q['answer_id'], \
+ q['question_id'])) for q in answers]
+ activities.extend(answers)
+
+ # question comments
+ comments = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'comment.object_id',
+ 'added_at' : 'comment.added_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'question', 'comment'],
+
+ where=['activity.content_type_id = %s AND activity.object_id = comment.id AND '+
+ 'activity.user_id = comment.user_id AND comment.object_id=question.id AND '+
+ 'comment.content_type_id=%s AND activity.user_id = %s AND activity.activity_type=%s AND ' +
+ 'question.deleted=0'],
+ params=[comment_type_id, question_type_id, user_id, TYPE_ACTIVITY_COMMENT_QUESTION],
+ order_by=['-comment.added_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'activity_type'
+ )
+
+ if len(comments) > 0:
+ comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \
+ q['question_id'])) for q in comments]
+ activities.extend(comments)
+
+ # answer comments
+ comments = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'comment.added_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'question', 'answer', 'comment'],
+
+ where=['activity.content_type_id = %s AND activity.object_id = comment.id AND '+
+ 'activity.user_id = comment.user_id AND comment.object_id=answer.id AND '+
+ 'comment.content_type_id=%s AND question.id = answer.question_id AND '+
+ 'activity.user_id = %s AND activity.activity_type=%s AND '+
+ 'answer.deleted=0 AND question.deleted=0'],
+ params=[comment_type_id, answer_type_id, user_id, TYPE_ACTIVITY_COMMENT_ANSWER],
+ order_by=['-comment.added_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'activity_type'
+ )
+
+ if len(comments) > 0:
+ comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', q['answer_id'], \
+ q['question_id'])) for q in comments]
+ activities.extend(comments)
+
+ # question revisions
+ revisions = Activity.objects.extra(
+ select={
+ 'title' : 'question_revision.title',
+ 'question_id' : 'question_revision.question_id',
+ 'added_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type',
+ 'summary' : 'question_revision.summary'
+ },
+ tables=['activity', 'question_revision', 'question'],
+ where=['activity.content_type_id = %s AND activity.object_id = question_revision.id AND '+
+ 'question_revision.id=question.id AND question.deleted=0 AND '+
+ 'activity.user_id = question_revision.author_id AND activity.user_id = %s AND '+
+ 'activity.activity_type=%s'],
+ params=[question_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_QUESTION],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'activity_type',
+ 'summary'
+ )
+
+ if len(revisions) > 0:
+ revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], '0', \
+ q['question_id'])) for q in revisions]
+ activities.extend(revisions)
+
+ # answer revisions
+ revisions = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type',
+ 'summary' : 'answer_revision.summary'
+ },
+ tables=['activity', 'answer_revision', 'question', 'answer'],
+
+ where=['activity.content_type_id = %s AND activity.object_id = answer_revision.id AND '+
+ 'activity.user_id = answer_revision.author_id AND activity.user_id = %s AND '+
+ 'answer_revision.answer_id=answer.id AND answer.question_id = question.id AND '+
+ 'question.deleted=0 AND answer.deleted=0 AND '+
+ 'activity.activity_type=%s'],
+ params=[answer_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_ANSWER],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'answer_id',
+ 'activity_type',
+ 'summary'
+ )
+
+ if len(revisions) > 0:
+ revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], \
+ q['answer_id'], q['question_id'])) for q in revisions]
+ activities.extend(revisions)
+
+ # accepted answers
+ accept_answers = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'added_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type',
+ },
+ tables=['activity', 'answer', 'question'],
+ where=['activity.content_type_id = %s AND activity.object_id = answer.id AND '+
+ 'activity.user_id = question.author_id AND activity.user_id = %s AND '+
+ 'answer.deleted=0 AND question.deleted=0 AND '+
+ 'answer.question_id=question.id AND activity.activity_type=%s'],
+ params=[answer_type_id, user_id, TYPE_ACTIVITY_MARK_ANSWER],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'activity_type',
+ )
+ if len(accept_answers) > 0:
+ accept_answers = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \
+ q['question_id'])) for q in accept_answers]
+ activities.extend(accept_answers)
+ #award history
+ awards = Activity.objects.extra(
+ select={
+ 'badge_id' : 'badge.id',
+ 'awarded_at': 'award.awarded_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'award', 'badge'],
+ where=['activity.user_id = award.user_id AND activity.user_id = %s AND '+
+ 'award.badge_id=badge.id AND activity.object_id=award.id AND activity.activity_type=%s'],
+ params=[user_id, TYPE_ACTIVITY_PRIZE],
+ order_by=['-activity.active_at']
+ ).values(
+ 'badge_id',
+ 'awarded_at',
+ 'activity_type'
+ )
+ if len(awards) > 0:
+ awards = [(AwardEvent(q['awarded_at'], q['activity_type'], q['badge_id'])) for q in awards]
+ activities.extend(awards)
+
+ activities.sort(lambda x,y: cmp(y.time, x.time))
+
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "activities" : activities[:user_view.data_size]
+ }, context_instance=RequestContext(request))
+
+def user_responses(request, user_id, user_view):
+ """
+ We list answers for question, comments, and answer accepted by others for this user.
+ """
+ class Response:
+ def __init__(self, type, title, question_id, answer_id, time, username, user_id, content):
+ self.type = type
+ self.title = title
+ self.titlelink = reverse('question', args=[question_id]) + u'%s#%s' % (slugify(title), answer_id)
+ self.time = time
+ self.userlink = reverse('users') + u'%s/%s/' % (user_id, username)
+ self.username = username
+ self.content = u'%s ...' % strip_tags(content)[:300]
+
+ def __unicode__(self):
+ return u'%s %s' % (self.type, self.titlelink)
+
+ user = get_object_or_404(User, id=user_id)
+ responses = []
+ answers = Answer.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'answer.added_at',
+ 'html' : 'answer.html',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ select_params=[user_id],
+ tables=['answer', 'question', 'auth_user'],
+ where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+
+ 'question.author_id = %s AND answer.author_id <> %s AND answer.author_id=auth_user.id'],
+ params=[user_id, user_id],
+ order_by=['-answer.id']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'html',
+ 'username',
+ 'user_id'
+ )
+ if len(answers) > 0:
+ answers = [(Response(TYPE_RESPONSE['QUESTION_ANSWERED'], a['title'], a['question_id'],
+ a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
+ responses.extend(answers)
+
+
+ # question comments
+ comments = Comment.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'comment.object_id',
+ 'added_at' : 'comment.added_at',
+ 'comment' : 'comment.comment',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ tables=['question', 'auth_user', 'comment'],
+ where=['question.deleted = 0 AND question.author_id = %s AND comment.object_id=question.id AND '+
+ 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id'],
+ params=[user_id, question_type_id, user_id],
+ order_by=['-comment.added_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'comment',
+ 'username',
+ 'user_id'
+ )
+
+ if len(comments) > 0:
+ comments = [(Response(TYPE_RESPONSE['QUESTION_COMMENTED'], c['title'], c['question_id'],
+ '', c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments]
+ responses.extend(comments)
+
+ # answer comments
+ comments = Comment.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'comment.added_at',
+ 'comment' : 'comment.comment',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ tables=['answer', 'auth_user', 'comment', 'question'],
+ where=['answer.deleted = 0 AND answer.author_id = %s AND comment.object_id=answer.id AND '+
+ 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id '+
+ 'AND question.id = answer.question_id'],
+ params=[user_id, answer_type_id, user_id],
+ order_by=['-comment.added_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'comment',
+ 'username',
+ 'user_id'
+ )
+
+ if len(comments) > 0:
+ comments = [(Response(TYPE_RESPONSE['ANSWER_COMMENTED'], c['title'], c['question_id'],
+ c['answer_id'], c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments]
+ responses.extend(comments)
+
+ # answer has been accepted
+ answers = Answer.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'answer.accepted_at',
+ 'html' : 'answer.html',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ select_params=[user_id],
+ tables=['answer', 'question', 'auth_user'],
+ where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+
+ 'answer.author_id = %s AND answer.accepted=1 AND question.author_id=auth_user.id'],
+ params=[user_id],
+ order_by=['-answer.id']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'html',
+ 'username',
+ 'user_id'
+ )
+ if len(answers) > 0:
+ answers = [(Response(TYPE_RESPONSE['ANSWER_ACCEPTED'], a['title'], a['question_id'],
+ a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
+ responses.extend(answers)
+
+ # sort posts by time
+ responses.sort(lambda x,y: cmp(y.time, x.time))
+
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "responses" : responses[:user_view.data_size],
+
+ }, context_instance=RequestContext(request))
+
+def user_votes(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ if not can_view_user_votes(request.user, user):
+ raise Http404
+ votes = []
+ question_votes = Vote.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 0,
+ 'voted_at' : 'vote.voted_at',
+ 'vote' : 'vote',
+ },
+ select_params=[user_id],
+ tables=['vote', 'question', 'auth_user'],
+ where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = question.id '+
+ 'AND vote.user_id=auth_user.id'],
+ params=[question_type_id, user_id],
+ order_by=['-vote.id']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'voted_at',
+ 'vote',
+ )
+ if(len(question_votes) > 0):
+ votes.extend(question_votes)
+
+ answer_votes = Vote.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'voted_at' : 'vote.voted_at',
+ 'vote' : 'vote',
+ },
+ select_params=[user_id],
+ tables=['vote', 'answer', 'question', 'auth_user'],
+ where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = answer.id '+
+ 'AND answer.question_id = question.id AND vote.user_id=auth_user.id'],
+ params=[answer_type_id, user_id],
+ order_by=['-vote.id']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'voted_at',
+ 'vote',
+ )
+ if(len(answer_votes) > 0):
+ votes.extend(answer_votes)
+ votes.sort(lambda x,y: cmp(y['voted_at'], x['voted_at']))
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "votes" : votes[:user_view.data_size]
+
+ }, context_instance=RequestContext(request))
+
+def user_reputation(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ try:
+ from django.db.models import Sum
+ reputation = Repute.objects.extra(
+ select={'question_id':'question_id',
+ 'title': 'question.title'},
+ tables=['repute', 'question'],
+ order_by=['-reputed_at'],
+ where=['user_id=%s AND question_id=question.id'],
+ params=[user.id]
+ ).values('question_id', 'title', 'reputed_at', 'reputation')
+ reputation = reputation.annotate(positive=Sum("positive"), negative=Sum("negative"))
+ except ImportError:
+ reputation = Repute.objects.extra(
+ select={'positive':'sum(positive)', 'negative':'sum(negative)', 'question_id':'question_id',
+ 'title': 'question.title'},
+ tables=['repute', 'question'],
+ order_by=['-reputed_at'],
+ where=['user_id=%s AND question_id=question.id'],
+ params=[user.id]
+ ).values('positive', 'negative', 'question_id', 'title', 'reputed_at', 'reputation')
+ reputation.query.group_by = ['question_id']
+
+ rep_list = []
+ for rep in Repute.objects.filter(user=user).order_by('reputed_at'):
+ dic = '[%s,%s]' % (calendar.timegm(rep.reputed_at.timetuple()) * 1000, rep.reputation)
+ rep_list.append(dic)
+ reps = ','.join(rep_list)
+ reps = '[%s]' % reps
+
+ return render_to_response(user_view.template_file, {
+ "tab_name": user_view.id,
+ "tab_description": user_view.tab_description,
+ "page_title": user_view.page_title,
+ "view_user": user,
+ "reputation": reputation,
+ "reps": reps
+ }, context_instance=RequestContext(request))
+
+def user_favorites(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ questions = Question.objects.extra(
+ select={
+ 'vote_count' : 'question.vote_up_count + question.vote_down_count',
+ 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s '+
+ 'AND f.question_id = question.id',
+ 'la_user_id' : 'auth_user.id',
+ 'la_username' : 'auth_user.username',
+ 'la_user_gold' : 'auth_user.gold',
+ 'la_user_silver' : 'auth_user.silver',
+ 'la_user_bronze' : 'auth_user.bronze',
+ 'la_user_reputation' : 'auth_user.reputation'
+ },
+ select_params=[user_id],
+ tables=['question', 'auth_user', 'favorite_question'],
+ where=['question.deleted = 0 AND question.last_activity_by_id = auth_user.id '+
+ 'AND favorite_question.question_id = question.id AND favorite_question.user_id = %s'],
+ params=[user_id],
+ order_by=['-vote_count', '-question.id']
+ ).values('vote_count',
+ 'favorited_myself',
+ 'id',
+ 'title',
+ 'author_id',
+ 'added_at',
+ 'answer_accepted',
+ 'answer_count',
+ 'comment_count',
+ 'view_count',
+ 'favourite_count',
+ 'summary',
+ 'tagnames',
+ 'vote_up_count',
+ 'vote_down_count',
+ 'last_activity_at',
+ 'la_user_id',
+ 'la_username',
+ 'la_user_gold',
+ 'la_user_silver',
+ 'la_user_bronze',
+ 'la_user_reputation')
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "questions" : questions[:user_view.data_size],
+ "view_user" : user
+ }, context_instance=RequestContext(request))
+
+def user_email_subscriptions(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ if request.method == 'POST':
+ email_feeds_form = EditUserEmailFeedsForm(request.POST)
+ tag_filter_form = TagFilterSelectionForm(request.POST, instance=user)
+ if email_feeds_form.is_valid() and tag_filter_form.is_valid():
+
+ action_status = None
+ tag_filter_saved = tag_filter_form.save()
+ if tag_filter_saved:
+ action_status = _('changes saved')
+ if 'save' in request.POST:
+ feeds_saved = email_feeds_form.save(user)
+ if feeds_saved:
+ action_status = _('changes saved')
+ elif 'stop_email' in request.POST:
+ email_stopped = email_feeds_form.reset().save(user)
+ initial_values = EditUserEmailFeedsForm.NO_EMAIL_INITIAL
+ email_feeds_form = EditUserEmailFeedsForm(initial=initial_values)
+ if email_stopped:
+ action_status = _('email updates canceled')
+ else:
+ email_feeds_form = EditUserEmailFeedsForm()
+ email_feeds_form.set_initial_values(user)
+ tag_filter_form = TagFilterSelectionForm(instance=user)
+ action_status = None
+ return render_to_response(user_view.template_file,{
+ 'tab_name':user_view.id,
+ 'tab_description':user_view.tab_description,
+ 'page_title':user_view.page_title,
+ 'view_user':user,
+ 'email_feeds_form':email_feeds_form,
+ 'tag_filter_selection_form':tag_filter_form,
+ 'action_status':action_status,
+ }, context_instance=RequestContext(request))
+
+def question_comments(request, id):
+ question = get_object_or_404(Question, id=id)
+ user = request.user
+ return __comments(request, question, 'question')
+
+def answer_comments(request, id):
+ answer = get_object_or_404(Answer, id=id)
+ user = request.user
+ return __comments(request, answer, 'answer')
+
+def __comments(request, obj, type):
+ # only support get comments by ajax now
+ user = request.user
+ if request.is_ajax():
+ if request.method == "GET":
+ response = __generate_comments_json(obj, type, user)
+ elif request.method == "POST":
+ if auth.can_add_comments(user,obj):
+ comment_data = request.POST.get('comment')
+ comment = Comment(content_object=obj, comment=comment_data, user=request.user)
+ comment.save()
+ obj.comment_count = obj.comment_count + 1
+ obj.save()
+ response = __generate_comments_json(obj, type, user)
+ else:
+ response = HttpResponseForbidden(mimetype="application/json")
+ return response
+
+def __generate_comments_json(obj, type, user):
+ comments = obj.comments.all().order_by('id')
+ # {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
+ json_comments = []
+ from forum.templatetags.extra_tags import diff_date
+ for comment in comments:
+ comment_user = comment.user
+ delete_url = ""
+ if user != None and auth.can_delete_comment(user, comment):
+ #/posts/392845/comments/219852/delete
+ #todo translate this url
+ delete_url = reverse(index) + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id)
+ json_comments.append({"id" : comment.id,
+ "object_id" : obj.id,
+ "comment_age" : diff_date(comment.added_at),
+ "text" : comment.comment,
+ "user_display_name" : comment_user.username,
+ "user_url" : comment_user.get_profile_url(),
+ "delete_url" : delete_url
+ })
+
+ data = simplejson.dumps(json_comments)
+ return HttpResponse(data, mimetype="application/json")
+
+def delete_comment(request, object_id='', comment_id='', commented_object_type=None):
+ response = None
+ commented_object = None
+ if commented_object_type == 'question':
+ commented_object = Question
+ elif commented_object_type == 'answer':
+ commented_object = Answer
+
+ if request.is_ajax():
+ comment = get_object_or_404(Comment, id=comment_id)
+ if auth.can_delete_comment(request.user, comment):
+ obj = get_object_or_404(commented_object, id=object_id)
+ obj.comments.remove(comment)
+ obj.comment_count = obj.comment_count - 1
+ obj.save()
+ user = request.user
+ return __generate_comments_json(obj, commented_object_type, user)
+ raise PermissionDenied()
+
+def logout(request):
+ return render_to_response('logout.html', {
+ 'next' : get_next_url(request),
+ }, context_instance=RequestContext(request))
+
+def badges(request):
+ badges = Badge.objects.all().order_by('type')
+ my_badges = []
+ if request.user.is_authenticated():
+ my_badges = Award.objects.filter(user=request.user)
+ my_badges.query.group_by = ['badge_id']
+
+ return render_to_response('badges.html', {
+ 'badges' : badges,
+ 'mybadges' : my_badges,
+ 'feedback_faq_url' : reverse('feedback'),
+ }, context_instance=RequestContext(request))
+
+def badge(request, id):
+ badge = get_object_or_404(Badge, id=id)
+ awards = Award.objects.extra(
+ select={'id': 'auth_user.id',
+ 'name': 'auth_user.username',
+ 'rep':'auth_user.reputation',
+ 'gold': 'auth_user.gold',
+ 'silver': 'auth_user.silver',
+ 'bronze': 'auth_user.bronze'},
+ tables=['award', 'auth_user'],
+ where=['badge_id=%s AND user_id=auth_user.id'],
+ params=[id]
+ ).distinct('id')
+
+ return render_to_response('badge.html', {
+ 'awards' : awards,
+ 'badge' : badge,
+ }, context_instance=RequestContext(request))
+
+def read_message(request):
+ if request.method == "POST":
+ if request.POST['formdata'] == 'required':
+ request.session['message_silent'] = 1
+ if request.user.is_authenticated():
+ request.user.delete_messages()
+ return HttpResponse('')
+
+def upload(request):
+ class FileTypeNotAllow(Exception):
+ pass
+ class FileSizeNotAllow(Exception):
+ pass
+ class UploadPermissionNotAuthorized(Exception):
+ pass
+
+ #<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>
+ xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>"
+
+ try:
+ f = request.FILES['file-upload']
+ # check upload permission
+ if not can_upload_files(request.user):
+ raise UploadPermissionNotAuthorized
+
+ # check file type
+ file_name_suffix = os.path.splitext(f.name)[1].lower()
+ if not file_name_suffix in settings.ALLOW_FILE_TYPES:
+ raise FileTypeNotAllow
+
+ # generate new file name
+ new_file_name = str(time.time()).replace('.', str(random.randint(0,100000))) + file_name_suffix
+ # use default storage to store file
+ default_storage.save(new_file_name, f)
+ # check file size
+ # byte
+ size = default_storage.size(new_file_name)
+ if size > settings.ALLOW_MAX_FILE_SIZE:
+ default_storage.delete(new_file_name)
+ raise FileSizeNotAllow
+
+ result = xml_template % ('Good', '', default_storage.url(new_file_name))
+ except UploadPermissionNotAuthorized:
+ result = xml_template % ('', _('uploading images is limited to users with >60 reputation points'), '')
+ except FileTypeNotAllow:
+ result = xml_template % ('', _("allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"), '')
+ except FileSizeNotAllow:
+ result = xml_template % ('', _("maximum upload file size is %sK") % settings.ALLOW_MAX_FILE_SIZE / 1024, '')
+ except Exception:
+ result = xml_template % ('', _('Error uploading file. Please contact the site administrator. Thank you. %s' % Exception), '')
+
+ return HttpResponse(result, mimetype="application/xml")
+
+def books(request):
+ return HttpResponseRedirect(reverse('books') + '/mysql-zhaoyang')
+
+def book(request, short_name, unanswered=False):
+ """
+ 1. questions list
+ 2. book info
+ 3. author info and blog rss items
+ """
+ """
+ List of Questions, Tagged questions, and Unanswered questions.
+ """
+ books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
+ match_count = len(books)
+ if match_count == 0:
+ raise Http404
+ else:
+ # the book info
+ book = books[0]
+ # get author info
+ author_info = BookAuthorInfo.objects.get(book=book)
+ # get author rss info
+ author_rss = BookAuthorRss.objects.filter(book=book)
+
+ # get pagesize from session, if failed then get default value
+ user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
+ # set pagesize equal to logon user specified value in database
+ if request.user.is_authenticated() and request.user.questions_per_page > 0:
+ user_page_size = request.user.questions_per_page
+
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ view_id = request.GET.get('sort', None)
+ view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "latest"
+ orderby = "-added_at"
+
+ # check if request is from tagged questions
+ if unanswered:
+ # check if request is from unanswered questions
+ # Article.objects.filter(publications__id__exact=1)
+ objects = Question.objects.filter(book__id__exact=book.id, deleted=False, answer_count=0).order_by(orderby)
+ else:
+ objects = Question.objects.filter(book__id__exact=book.id, deleted=False).order_by(orderby)
+
+ # RISK - inner join queries
+ objects = objects.select_related();
+ objects_list = Paginator(objects, user_page_size)
+ questions = objects_list.page(page)
+
+ return render_to_response('book.html', {
+ "book" : book,
+ "author_info" : author_info,
+ "author_rss" : author_rss,
+ "questions" : questions,
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?sort=%s&' % view_id,
+ 'pagesize' : user_page_size
+ }
+ }, context_instance=RequestContext(request))
+
+@login_required
+def ask_book(request, short_name):
+ if request.method == "POST":
+ form = AskForm(request.POST)
+ if form.is_valid():
+ added_at = datetime.datetime.now()
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
+ question = Question(
+ title = strip_tags(form.cleaned_data['title']),
+ author = request.user,
+ added_at = added_at,
+ last_activity_at = added_at,
+ last_activity_by = request.user,
+ wiki = form.cleaned_data['wiki'],
+ tagnames = form.cleaned_data['tags'].strip(),
+ html = html,
+ summary = strip_tags(html)[:120]
+ )
+ if question.wiki:
+ question.last_edited_by = question.author
+ question.last_edited_at = added_at
+ question.wikified_at = added_at
+
+ question.save()
+
+ # create the first revision
+ QuestionRevision.objects.create(
+ question = question,
+ revision = 1,
+ title = question.title,
+ author = request.user,
+ revised_at = added_at,
+ tagnames = question.tagnames,
+ summary = CONST['default_version'],
+ text = form.cleaned_data['text']
+ )
+
+ books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
+ match_count = len(books)
+ if match_count == 1:
+ # the book info
+ book = books[0]
+ book.questions.add(question)
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ form = AskForm()
+
+ tags = _get_tags_cache_json()
+ return render_to_response('ask.html', {
+ 'form' : form,
+ 'tags' : tags,
+ 'email_validation_faq_url': reverse('faq') + '#validate',
+ }, context_instance=RequestContext(request))
+
+def search(request):
+ """
+ Search by question, user and tag keywords.
+ For questions now we only search keywords in question title.
+ """
+ if request.method == "GET":
+ keywords = request.GET.get("q")
+ search_type = request.GET.get("t")
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+ if keywords is None:
+ return HttpResponseRedirect(reverse(index))
+ if search_type == 'tag':
+ return HttpResponseRedirect(reverse('tags') + '?q=%s&page=%s' % (keywords.strip(), page))
+ elif search_type == "user":
+ return HttpResponseRedirect(reverse('users') + '?q=%s&page=%s' % (keywords.strip(), page))
+ elif search_type == "question":
+
+ template_file = "questions.html"
+ # Set flag to False by default. If it is equal to True, then need to be saved.
+ pagesize_changed = False
+ # get pagesize from session, if failed then get default value
+ user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
+ # set pagesize equal to logon user specified value in database
+ if request.user.is_authenticated() and request.user.questions_per_page > 0:
+ user_page_size = request.user.questions_per_page
+
+ try:
+ page = int(request.GET.get('page', '1'))
+ # get new pagesize from UI selection
+ pagesize = int(request.GET.get('pagesize', user_page_size))
+ if pagesize <> user_page_size:
+ pagesize_changed = True
+
+ except ValueError:
+ page = 1
+ pagesize = user_page_size
+
+ # save this pagesize to user database
+ if pagesize_changed:
+ request.session["pagesize"] = pagesize
+ if request.user.is_authenticated():
+ user = request.user
+ user.questions_per_page = pagesize
+ user.save()
+
+ view_id = request.GET.get('sort', None)
+ view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "latest"
+ orderby = "-added_at"
+
+ if settings.USE_SPHINX_SEARCH == True:
+ #search index is now free of delete questions and answers
+ #so there is not "antideleted" filtering here
+ objects = Question.search.query(keywords)
+ #no related selection either because we're relying on full text search here
+ else:
+ objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby)
+ # RISK - inner join queries
+ objects = objects.select_related();
+
+ objects_list = Paginator(objects, pagesize)
+ questions = objects_list.page(page)
+
+ # Get related tags from this page objects
+ related_tags = []
+ for question in questions.object_list:
+ tags = list(question.tags.all())
+ for tag in tags:
+ if tag not in related_tags:
+ related_tags.append(tag)
+
+ #if is_search is true in the context, prepend this string to soting tabs urls
+ search_uri = "?q=%s&page=%d&t=question" % ("+".join(keywords.split()), page)
+
+ return render_to_response(template_file, {
+ "questions" : questions,
+ "tab_id" : view_id,
+ "questions_count" : objects_list.count,
+ "tags" : related_tags,
+ "searchtag" : None,
+ "searchtitle" : keywords,
+ "keywords" : keywords,
+ "is_unanswered" : False,
+ "is_search": True,
+ "search_uri": search_uri,
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?t=question&q=%s&sort=%s&' % (keywords, view_id),
+ 'pagesize' : pagesize
+ }}, context_instance=RequestContext(request))
+
+ else:
+ raise Http404
diff --git a/templates/content/js/com.cnprog.post.js b/templates/content/js/com.cnprog.post.js
index 02ed7757..a073d20f 100644
--- a/templates/content/js/com.cnprog.post.js
+++ b/templates/content/js/com.cnprog.post.js
@@ -1,690 +1,690 @@
-ļ»æ/*
-Scripts for cnprog.com
-Project Name: Lanai
-All Rights Resevred 2008. CNPROG.COM
-*/
-var lanai =
-{
- /**
- * Finds any <pre><code></code></pre> tags which aren't registered for
- * pretty printing, adds the appropriate class name and invokes prettify.
- */
- highlightSyntax: function(){
- var styled = false;
- $("pre code").parent().each(function(){
- if (!$(this).hasClass('prettyprint')){
- $(this).addClass('prettyprint');
- styled = true;
- }
- });
-
- if (styled){
- prettyPrint();
- }
- }
-};
-
-var Vote = function(){
- // All actions are related to a question
- var questionId;
- //question slug to build redirect urls
- var questionSlug;
- // The object we operate on actually. It can be a question or an answer.
- var postId;
- var questionAuthorId;
- var currentUserId;
- var answerContainerIdPrefix = 'answer-container-';
- var voteContainerId = 'vote-buttons';
- var imgIdPrefixAccept = 'answer-img-accept-';
- var imgClassPrefixFavorite = 'question-img-favorite';
- var imgIdPrefixQuestionVoteup = 'question-img-upvote-';
- var imgIdPrefixQuestionVotedown = 'question-img-downvote-';
- var imgIdPrefixAnswerVoteup = 'answer-img-upvote-';
- var imgIdPrefixAnswerVotedown = 'answer-img-downvote-';
- var divIdFavorite = 'favorite-number';
- var commentLinkIdPrefix = 'comment-';
- var voteNumberClass = "vote-number";
- var offensiveIdPrefixQuestionFlag = 'question-offensive-flag-';
- var offensiveIdPrefixAnswerFlag = 'answer-offensive-flag-';
- var offensiveClassFlag = 'offensive-flag';
- var questionControlsId = 'question-controls';
- var removeQuestionLinkIdPrefix = 'question-delete-link-';
- var removeAnswerLinkIdPrefix = 'answer-delete-link-';
- var questionSubscribeUpdates = 'question-subscribe-updates';
-
- var acceptAnonymousMessage = $.i18n._('insufficient privilege');
- var acceptOwnAnswerMessage = $.i18n._('cannot pick own answer as best');
-
- var pleaseLogin = "<a href='" + scriptUrl + $.i18n._("account/") + $.i18n._("signin/")
- + "?next=" + scriptUrl + $.i18n._("question/") + "{{QuestionID}}/{{questionSlug}}'>"
- + $.i18n._('please login') + "</a>";
-
- var pleaseSeeFAQ = $.i18n._('please see') + "<a href='" + scriptUrl + $.i18n._("faq/") + "'>faq</a>";
-
- var favoriteAnonymousMessage = $.i18n._('anonymous users cannot select favorite questions')
- var voteAnonymousMessage = $.i18n._('anonymous users cannot vote') + pleaseLogin;
- var upVoteRequiredScoreMessage = $.i18n._('>15 points requried to upvote') + pleaseSeeFAQ;
- var downVoteRequiredScoreMessage = $.i18n._('>100 points required to downvote') + pleaseSeeFAQ;
- var voteOwnDeniedMessage = $.i18n._('cannot vote for own posts');
- var voteRequiredMoreVotes = $.i18n._('daily vote cap exhausted') + pleaseSeeFAQ;
- var voteDenyCancelMessage = $.i18n._('cannot revoke old vote') + pleaseSeeFAQ;
- var offensiveConfirmation = $.i18n._('please confirm offensive');
- var offensiveAnonymousMessage = $.i18n._('anonymous users cannot flag offensive posts') + pleaseLogin;
- var offensiveTwiceMessage = $.i18n._('cannot flag message as offensive twice') + pleaseSeeFAQ;
- var offensiveNoFlagsLeftMessage = $.i18n._('flag offensive cap exhausted') + pleaseSeeFAQ;
- var offensiveNoPermissionMessage = $.i18n._('need >15 points to report spam') + pleaseSeeFAQ;
- var removeConfirmation = $.i18n._('confirm delete');
- var removeAnonymousMessage = $.i18n._('anonymous users cannot delete/undelete');
- var recoveredMessage = $.i18n._('post recovered');
- var deletedMessage = $.i18n._('post deleted');
-
- var VoteType = {
- acceptAnswer : 0,
- questionUpVote : 1,
- questionDownVote : 2,
- favorite : 4,
- answerUpVote: 5,
- answerDownVote:6,
- offensiveQuestion : 7,
- offensiveAnswer:8,
- removeQuestion: 9,
- removeAnswer:10,
- questionSubscribeUpdates:11,
- questionUnsubscribeUpdates:12
- };
-
- var getFavoriteButton = function(){
- var favoriteButton = 'div.'+ voteContainerId +' img[class='+ imgClassPrefixFavorite +']';
- return $(favoriteButton);
- };
- var getFavoriteNumber = function(){
- var favoriteNumber = '#'+ divIdFavorite ;
- return $(favoriteNumber);
- };
- var getQuestionVoteUpButton = function(){
- var questionVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVoteup +']';
- return $(questionVoteUpButton);
- };
- var getQuestionVoteDownButton = function(){
- var questionVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVotedown +']';
- return $(questionVoteDownButton);
- };
- var getAnswerVoteUpButtons = function(){
- var answerVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVoteup +']';
- return $(answerVoteUpButton);
- };
- var getAnswerVoteDownButtons = function(){
- var answerVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVotedown +']';
- return $(answerVoteDownButton);
- };
- var getAnswerVoteUpButton = function(id){
- var answerVoteUpButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVoteup + id + ']';
- return $(answerVoteUpButton);
- };
- var getAnswerVoteDownButton = function(id){
- var answerVoteDownButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVotedown + id + ']';
- return $(answerVoteDownButton);
- };
-
- var getOffensiveQuestionFlag = function(){
- var offensiveQuestionFlag = '#question-table span[class='+ offensiveClassFlag +']';
- return $(offensiveQuestionFlag);
- };
-
- var getOffensiveAnswerFlags = function(){
- var offensiveQuestionFlag = 'div.answer span[class='+ offensiveClassFlag +']';
- return $(offensiveQuestionFlag);
- };
-
- var getremoveQuestionLink = function(){
- var removeQuestionLink = 'div#question-controls a[id^='+ removeQuestionLinkIdPrefix +']';
- return $(removeQuestionLink);
- };
-
- var getquestionSubscribeUpdatesCheckbox = function(){
- return $('#' + questionSubscribeUpdates);
- };
-
- var getremoveAnswersLinks = function(){
- var removeAnswerLinks = 'div.answer-controls a[id^='+ removeAnswerLinkIdPrefix +']';
- return $(removeAnswerLinks);
- };
-
- var setVoteImage = function(voteType, undo, object){
- var flag = undo ? "" : "-on";
- var arrow = (voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote) ? "up" : "down";
- object.attr("src", scriptUrl + "content/images/vote-arrow-"+ arrow + flag +".png");
-
- // if undo voting, then undo the pair of arrows.
- if(undo){
- if(voteType == VoteType.questionUpVote || voteType == VoteType.questionDownVote){
- $(getQuestionVoteUpButton()).attr("src", scriptUrl + "content/images/vote-arrow-up.png");
- $(getQuestionVoteDownButton()).attr("src", scriptUrl + "content/images/vote-arrow-down.png");
- }
- else{
- $(getAnswerVoteUpButton(postId)).attr("src", scriptUrl + "content/images/vote-arrow-up.png");
- $(getAnswerVoteDownButton(postId)).attr("src", scriptUrl + "content/images/vote-arrow-down.png");
- }
- }
- };
-
- var setVoteNumber = function(object, number){
- var voteNumber = object.parent('div.'+ voteContainerId).find('div.'+ voteNumberClass);
- $(voteNumber).text(number);
- };
-
- var bindEvents = function(){
- // accept answers
- if(questionAuthorId == currentUserId){
- var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
- $(acceptedButtons).unbind('click').click(function(event){
- Vote.accept($(event.target));
- });
- }
- // set favorite question
- var favoriteButton = getFavoriteButton();
- favoriteButton.unbind('click').click(function(event){
- Vote.favorite($(event.target));
- });
-
- // question vote up
- var questionVoteUpButton = getQuestionVoteUpButton();
- questionVoteUpButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.questionUpVote);
- });
-
- var questionVoteDownButton = getQuestionVoteDownButton();
- questionVoteDownButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.questionDownVote);
- });
-
- var answerVoteUpButton = getAnswerVoteUpButtons();
- answerVoteUpButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.answerUpVote);
- });
-
- var answerVoteDownButton = getAnswerVoteDownButtons();
- answerVoteDownButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.answerDownVote);
- });
-
- getOffensiveQuestionFlag().unbind('click').click(function(event){
- Vote.offensive(this, VoteType.offensiveQuestion);
- });
-
- getOffensiveAnswerFlags().unbind('click').click(function(event){
- Vote.offensive(this, VoteType.offensiveAnswer);
- });
-
- getremoveQuestionLink().unbind('click').click(function(event){
- Vote.remove(this, VoteType.removeQuestion);
- });
-
- getquestionSubscribeUpdatesCheckbox().unbind('click').click(function(event){
- if (this.checked){
- Vote.vote($(event.target), VoteType.questionSubscribeUpdates);
- }
- else {
- Vote.vote($(event.target), VoteType.questionUnsubscribeUpdates);
- }
- });
-
- getremoveAnswersLinks().unbind('click').click(function(event){
- Vote.remove(this, VoteType.removeAnswer);
- });
- };
-
- var submit = function(object, voteType, callback) {
- $.ajax({
- type: "POST",
- cache: false,
- dataType: "json",
- url: scriptUrl + $.i18n._("questions/") + questionId + "/" + $.i18n._("vote/"),
- data: { "type": voteType, "postId": postId },
- error: handleFail,
- success: function(data){callback(object, voteType, data)}});
- };
-
- var handleFail = function(xhr, msg){
- alert("Callback invoke error: " + msg);
- };
-
- // callback function for Accept Answer action
- var callback_accept = function(object, voteType, data){
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, acceptAnonymousMessage);
- }
- else if(data.allowed == "-1"){
- showMessage(object, acceptOwnAnswerMessage);
- }
- else if(data.status == "1"){
- object.attr("src", scriptUrl + "content/images/vote-accepted.png");
- $("#"+answerContainerIdPrefix+postId).removeClass("accepted-answer");
- $("#"+commentLinkIdPrefix+postId).removeClass("comment-link-accepted");
- }
- else if(data.success == "1"){
- var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
- $(acceptedButtons).attr("src", scriptUrl + "content/images/vote-accepted.png");
- var answers = ("div[id^="+answerContainerIdPrefix +"]");
- $(answers).removeClass("accepted-answer");
- var commentLinks = ("div[id^="+answerContainerIdPrefix +"] div[id^="+ commentLinkIdPrefix +"]");
- $(commentLinks).removeClass("comment-link-accepted");
-
- object.attr("src", scriptUrl + "content/images/vote-accepted-on.png");
- $("#"+answerContainerIdPrefix+postId).addClass("accepted-answer");
- $("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted");
- }
- else{
- showMessage(object, data.message);
- }
- };
-
- var callback_favorite = function(object, voteType, data){
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
- }
- else if(data.status == "1"){
- object.attr("src", scriptUrl + "content/images/vote-favorite-off.png");
- var fav = getFavoriteNumber();
- fav.removeClass("my-favorite-number");
- if(data.count == 0)
- data.count = '';
- fav.text(data.count);
- }
- else if(data.success == "1"){
- object.attr("src", scriptUrl + "content/images/vote-favorite-on.png");
- var fav = getFavoriteNumber();
- fav.text(data.count);
- fav.addClass("my-favorite-number");
- }
- else{
- showMessage(object, data.message);
- }
- };
-
- var callback_vote = function(object, voteType, data){
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
- }
- else if (data.allowed == "-3"){
- showMessage(object, voteRequiredMoreVotes);
- }
- else if (data.allowed == "-2"){
- if (voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote){
- showMessage(object, upVoteRequiredScoreMessage);
- }
- else if (voteType == VoteType.questionDownVote || voteType == VoteType.answerDownVote){
- showMessage(object, downVoteRequiredScoreMessage);
- }
- }
- else if (data.allowed == "-1"){
- showMessage(object, voteOwnDeniedMessage);
- }
- else if (data.status == "2"){
- showMessage(object, voteDenyCancelMessage);
- }
- else if (data.status == "1"){
- setVoteImage(voteType, true, object);
- setVoteNumber(object, data.count);
- }
- else if (data.success == "1"){
- setVoteImage(voteType, false, object);
- setVoteNumber(object, data.count);
- if (data.message.length > 0){
- showMessage(object, data.message);
- }
- }
- };
-
- var callback_offensive = function(object, voteType, data){
- object = $(object);
- if (data.allowed == "0" && data.success == "0"){
- showMessage(object, offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
- }
- else if (data.allowed == "-3"){
- showMessage(object, offensiveNoFlagsLeftMessage);
- }
- else if (data.allowed == "-2"){
- showMessage(object, offensiveNoPermissionMessage);
- }
- else if (data.status == "1"){
- showMessage(object, offensiveTwiceMessage);
- }
- else if (data.success == "1"){
- $(object).children('span[class=darkred]').text("("+ data.count +")");
- }
- };
-
- var callback_remove = function(object, voteType, data){
- if (data.allowed == "0" && data.success == "0"){
- showMessage(object, removeAnonymousMessage.replace("{{QuestionID}}", questionId));
- }
- else if (data.success == "1"){
- if (voteType == VoteType.removeQuestion){
- window.location.href = scriptUrl + $.i18n._("questions/");
- }
- else {
- if (removeActionType == 'delete'){
- postNode.addClass('deleted');
- postRemoveLink.innerHTML = $.i18n._('undelete');
- showMessage(object, deletedMessage);
- }
- else if (removeActionType == 'undelete') {
- postNode.removeClass('deleted');
- postRemoveLink.innerHTML = $.i18n._('delete');
- showMessage(object, recoveredMessage);
- }
- }
- }
- };
-
- return {
- init : function(qId, qSlug, questionAuthor, userId){
- questionId = qId;
- questionSlug = qSlug;
- questionAuthorId = questionAuthor;
- currentUserId = userId;
- bindEvents();
- },
-
- // Accept answer public function
- accept: function(object){
- postId = object.attr("id").substring(imgIdPrefixAccept.length);
- submit(object, VoteType.acceptAnswer, callback_accept);
- },
-
- favorite: function(object){
- if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
- return false;
- }
- submit(object, VoteType.favorite, callback_favorite);
- },
-
- vote: function(object, voteType){
- if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId).replace("{{questionSlug}}", questionSlug));
- return false;
- }
- if (voteType == VoteType.answerUpVote){
- postId = object.attr("id").substring(imgIdPrefixAnswerVoteup.length);
- }
- else if (voteType == VoteType.answerDownVote){
- postId = object.attr("id").substring(imgIdPrefixAnswerVotedown.length);
- }
-
- submit(object, voteType, callback_vote);
- },
-
- offensive: function(object, voteType){
- if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage($(object), offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
- return false;
- }
- if (confirm(offensiveConfirmation)){
- postId = object.id.substr(object.id.lastIndexOf('-') + 1);
- submit(object, voteType, callback_offensive);
- }
- },
-
- remove: function(object, voteType){
- if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage($(object), removeAnonymousMessage.replace("{{QuestionID}}", questionId));
- return false;
- }
- bits = object.id.split('-');
- postId = bits.pop();/* this seems to be used within submit! */
- postType = bits.shift();
-
- var do_proceed = false;
- if (postType == 'answer'){
- postNode = $('#answer-container-' + postId);
- postRemoveLink = object;
- if (postNode.hasClass('deleted')){
- removeActionType = 'undelete';
- do_proceed = true;
- }
- else {
- removeActionType = 'delete';
- do_proceed = confirm(removeConfirmation);
- }
- }
- else {
- do_proceed = confirm(removeConfirmation);
- }
- if (do_proceed) {
- submit($(object), voteType, callback_remove);
- }
- }
- }
-} ();
-
-
-// site comments
-function createComments(type) {
- var objectType = type;
- var jDivInit = function(id) {
- return $("#comments-container-" + objectType + '-' + id);
- };
-
- var appendLoaderImg = function(id) {
- appendLoader("#comments-container-" + objectType + '-' + id);
- };
-
- var canPostComments = function(id) {
- var jHidden = $("#can-post-comments-" + objectType + '-' + id);
- return jHidden.val().toLowerCase() == "true";
- };
-
- var renderForm = function(id) {
- var formId = "form-comments-" + objectType + "-" + id;
- var jDiv = $('#comments-link-' + objectType + "-" + id).parent();
- $(jDiv).css('background','none');
- $(jDiv).css('padding-left',0);
- if (canPostComments(id)) {
- if (jDiv.find("#" + formId).length == 0) {
- var form = '<form id="' + formId + '" class="post-comments"><div>';
- form += '<textarea name="comment" cols="60" rows="5" maxlength="300" onblur="'+ objectType +'Comments.updateTextCounter(this)" ';
- form += 'onfocus="' + objectType + 'Comments.updateTextCounter(this)" onkeyup="'+ objectType +'Comments.updateTextCounter(this)"></textarea>';
- form += '<input type="submit" value="'
- + $.i18n._('add comment') + '" /><br><span class="text-counter"></span>';
- form += '<span class="form-error"></span></div></form>';
-
- jDiv.append(form);
-
- setupFormValidation("#" + formId,
- { comment: { required: true, minlength: 10} }, '',
- function() { postComment(id, formId); });
- }
- }
- else {
- var divId = "comments-rep-needed-" + objectType + '-' + id;
- if (jDiv.find("#" + divId).length == 0) {
- jDiv.append('<p id="' + divId + '" class="comment">'
- + $.i18n._('to comment, need') + ' ' +
- + repNeededForComments + ' ' + $.i18n._('community karma points')
- + '<a href="' + scriptUrl + $.i18n._('faq/') + '" class="comment-user">'
- + $.i18n._('please see') + 'faq</a></span></p>');
- }
- }
- };
-
- var getComments = function(id, jDiv) {
- //appendLoaderImg(id);
- $.getJSON(scriptUrl + objectType + "s/" + id + "/" + $.i18n._("comments/")
- , function(json) { showComments(id, json); });
- };
-
- var showComments = function(id, json) {
- var jDiv = jDivInit(id);
-
- //jDiv = jDiv.find("div.comments"); // this div should contain any fetched comments..
- //jDiv.find("div[id^='comment-" + objectType + "-'" + "]").remove(); // clean previous calls..
- jDiv.children().remove();
- removeLoader();
- if (json && json.length > 0) {
- for (var i = 0; i < json.length; i++)
- renderComment(jDiv, json[i]);
- jDiv.children().show();
- }
- };
-
- var renderDeleteCommentIcon = function(post_id, delete_url){
- if (canPostComments(post_id)){
- var html = '';
- var img = scriptUrl + "content/images/close-small.png";
- var imgHover = scriptUrl + "content/images/close-small-hover.png";
- html += '<img class="delete-icon" onclick="' + objectType + 'Comments.deleteComment($(this), ' + post_id + ', \'' + delete_url + '\')" src="' + img;
- html += '" onmouseover="$(this).attr(\'src\', \'' + imgHover + '\')" onmouseout="$(this).attr(\'src\', \'' + img
- html += '\')" title="' + $.i18n._('delete this comment') + '" />';
- return html;
- }
- else{
- return '';
- }
- }
-
- // {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
- var renderComment = function(jDiv, json) {
- var html = '<p id="comment-' + json.id + '" class="comment" style="display:none">' + json.text;
- html += json.user_url ? ' - <a href="' + json.user_url + '"' : '<span';
- html += ' class="comment-user">' + json.user_display_name + (json.user_url ? '</a>' : '</span>');
- html += ' (' + json.comment_age + ')';
-
- if (json.delete_url){
- html += renderDeleteCommentIcon(json.object_id, json.delete_url);
- }
-
- if (json.delete_url) {
- }
-
- html += '</p>';
-
- jDiv.append(html);
- };
-
- var postComment = function(id, formId) {
- //appendLoaderImg(id);
-
- var formSelector = "#" + formId;
- var textarea = $(formSelector + " textarea");
-
- //todo fix url translations!!!
- $.ajax({
- type: "POST",
- url: scriptUrl + objectType + "s/" + id + "/" + $.i18n._("comments/"),
- dataType: "json",
- data: { comment: textarea.val() },
- success: function(json) {
- showComments(id, json);
- textarea.val("");
- commentsFactory[objectType].updateTextCounter(textarea);
- enableSubmitButton(formSelector);
- },
- error: function(res, textStatus, errorThrown) {
- removeLoader();
- showMessage(formSelector, res.responseText);
- enableSubmitButton(formSelector);
- }
- });
- };
-
- // public methods..
- return {
-
- init: function() {
- // Setup "show comments" clicks..
- $("a[id^='comments-link-" + objectType + "-" + "']").unbind("click").click(function() {
- commentsFactory[objectType].show($(this).attr("id").substr(("comments-link-" + objectType + "-").length));
- });
-
- var cBox = $("[id^='comments-container-" + objectType + "']");
- cBox.each( function(i){
- var post_id = $(this).attr('id').replace('comments-container-' + objectType + '-', '');
- $(this).children().each(
- function(i){
- var comment_id = $(this).attr('id').replace('comment-','');
- var delete_url = scriptUrl + objectType + 's/' + post_id + '/'
- + $.i18n._('comments/') + comment_id + '/' + $.i18n._('delete/');
- var html = $(this).html();
- var CommentsClass;
- if (objectType == 'question'){
- CommentsClass = questionComments;
- }
- else if (objectType == 'answer') {
- CommentsClass = answerComments;
- }
- var delete_icon = $(this).find('img.delete-icon');
- delete_icon.click(function(){CommentsClass.deleteComment($(this),comment_id,delete_url);});
- delete_icon.unbind('mouseover').bind('mouseover',
- function(){
- $(this).attr('src',scriptUrl + 'content/images/close-small-hover.png');
- }
- );
- delete_icon.unbind('mouseout').bind('mouseout',
- function(){
- $(this).attr('src',scriptUrl + 'content/images/close-small.png');
- }
- );
- }
- );
- });
- },
-
- show: function(id) {
- var jDiv = jDivInit(id);
- getComments(id, jDiv);
- renderForm(id);
- jDiv.show();
-
- var link = $('#comments-link-' + objectType + '-' + id);
- if (canPostComments(id)) link.parent().find("textarea").get(0).focus();
- link.remove();
- },
-
- hide: function(id) {
- var jDiv = jDivInit(id);
- var len = jDiv.children("div.comments").children().length;
- var anchorText = len == 0 ? $.i18n._('add a comment') : $.i18n._('comments') + ' (<b>' + len + "</b>)";
-
- jDiv.hide();
- jDiv.siblings("a").unbind("click").click(function() { commentsFactory[objectType].show(id); }).html(anchorText);
- jDiv.children("div.comments").children().hide();
- },
-
- deleteComment: function(jImg, id, deleteUrl) {
- if (confirm($.i18n._('confirm delete comment'))) {
- jImg.hide();
- $.post(deleteUrl, { dataNeeded: "forIIS7" }, function(json) {
- var par = jImg.parent();
- par.remove();
- }, "json");
- }
- },
-
- updateTextCounter: function(textarea) {
- var length = textarea.value ? textarea.value.length : 0;
- var color = length > 270 ? "#f00" : length > 200 ? "#f60" : "#999";
- var jSpan = $(textarea).siblings("span.text-counter");
- jSpan.html($.i18n._('can write')
- + (300 - length) + ' '
- + $.i18n._('characters')).css("color", color);
- }
- };
-}
-
-var questionComments = createComments('question');
-var answerComments = createComments('answer');
-
-$().ready(function() {
- questionComments.init();
- answerComments.init();
-});
-
-var commentsFactory = {'question' : questionComments, 'answer' : answerComments};
-
-/*
-Prettify
-http://www.apache.org/licenses/LICENSE-2.0
-*/
-var PR_SHOULD_USE_CONTINUATION = true; var PR_TAB_WIDTH = 8; var PR_normalizedHtml; var PR; var prettyPrintOne; var prettyPrint; function _pr_isIE6() { var isIE6 = navigator && navigator.userAgent && /\bMSIE 6\./.test(navigator.userAgent); _pr_isIE6 = function() { return isIE6; }; return isIE6; } (function() { function wordSet(words) { words = words.split(/ /g); var set = {}; for (var i = words.length; --i >= 0; ) { var w = words[i]; if (w) { set[w] = null; } } return set; } var FLOW_CONTROL_KEYWORDS = "break continue do else for if return while "; var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " + "double enum extern float goto int long register short signed sizeof " + "static struct switch typedef union unsigned void volatile "; var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " + "new operator private protected public this throw true try "; var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " + "concept concept_map const_cast constexpr decltype " + "dynamic_cast explicit export friend inline late_check " + "mutable namespace nullptr reinterpret_cast static_assert static_cast " + "template typeid typename typeof using virtual wchar_t where "; var JAVA_KEYWORDS = COMMON_KEYWORDS + "boolean byte extends final finally implements import instanceof null " + "native package strictfp super synchronized throws transient "; var CSHARP_KEYWORDS = JAVA_KEYWORDS + "as base by checked decimal delegate descending event " + "fixed foreach from group implicit in interface internal into is lock " + "object out override orderby params readonly ref sbyte sealed " + "stackalloc string select uint ulong unchecked unsafe ushort var "; var JSCRIPT_KEYWORDS = COMMON_KEYWORDS + "debugger eval export function get null set undefined var with " + "Infinity NaN "; var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " + "goto if import last local my next no our print package redo require " + "sub undef unless until use wantarray while BEGIN END "; var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " + "elif except exec finally from global import in is lambda " + "nonlocal not or pass print raise try with yield " + "False True None "; var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" + " defined elsif end ensure false in module next nil not or redo rescue " + "retry self super then true undef unless until when yield BEGIN END "; var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " + "function in local set then until "; var ALL_KEYWORDS = (CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS + PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS); var PR_STRING = 'str'; var PR_KEYWORD = 'kwd'; var PR_COMMENT = 'com'; var PR_TYPE = 'typ'; var PR_LITERAL = 'lit'; var PR_PUNCTUATION = 'pun'; var PR_PLAIN = 'pln'; var PR_TAG = 'tag'; var PR_DECLARATION = 'dec'; var PR_SOURCE = 'src'; var PR_ATTRIB_NAME = 'atn'; var PR_ATTRIB_VALUE = 'atv'; var PR_NOCODE = 'nocode'; function isWordChar(ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); } function spliceArrayInto(inserted, container, containerPosition, countReplaced) { inserted.unshift(containerPosition, countReplaced || 0); try { container.splice.apply(container, inserted); } finally { inserted.splice(0, 2); } } var REGEXP_PRECEDER_PATTERN = function() { var preceders = ["!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=", "&=", "(", "*", "*=", "+=", ",", "-=", "->", "/", "/=", ":", "::", ";", "<", "<<", "<<=", "<=", "=", "==", "===", ">", ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[", "^", "^=", "^^", "^^=", "{", "|", "|=", "||", "||=", "~", "break", "case", "continue", "delete", "do", "else", "finally", "instanceof", "return", "throw", "try", "typeof"]; var pattern = '(?:' + '(?:(?:^|[^0-9.])\\.{1,3})|' + '(?:(?:^|[^\\+])\\+)|' + '(?:(?:^|[^\\-])-)'; for (var i = 0; i < preceders.length; ++i) { var preceder = preceders[i]; if (isWordChar(preceder.charAt(0))) { pattern += '|\\b' + preceder; } else { pattern += '|' + preceder.replace(/([^=<>:&])/g, '\\$1'); } } pattern += '|^)\\s*$'; return new RegExp(pattern); } (); var pr_amp = /&/g; var pr_lt = /</g; var pr_gt = />/g; var pr_quot = /\"/g; function attribToHtml(str) { return str.replace(pr_amp, '&amp;').replace(pr_lt, '&lt;').replace(pr_gt, '&gt;').replace(pr_quot, '&quot;'); } function textToHtml(str) { return str.replace(pr_amp, '&amp;').replace(pr_lt, '&lt;').replace(pr_gt, '&gt;'); } var pr_ltEnt = /&lt;/g; var pr_gtEnt = /&gt;/g; var pr_aposEnt = /&apos;/g; var pr_quotEnt = /&quot;/g; var pr_ampEnt = /&amp;/g; var pr_nbspEnt = /&nbsp;/g; function htmlToText(html) { var pos = html.indexOf('&'); if (pos < 0) { return html; } for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0; ) { var end = html.indexOf(';', pos); if (end >= 0) { var num = html.substring(pos + 3, end); var radix = 10; if (num && num.charAt(0) === 'x') { num = num.substring(1); radix = 16; } var codePoint = parseInt(num, radix); if (!isNaN(codePoint)) { html = (html.substring(0, pos) + String.fromCharCode(codePoint) + html.substring(end + 1)); } } } return html.replace(pr_ltEnt, '<').replace(pr_gtEnt, '>').replace(pr_aposEnt, "'").replace(pr_quotEnt, '"').replace(pr_ampEnt, '&').replace(pr_nbspEnt, ' '); } function isRawContent(node) { return 'XMP' === node.tagName; } function normalizedHtml(node, out) { switch (node.nodeType) { case 1: var name = node.tagName.toLowerCase(); out.push('<', name); for (var i = 0; i < node.attributes.length; ++i) { var attr = node.attributes[i]; if (!attr.specified) { continue; } out.push(' '); normalizedHtml(attr, out); } out.push('>'); for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } if (node.firstChild || !/^(?:br|link|img)$/.test(name)) { out.push('<\/', name, '>'); } break; case 2: out.push(node.name.toLowerCase(), '="', attribToHtml(node.value), '"'); break; case 3: case 4: out.push(textToHtml(node.nodeValue)); break; } } var PR_innerHtmlWorks = null; function getInnerHtml(node) { if (null === PR_innerHtmlWorks) { var testNode = document.createElement('PRE'); testNode.appendChild(document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />')); PR_innerHtmlWorks = !/</.test(testNode.innerHTML); } if (PR_innerHtmlWorks) { var content = node.innerHTML; if (isRawContent(node)) { content = textToHtml(content); } return content; } var out = []; for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } return out.join(''); } function makeTabExpander(tabWidth) { var SPACES = ' '; var charInLine = 0; return function(plainText) { var out = null; var pos = 0; for (var i = 0, n = plainText.length; i < n; ++i) { var ch = plainText.charAt(i); switch (ch) { case '\t': if (!out) { out = []; } out.push(plainText.substring(pos, i)); var nSpaces = tabWidth - (charInLine % tabWidth); charInLine += nSpaces; for (; nSpaces >= 0; nSpaces -= SPACES.length) { out.push(SPACES.substring(0, nSpaces)); } pos = i + 1; break; case '\n': charInLine = 0; break; default: ++charInLine; } } if (!out) { return plainText; } out.push(plainText.substring(pos)); return out.join(''); }; } var pr_chunkPattern = /(?:[^<]+|<!--[\s\S]*?-->|<!\[CDATA\[([\s\S]*?)\]\]>|<\/?[a-zA-Z][^>]*>|<)/g; var pr_commentPrefix = /^<!--/; var pr_cdataPrefix = /^<\[CDATA\[/; var pr_brPrefix = /^<br\b/i; var pr_tagNameRe = /^<(\/?)([a-zA-Z]+)/; function extractTags(s) { var matches = s.match(pr_chunkPattern); var sourceBuf = []; var sourceBufLen = 0; var extractedTags = []; if (matches) { for (var i = 0, n = matches.length; i < n; ++i) { var match = matches[i]; if (match.length > 1 && match.charAt(0) === '<') { if (pr_commentPrefix.test(match)) { continue; } if (pr_cdataPrefix.test(match)) { sourceBuf.push(match.substring(9, match.length - 3)); sourceBufLen += match.length - 12; } else if (pr_brPrefix.test(match)) { sourceBuf.push('\n'); ++sourceBufLen; } else { if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) { var name = match.match(pr_tagNameRe)[2]; var depth = 1; end_tag_loop: for (var j = i + 1; j < n; ++j) { var name2 = matches[j].match(pr_tagNameRe); if (name2 && name2[2] === name) { if (name2[1] === '/') { if (--depth === 0) { break end_tag_loop; } } else { ++depth; } } } if (j < n) { extractedTags.push(sourceBufLen, matches.slice(i, j + 1).join('')); i = j; } else { extractedTags.push(sourceBufLen, match); } } else { extractedTags.push(sourceBufLen, match); } } } else { var literalText = htmlToText(match); sourceBuf.push(literalText); sourceBufLen += literalText.length; } } } return { source: sourceBuf.join(''), tags: extractedTags }; } function isNoCodeTag(tag) { return !!tag.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g, ' $1="$2$3$4"').match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/); } function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) { var shortcuts = {}; (function() { var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns); for (var i = allPatterns.length; --i >= 0; ) { var patternParts = allPatterns[i]; var shortcutChars = patternParts[3]; if (shortcutChars) { for (var c = shortcutChars.length; --c >= 0; ) { shortcuts[shortcutChars.charAt(c)] = patternParts; } } } })(); var nPatterns = fallthroughStylePatterns.length; var notWs = /\S/; return function(sourceCode, opt_basePos) { opt_basePos = opt_basePos || 0; var decorations = [opt_basePos, PR_PLAIN]; var lastToken = ''; var pos = 0; var tail = sourceCode; while (tail.length) { var style; var token = null; var match; var patternParts = shortcuts[tail.charAt(0)]; if (patternParts) { match = tail.match(patternParts[1]); token = match[0]; style = patternParts[0]; } else { for (var i = 0; i < nPatterns; ++i) { patternParts = fallthroughStylePatterns[i]; var contextPattern = patternParts[2]; if (contextPattern && !contextPattern.test(lastToken)) { continue; } match = tail.match(patternParts[1]); if (match) { token = match[0]; style = patternParts[0]; break; } } if (!token) { style = PR_PLAIN; token = tail.substring(0, 1); } } decorations.push(opt_basePos + pos, style); pos += token.length; tail = tail.substring(token.length); if (style !== PR_COMMENT && notWs.test(token)) { lastToken = token; } } return decorations; }; } var PR_MARKUP_LEXER = createSimpleLexer([], [[PR_PLAIN, /^[^<]+/, null], [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/, null], [PR_COMMENT, /^<!--[\s\S]*?(?:-->|$)/, null], [PR_SOURCE, /^<\?[\s\S]*?(?:\?>|$)/, null], [PR_SOURCE, /^<%[\s\S]*?(?:%>|$)/, null], [PR_SOURCE, /^<(script|style|xmp)\b[^>]*>[\s\S]*?<\/\1\b[^>]*>/i, null], [PR_TAG, /^<\/?\w[^<>]*>/, null]]); var PR_SOURCE_CHUNK_PARTS = /^(<[^>]*>)([\s\S]*)(<\/[^>]*>)$/; function tokenizeMarkup(source) { var decorations = PR_MARKUP_LEXER(source); for (var i = 0; i < decorations.length; i += 2) { if (decorations[i + 1] === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var sourceChunk = source.substring(start, end); var match = sourceChunk.match(PR_SOURCE_CHUNK_PARTS); if (match) { decorations.splice(i, 2, start, PR_TAG, start + match[1].length, PR_SOURCE, start + match[1].length + (match[2] || '').length, PR_TAG); } } } return decorations; } var PR_TAG_LEXER = createSimpleLexer([[PR_ATTRIB_VALUE, /^\'[^\']*(?:\'|$)/, null, "'"], [PR_ATTRIB_VALUE, /^\"[^\"]*(?:\"|$)/, null, '"'], [PR_PUNCTUATION, /^[<>\/=]+/, null, '<>/=']], [[PR_TAG, /^[\w:\-]+/, /^</], [PR_ATTRIB_VALUE, /^[\w\-]+/, /^=/], [PR_ATTRIB_NAME, /^[\w:\-]+/, null], [PR_PLAIN, /^\s+/, null, ' \t\r\n']]); function splitTagAttributes(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_TAG) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var chunk = source.substring(start, end); var subDecorations = PR_TAG_LEXER(chunk, start); spliceArrayInto(subDecorations, decorations, i, 2); i += subDecorations.length - 2; } } return decorations; } function sourceDecorator(options) { var shortcutStylePatterns = [], fallthroughStylePatterns = []; if (options.tripleQuotedStrings) { shortcutStylePatterns.push([PR_STRING, /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/, null, '\'"']); } else if (options.multiLineStrings) { shortcutStylePatterns.push([PR_STRING, /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/, null, '\'"`']); } else { shortcutStylePatterns.push([PR_STRING, /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/, null, '"\'']); } fallthroughStylePatterns.push([PR_PLAIN, /^(?:[^\'\"\`\/\#]+)/, null, ' \r\n']); if (options.hashComments) { shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']); } if (options.cStyleComments) { fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]); fallthroughStylePatterns.push([PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]); } if (options.regexLiterals) { var REGEX_LITERAL = ('^/(?=[^/*])' + '(?:[^/\\x5B\\x5C]' + '|\\x5C[\\s\\S]' + '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+' + '(?:/|$)'); fallthroughStylePatterns.push([PR_STRING, new RegExp(REGEX_LITERAL), REGEXP_PRECEDER_PATTERN]); } var keywords = wordSet(options.keywords); options = null; var splitStringAndCommentTokens = createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns); var styleLiteralIdentifierPuncRecognizer = createSimpleLexer([], [[PR_PLAIN, /^\s+/, null, ' \r\n'], [PR_PLAIN, /^[a-z_$@][a-z_$@0-9]*/i, null], [PR_LITERAL, /^0x[a-f0-9]+[a-z]/i, null], [PR_LITERAL, /^(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d+)(?:e[+\-]?\d+)?[a-z]*/i, null, '123456789'], [PR_PUNCTUATION, /^[^\s\w\.$@]+/, null]]); function splitNonStringNonCommentTokens(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_PLAIN) { var start, end, chunk, subDecs; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; chunk = source.substring(start, end); subDecs = styleLiteralIdentifierPuncRecognizer(chunk, start); for (var j = 0, m = subDecs.length; j < m; j += 2) { var subStyle = subDecs[j + 1]; if (subStyle === PR_PLAIN) { var subStart = subDecs[j]; var subEnd = j + 2 < m ? subDecs[j + 2] : chunk.length; var token = source.substring(subStart, subEnd); if (token === '.') { subDecs[j + 1] = PR_PUNCTUATION; } else if (token in keywords) { subDecs[j + 1] = PR_KEYWORD; } else if (/^@?[A-Z][A-Z$]*[a-z][A-Za-z$]*$/.test(token)) { subDecs[j + 1] = token.charAt(0) === '@' ? PR_LITERAL : PR_TYPE; } } } spliceArrayInto(subDecs, decorations, i, 2); i += subDecs.length - 2; } } return decorations; } return function(sourceCode) { var decorations = splitStringAndCommentTokens(sourceCode); decorations = splitNonStringNonCommentTokens(sourceCode, decorations); return decorations; }; } var decorateSource = sourceDecorator({ keywords: ALL_KEYWORDS, hashComments: true, cStyleComments: true, multiLineStrings: true, regexLiterals: true }); function splitSourceNodes(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var subDecorations = decorateSource(source.substring(start, end)); for (var j = 0, m = subDecorations.length; j < m; j += 2) { subDecorations[j] += start; } spliceArrayInto(subDecorations, decorations, i, 2); i += subDecorations.length - 2; } } return decorations; } function splitSourceAttributes(source, decorations) { var nextValueIsSource = false; for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; var start, end; if (style === PR_ATTRIB_NAME) { start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; nextValueIsSource = /^on|^style$/i.test(source.substring(start, end)); } else if (style === PR_ATTRIB_VALUE) { if (nextValueIsSource) { start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var attribValue = source.substring(start, end); var attribLen = attribValue.length; var quoted = (attribLen >= 2 && /^[\"\']/.test(attribValue) && attribValue.charAt(0) === attribValue.charAt(attribLen - 1)); var attribSource; var attribSourceStart; var attribSourceEnd; if (quoted) { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue; } else { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue.substring(1, attribValue.length - 1); } var attribSourceDecorations = decorateSource(attribSource); for (var j = 0, m = attribSourceDecorations.length; j < m; j += 2) { attribSourceDecorations[j] += attribSourceStart; } if (quoted) { attribSourceDecorations.push(attribSourceEnd, PR_ATTRIB_VALUE); spliceArrayInto(attribSourceDecorations, decorations, i + 2, 0); } else { spliceArrayInto(attribSourceDecorations, decorations, i, 2); } } nextValueIsSource = false; } } return decorations; } function decorateMarkup(sourceCode) { var decorations = tokenizeMarkup(sourceCode); decorations = splitTagAttributes(sourceCode, decorations); decorations = splitSourceNodes(sourceCode, decorations); decorations = splitSourceAttributes(sourceCode, decorations); return decorations; } function recombineTagsAndDecorations(sourceText, extractedTags, decorations) { var html = []; var outputIdx = 0; var openDecoration = null; var currentDecoration = null; var tagPos = 0; var decPos = 0; var tabExpander = makeTabExpander(PR_TAB_WIDTH); var adjacentSpaceRe = /([\r\n ]) /g; var startOrSpaceRe = /(^| ) /gm; var newlineRe = /\r\n?|\n/g; var trailingSpaceRe = /[ \r\n]$/; var lastWasSpace = true; function emitTextUpTo(sourceIdx) { if (sourceIdx > outputIdx) { if (openDecoration && openDecoration !== currentDecoration) { html.push('</span>'); openDecoration = null; } if (!openDecoration && currentDecoration) { openDecoration = currentDecoration; html.push('<span class="', openDecoration, '">'); } var htmlChunk = textToHtml(tabExpander(sourceText.substring(outputIdx, sourceIdx))).replace(lastWasSpace ? startOrSpaceRe : adjacentSpaceRe, '$1&nbsp;'); lastWasSpace = trailingSpaceRe.test(htmlChunk); html.push(htmlChunk.replace(newlineRe, '<br />')); outputIdx = sourceIdx; } } while (true) { var outputTag; if (tagPos < extractedTags.length) { if (decPos < decorations.length) { outputTag = extractedTags[tagPos] <= decorations[decPos]; } else { outputTag = true; } } else { outputTag = false; } if (outputTag) { emitTextUpTo(extractedTags[tagPos]); if (openDecoration) { html.push('</span>'); openDecoration = null; } html.push(extractedTags[tagPos + 1]); tagPos += 2; } else if (decPos < decorations.length) { emitTextUpTo(decorations[decPos]); currentDecoration = decorations[decPos + 1]; decPos += 2; } else { break; } } emitTextUpTo(sourceText.length); if (openDecoration) { html.push('</span>'); } return html.join(''); } var langHandlerRegistry = {}; function registerLangHandler(handler, fileExtensions) { for (var i = fileExtensions.length; --i >= 0; ) { var ext = fileExtensions[i]; if (!langHandlerRegistry.hasOwnProperty(ext)) { langHandlerRegistry[ext] = handler; } else if ('console' in window) { console.log('cannot override language handler %s', ext); } } } registerLangHandler(decorateSource, ['default-code']); registerLangHandler(decorateMarkup, ['default-markup', 'html', 'htm', 'xhtml', 'xml', 'xsl']); registerLangHandler(sourceDecorator({ keywords: CPP_KEYWORDS, hashComments: true, cStyleComments: true }), ['c', 'cc', 'cpp', 'cs', 'cxx', 'cyc']); registerLangHandler(sourceDecorator({ keywords: JAVA_KEYWORDS, cStyleComments: true }), ['java']); registerLangHandler(sourceDecorator({ keywords: SH_KEYWORDS, hashComments: true, multiLineStrings: true }), ['bsh', 'csh', 'sh']); registerLangHandler(sourceDecorator({ keywords: PYTHON_KEYWORDS, hashComments: true, multiLineStrings: true, tripleQuotedStrings: true }), ['cv', 'py']); registerLangHandler(sourceDecorator({ keywords: PERL_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['perl', 'pl', 'pm']); registerLangHandler(sourceDecorator({ keywords: RUBY_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['rb']); registerLangHandler(sourceDecorator({ keywords: JSCRIPT_KEYWORDS, cStyleComments: true, regexLiterals: true }), ['js']); function prettyPrintOne(sourceCodeHtml, opt_langExtension) { try { var sourceAndExtractedTags = extractTags(sourceCodeHtml); var source = sourceAndExtractedTags.source; var extractedTags = sourceAndExtractedTags.tags; if (!langHandlerRegistry.hasOwnProperty(opt_langExtension)) { opt_langExtension = /^\s*</.test(source) ? 'default-markup' : 'default-code'; } var decorations = langHandlerRegistry[opt_langExtension].call({}, source); return recombineTagsAndDecorations(source, extractedTags, decorations); } catch (e) { if ('console' in window) { console.log(e); console.trace(); } return sourceCodeHtml; } } function prettyPrint(opt_whenDone) { var isIE6 = _pr_isIE6(); var codeSegments = [document.getElementsByTagName('pre'), document.getElementsByTagName('code'), document.getElementsByTagName('xmp')]; var elements = []; for (var i = 0; i < codeSegments.length; ++i) { for (var j = 0; j < codeSegments[i].length; ++j) { elements.push(codeSegments[i][j]); } } codeSegments = null; var k = 0; function doWork() { var endTime = (PR_SHOULD_USE_CONTINUATION ? new Date().getTime() + 250 : Infinity); for (; k < elements.length && new Date().getTime() < endTime; k++) { var cs = elements[k]; if (cs.className && cs.className.indexOf('prettyprint') >= 0) { var langExtension = cs.className.match(/\blang-(\w+)\b/); if (langExtension) { langExtension = langExtension[1]; } var nested = false; for (var p = cs.parentNode; p; p = p.parentNode) { if ((p.tagName === 'pre' || p.tagName === 'code' || p.tagName === 'xmp') && p.className && p.className.indexOf('prettyprint') >= 0) { nested = true; break; } } if (!nested) { var content = getInnerHtml(cs); content = content.replace(/(?:\r\n?|\n)$/, ''); var newContent = prettyPrintOne(content, langExtension); if (!isRawContent(cs)) { cs.innerHTML = newContent; } else { var pre = document.createElement('PRE'); for (var i = 0; i < cs.attributes.length; ++i) { var a = cs.attributes[i]; if (a.specified) { var aname = a.name.toLowerCase(); if (aname === 'class') { pre.className = a.value; } else { pre.setAttribute(a.name, a.value); } } } pre.innerHTML = newContent; cs.parentNode.replaceChild(pre, cs); cs = pre; } if (isIE6 && cs.tagName === 'PRE') { var lineBreaks = cs.getElementsByTagName('br'); for (var j = lineBreaks.length; --j >= 0; ) { var lineBreak = lineBreaks[j]; lineBreak.parentNode.replaceChild(document.createTextNode('\r\n'), lineBreak); } } } } } if (k < elements.length) { setTimeout(doWork, 250); } else if (opt_whenDone) { opt_whenDone(); } } doWork(); } window['PR_normalizedHtml'] = normalizedHtml; window['prettyPrintOne'] = prettyPrintOne; window['prettyPrint'] = prettyPrint; window['PR'] = { 'createSimpleLexer': createSimpleLexer, 'registerLangHandler': registerLangHandler, 'sourceDecorator': sourceDecorator, 'PR_ATTRIB_NAME': PR_ATTRIB_NAME, 'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE, 'PR_COMMENT': PR_COMMENT, 'PR_DECLARATION': PR_DECLARATION, 'PR_KEYWORD': PR_KEYWORD, 'PR_LITERAL': PR_LITERAL, 'PR_NOCODE': PR_NOCODE, 'PR_PLAIN': PR_PLAIN, 'PR_PUNCTUATION': PR_PUNCTUATION, 'PR_SOURCE': PR_SOURCE, 'PR_STRING': PR_STRING, 'PR_TAG': PR_TAG, 'PR_TYPE': PR_TYPE }; })();
+ļ»æ/*
+Scripts for cnprog.com
+Project Name: Lanai
+All Rights Resevred 2008. CNPROG.COM
+*/
+var lanai =
+{
+ /**
+ * Finds any <pre><code></code></pre> tags which aren't registered for
+ * pretty printing, adds the appropriate class name and invokes prettify.
+ */
+ highlightSyntax: function(){
+ var styled = false;
+ $("pre code").parent().each(function(){
+ if (!$(this).hasClass('prettyprint')){
+ $(this).addClass('prettyprint');
+ styled = true;
+ }
+ });
+
+ if (styled){
+ prettyPrint();
+ }
+ }
+};
+
+var Vote = function(){
+ // All actions are related to a question
+ var questionId;
+ //question slug to build redirect urls
+ var questionSlug;
+ // The object we operate on actually. It can be a question or an answer.
+ var postId;
+ var questionAuthorId;
+ var currentUserId;
+ var answerContainerIdPrefix = 'answer-container-';
+ var voteContainerId = 'vote-buttons';
+ var imgIdPrefixAccept = 'answer-img-accept-';
+ var imgClassPrefixFavorite = 'question-img-favorite';
+ var imgIdPrefixQuestionVoteup = 'question-img-upvote-';
+ var imgIdPrefixQuestionVotedown = 'question-img-downvote-';
+ var imgIdPrefixAnswerVoteup = 'answer-img-upvote-';
+ var imgIdPrefixAnswerVotedown = 'answer-img-downvote-';
+ var divIdFavorite = 'favorite-number';
+ var commentLinkIdPrefix = 'comment-';
+ var voteNumberClass = "vote-number";
+ var offensiveIdPrefixQuestionFlag = 'question-offensive-flag-';
+ var offensiveIdPrefixAnswerFlag = 'answer-offensive-flag-';
+ var offensiveClassFlag = 'offensive-flag';
+ var questionControlsId = 'question-controls';
+ var removeQuestionLinkIdPrefix = 'question-delete-link-';
+ var removeAnswerLinkIdPrefix = 'answer-delete-link-';
+ var questionSubscribeUpdates = 'question-subscribe-updates';
+
+ var acceptAnonymousMessage = $.i18n._('insufficient privilege');
+ var acceptOwnAnswerMessage = $.i18n._('cannot pick own answer as best');
+
+ var pleaseLogin = "<a href='" + scriptUrl + $.i18n._("account/") + $.i18n._("signin/")
+ + "?next=" + scriptUrl + $.i18n._("question/") + "{{QuestionID}}/{{questionSlug}}'>"
+ + $.i18n._('please login') + "</a>";
+
+ var pleaseSeeFAQ = $.i18n._('please see') + "<a href='" + scriptUrl + $.i18n._("faq/") + "'>faq</a>";
+
+ var favoriteAnonymousMessage = $.i18n._('anonymous users cannot select favorite questions')
+ var voteAnonymousMessage = $.i18n._('anonymous users cannot vote') + pleaseLogin;
+ var upVoteRequiredScoreMessage = $.i18n._('>15 points requried to upvote') + pleaseSeeFAQ;
+ var downVoteRequiredScoreMessage = $.i18n._('>100 points required to downvote') + pleaseSeeFAQ;
+ var voteOwnDeniedMessage = $.i18n._('cannot vote for own posts');
+ var voteRequiredMoreVotes = $.i18n._('daily vote cap exhausted') + pleaseSeeFAQ;
+ var voteDenyCancelMessage = $.i18n._('cannot revoke old vote') + pleaseSeeFAQ;
+ var offensiveConfirmation = $.i18n._('please confirm offensive');
+ var offensiveAnonymousMessage = $.i18n._('anonymous users cannot flag offensive posts') + pleaseLogin;
+ var offensiveTwiceMessage = $.i18n._('cannot flag message as offensive twice') + pleaseSeeFAQ;
+ var offensiveNoFlagsLeftMessage = $.i18n._('flag offensive cap exhausted') + pleaseSeeFAQ;
+ var offensiveNoPermissionMessage = $.i18n._('need >15 points to report spam') + pleaseSeeFAQ;
+ var removeConfirmation = $.i18n._('confirm delete');
+ var removeAnonymousMessage = $.i18n._('anonymous users cannot delete/undelete');
+ var recoveredMessage = $.i18n._('post recovered');
+ var deletedMessage = $.i18n._('post deleted');
+
+ var VoteType = {
+ acceptAnswer : 0,
+ questionUpVote : 1,
+ questionDownVote : 2,
+ favorite : 4,
+ answerUpVote: 5,
+ answerDownVote:6,
+ offensiveQuestion : 7,
+ offensiveAnswer:8,
+ removeQuestion: 9,
+ removeAnswer:10,
+ questionSubscribeUpdates:11,
+ questionUnsubscribeUpdates:12
+ };
+
+ var getFavoriteButton = function(){
+ var favoriteButton = 'div.'+ voteContainerId +' img[class='+ imgClassPrefixFavorite +']';
+ return $(favoriteButton);
+ };
+ var getFavoriteNumber = function(){
+ var favoriteNumber = '#'+ divIdFavorite ;
+ return $(favoriteNumber);
+ };
+ var getQuestionVoteUpButton = function(){
+ var questionVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVoteup +']';
+ return $(questionVoteUpButton);
+ };
+ var getQuestionVoteDownButton = function(){
+ var questionVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVotedown +']';
+ return $(questionVoteDownButton);
+ };
+ var getAnswerVoteUpButtons = function(){
+ var answerVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVoteup +']';
+ return $(answerVoteUpButton);
+ };
+ var getAnswerVoteDownButtons = function(){
+ var answerVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVotedown +']';
+ return $(answerVoteDownButton);
+ };
+ var getAnswerVoteUpButton = function(id){
+ var answerVoteUpButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVoteup + id + ']';
+ return $(answerVoteUpButton);
+ };
+ var getAnswerVoteDownButton = function(id){
+ var answerVoteDownButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVotedown + id + ']';
+ return $(answerVoteDownButton);
+ };
+
+ var getOffensiveQuestionFlag = function(){
+ var offensiveQuestionFlag = '#question-table span[class='+ offensiveClassFlag +']';
+ return $(offensiveQuestionFlag);
+ };
+
+ var getOffensiveAnswerFlags = function(){
+ var offensiveQuestionFlag = 'div.answer span[class='+ offensiveClassFlag +']';
+ return $(offensiveQuestionFlag);
+ };
+
+ var getremoveQuestionLink = function(){
+ var removeQuestionLink = 'div#question-controls a[id^='+ removeQuestionLinkIdPrefix +']';
+ return $(removeQuestionLink);
+ };
+
+ var getquestionSubscribeUpdatesCheckbox = function(){
+ return $('#' + questionSubscribeUpdates);
+ };
+
+ var getremoveAnswersLinks = function(){
+ var removeAnswerLinks = 'div.answer-controls a[id^='+ removeAnswerLinkIdPrefix +']';
+ return $(removeAnswerLinks);
+ };
+
+ var setVoteImage = function(voteType, undo, object){
+ var flag = undo ? "" : "-on";
+ var arrow = (voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote) ? "up" : "down";
+ object.attr("src", scriptUrl + "content/images/vote-arrow-"+ arrow + flag +".png");
+
+ // if undo voting, then undo the pair of arrows.
+ if(undo){
+ if(voteType == VoteType.questionUpVote || voteType == VoteType.questionDownVote){
+ $(getQuestionVoteUpButton()).attr("src", scriptUrl + "content/images/vote-arrow-up.png");
+ $(getQuestionVoteDownButton()).attr("src", scriptUrl + "content/images/vote-arrow-down.png");
+ }
+ else{
+ $(getAnswerVoteUpButton(postId)).attr("src", scriptUrl + "content/images/vote-arrow-up.png");
+ $(getAnswerVoteDownButton(postId)).attr("src", scriptUrl + "content/images/vote-arrow-down.png");
+ }
+ }
+ };
+
+ var setVoteNumber = function(object, number){
+ var voteNumber = object.parent('div.'+ voteContainerId).find('div.'+ voteNumberClass);
+ $(voteNumber).text(number);
+ };
+
+ var bindEvents = function(){
+ // accept answers
+ if(questionAuthorId == currentUserId){
+ var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
+ $(acceptedButtons).unbind('click').click(function(event){
+ Vote.accept($(event.target));
+ });
+ }
+ // set favorite question
+ var favoriteButton = getFavoriteButton();
+ favoriteButton.unbind('click').click(function(event){
+ Vote.favorite($(event.target));
+ });
+
+ // question vote up
+ var questionVoteUpButton = getQuestionVoteUpButton();
+ questionVoteUpButton.unbind('click').click(function(event){
+ Vote.vote($(event.target), VoteType.questionUpVote);
+ });
+
+ var questionVoteDownButton = getQuestionVoteDownButton();
+ questionVoteDownButton.unbind('click').click(function(event){
+ Vote.vote($(event.target), VoteType.questionDownVote);
+ });
+
+ var answerVoteUpButton = getAnswerVoteUpButtons();
+ answerVoteUpButton.unbind('click').click(function(event){
+ Vote.vote($(event.target), VoteType.answerUpVote);
+ });
+
+ var answerVoteDownButton = getAnswerVoteDownButtons();
+ answerVoteDownButton.unbind('click').click(function(event){
+ Vote.vote($(event.target), VoteType.answerDownVote);
+ });
+
+ getOffensiveQuestionFlag().unbind('click').click(function(event){
+ Vote.offensive(this, VoteType.offensiveQuestion);
+ });
+
+ getOffensiveAnswerFlags().unbind('click').click(function(event){
+ Vote.offensive(this, VoteType.offensiveAnswer);
+ });
+
+ getremoveQuestionLink().unbind('click').click(function(event){
+ Vote.remove(this, VoteType.removeQuestion);
+ });
+
+ getquestionSubscribeUpdatesCheckbox().unbind('click').click(function(event){
+ if (this.checked){
+ Vote.vote($(event.target), VoteType.questionSubscribeUpdates);
+ }
+ else {
+ Vote.vote($(event.target), VoteType.questionUnsubscribeUpdates);
+ }
+ });
+
+ getremoveAnswersLinks().unbind('click').click(function(event){
+ Vote.remove(this, VoteType.removeAnswer);
+ });
+ };
+
+ var submit = function(object, voteType, callback) {
+ $.ajax({
+ type: "POST",
+ cache: false,
+ dataType: "json",
+ url: scriptUrl + $.i18n._("questions/") + questionId + "/" + $.i18n._("vote/"),
+ data: { "type": voteType, "postId": postId },
+ error: handleFail,
+ success: function(data){callback(object, voteType, data)}});
+ };
+
+ var handleFail = function(xhr, msg){
+ alert("Callback invoke error: " + msg);
+ };
+
+ // callback function for Accept Answer action
+ var callback_accept = function(object, voteType, data){
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, acceptAnonymousMessage);
+ }
+ else if(data.allowed == "-1"){
+ showMessage(object, acceptOwnAnswerMessage);
+ }
+ else if(data.status == "1"){
+ object.attr("src", scriptUrl + "content/images/vote-accepted.png");
+ $("#"+answerContainerIdPrefix+postId).removeClass("accepted-answer");
+ $("#"+commentLinkIdPrefix+postId).removeClass("comment-link-accepted");
+ }
+ else if(data.success == "1"){
+ var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
+ $(acceptedButtons).attr("src", scriptUrl + "content/images/vote-accepted.png");
+ var answers = ("div[id^="+answerContainerIdPrefix +"]");
+ $(answers).removeClass("accepted-answer");
+ var commentLinks = ("div[id^="+answerContainerIdPrefix +"] div[id^="+ commentLinkIdPrefix +"]");
+ $(commentLinks).removeClass("comment-link-accepted");
+
+ object.attr("src", scriptUrl + "content/images/vote-accepted-on.png");
+ $("#"+answerContainerIdPrefix+postId).addClass("accepted-answer");
+ $("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted");
+ }
+ else{
+ showMessage(object, data.message);
+ }
+ };
+
+ var callback_favorite = function(object, voteType, data){
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
+ }
+ else if(data.status == "1"){
+ object.attr("src", scriptUrl + "content/images/vote-favorite-off.png");
+ var fav = getFavoriteNumber();
+ fav.removeClass("my-favorite-number");
+ if(data.count == 0)
+ data.count = '';
+ fav.text(data.count);
+ }
+ else if(data.success == "1"){
+ object.attr("src", scriptUrl + "content/images/vote-favorite-on.png");
+ var fav = getFavoriteNumber();
+ fav.text(data.count);
+ fav.addClass("my-favorite-number");
+ }
+ else{
+ showMessage(object, data.message);
+ }
+ };
+
+ var callback_vote = function(object, voteType, data){
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
+ }
+ else if (data.allowed == "-3"){
+ showMessage(object, voteRequiredMoreVotes);
+ }
+ else if (data.allowed == "-2"){
+ if (voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote){
+ showMessage(object, upVoteRequiredScoreMessage);
+ }
+ else if (voteType == VoteType.questionDownVote || voteType == VoteType.answerDownVote){
+ showMessage(object, downVoteRequiredScoreMessage);
+ }
+ }
+ else if (data.allowed == "-1"){
+ showMessage(object, voteOwnDeniedMessage);
+ }
+ else if (data.status == "2"){
+ showMessage(object, voteDenyCancelMessage);
+ }
+ else if (data.status == "1"){
+ setVoteImage(voteType, true, object);
+ setVoteNumber(object, data.count);
+ }
+ else if (data.success == "1"){
+ setVoteImage(voteType, false, object);
+ setVoteNumber(object, data.count);
+ if (data.message.length > 0){
+ showMessage(object, data.message);
+ }
+ }
+ };
+
+ var callback_offensive = function(object, voteType, data){
+ object = $(object);
+ if (data.allowed == "0" && data.success == "0"){
+ showMessage(object, offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
+ }
+ else if (data.allowed == "-3"){
+ showMessage(object, offensiveNoFlagsLeftMessage);
+ }
+ else if (data.allowed == "-2"){
+ showMessage(object, offensiveNoPermissionMessage);
+ }
+ else if (data.status == "1"){
+ showMessage(object, offensiveTwiceMessage);
+ }
+ else if (data.success == "1"){
+ $(object).children('span[class=darkred]').text("("+ data.count +")");
+ }
+ };
+
+ var callback_remove = function(object, voteType, data){
+ if (data.allowed == "0" && data.success == "0"){
+ showMessage(object, removeAnonymousMessage.replace("{{QuestionID}}", questionId));
+ }
+ else if (data.success == "1"){
+ if (voteType == VoteType.removeQuestion){
+ window.location.href = scriptUrl + $.i18n._("questions/");
+ }
+ else {
+ if (removeActionType == 'delete'){
+ postNode.addClass('deleted');
+ postRemoveLink.innerHTML = $.i18n._('undelete');
+ showMessage(object, deletedMessage);
+ }
+ else if (removeActionType == 'undelete') {
+ postNode.removeClass('deleted');
+ postRemoveLink.innerHTML = $.i18n._('delete');
+ showMessage(object, recoveredMessage);
+ }
+ }
+ }
+ };
+
+ return {
+ init : function(qId, qSlug, questionAuthor, userId){
+ questionId = qId;
+ questionSlug = qSlug;
+ questionAuthorId = questionAuthor;
+ currentUserId = userId;
+ bindEvents();
+ },
+
+ // Accept answer public function
+ accept: function(object){
+ postId = object.attr("id").substring(imgIdPrefixAccept.length);
+ submit(object, VoteType.acceptAnswer, callback_accept);
+ },
+
+ favorite: function(object){
+ if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
+ return false;
+ }
+ submit(object, VoteType.favorite, callback_favorite);
+ },
+
+ vote: function(object, voteType){
+ if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId).replace("{{questionSlug}}", questionSlug));
+ return false;
+ }
+ if (voteType == VoteType.answerUpVote){
+ postId = object.attr("id").substring(imgIdPrefixAnswerVoteup.length);
+ }
+ else if (voteType == VoteType.answerDownVote){
+ postId = object.attr("id").substring(imgIdPrefixAnswerVotedown.length);
+ }
+
+ submit(object, voteType, callback_vote);
+ },
+
+ offensive: function(object, voteType){
+ if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage($(object), offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
+ return false;
+ }
+ if (confirm(offensiveConfirmation)){
+ postId = object.id.substr(object.id.lastIndexOf('-') + 1);
+ submit(object, voteType, callback_offensive);
+ }
+ },
+
+ remove: function(object, voteType){
+ if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage($(object), removeAnonymousMessage.replace("{{QuestionID}}", questionId));
+ return false;
+ }
+ bits = object.id.split('-');
+ postId = bits.pop();/* this seems to be used within submit! */
+ postType = bits.shift();
+
+ var do_proceed = false;
+ if (postType == 'answer'){
+ postNode = $('#answer-container-' + postId);
+ postRemoveLink = object;
+ if (postNode.hasClass('deleted')){
+ removeActionType = 'undelete';
+ do_proceed = true;
+ }
+ else {
+ removeActionType = 'delete';
+ do_proceed = confirm(removeConfirmation);
+ }
+ }
+ else {
+ do_proceed = confirm(removeConfirmation);
+ }
+ if (do_proceed) {
+ submit($(object), voteType, callback_remove);
+ }
+ }
+ }
+} ();
+
+
+// site comments
+function createComments(type) {
+ var objectType = type;
+ var jDivInit = function(id) {
+ return $("#comments-container-" + objectType + '-' + id);
+ };
+
+ var appendLoaderImg = function(id) {
+ appendLoader("#comments-container-" + objectType + '-' + id);
+ };
+
+ var canPostComments = function(id) {
+ var jHidden = $("#can-post-comments-" + objectType + '-' + id);
+ return jHidden.val().toLowerCase() == "true";
+ };
+
+ var renderForm = function(id) {
+ var formId = "form-comments-" + objectType + "-" + id;
+ var jDiv = $('#comments-link-' + objectType + "-" + id).parent();
+ $(jDiv).css('background','none');
+ $(jDiv).css('padding-left',0);
+ if (canPostComments(id)) {
+ if (jDiv.find("#" + formId).length == 0) {
+ var form = '<form id="' + formId + '" class="post-comments"><div>';
+ form += '<textarea name="comment" cols="60" rows="5" maxlength="300" onblur="'+ objectType +'Comments.updateTextCounter(this)" ';
+ form += 'onfocus="' + objectType + 'Comments.updateTextCounter(this)" onkeyup="'+ objectType +'Comments.updateTextCounter(this)"></textarea>';
+ form += '<input type="submit" value="'
+ + $.i18n._('add comment') + '" /><br><span class="text-counter"></span>';
+ form += '<span class="form-error"></span></div></form>';
+
+ jDiv.append(form);
+
+ setupFormValidation("#" + formId,
+ { comment: { required: true, minlength: 10} }, '',
+ function() { postComment(id, formId); });
+ }
+ }
+ else {
+ var divId = "comments-rep-needed-" + objectType + '-' + id;
+ if (jDiv.find("#" + divId).length == 0) {
+ jDiv.append('<p id="' + divId + '" class="comment">'
+ + $.i18n._('to comment, need') + ' ' +
+ + repNeededForComments + ' ' + $.i18n._('community karma points')
+ + '<a href="' + scriptUrl + $.i18n._('faq/') + '" class="comment-user">'
+ + $.i18n._('please see') + 'faq</a></span></p>');
+ }
+ }
+ };
+
+ var getComments = function(id, jDiv) {
+ //appendLoaderImg(id);
+ $.getJSON(scriptUrl + objectType + "s/" + id + "/" + $.i18n._("comments/")
+ , function(json) { showComments(id, json); });
+ };
+
+ var showComments = function(id, json) {
+ var jDiv = jDivInit(id);
+
+ //jDiv = jDiv.find("div.comments"); // this div should contain any fetched comments..
+ //jDiv.find("div[id^='comment-" + objectType + "-'" + "]").remove(); // clean previous calls..
+ jDiv.children().remove();
+ removeLoader();
+ if (json && json.length > 0) {
+ for (var i = 0; i < json.length; i++)
+ renderComment(jDiv, json[i]);
+ jDiv.children().show();
+ }
+ };
+
+ var renderDeleteCommentIcon = function(post_id, delete_url){
+ if (canPostComments(post_id)){
+ var html = '';
+ var img = scriptUrl + "content/images/close-small.png";
+ var imgHover = scriptUrl + "content/images/close-small-hover.png";
+ html += '<img class="delete-icon" onclick="' + objectType + 'Comments.deleteComment($(this), ' + post_id + ', \'' + delete_url + '\')" src="' + img;
+ html += '" onmouseover="$(this).attr(\'src\', \'' + imgHover + '\')" onmouseout="$(this).attr(\'src\', \'' + img
+ html += '\')" title="' + $.i18n._('delete this comment') + '" />';
+ return html;
+ }
+ else{
+ return '';
+ }
+ }
+
+ // {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
+ var renderComment = function(jDiv, json) {
+ var html = '<p id="comment-' + json.id + '" class="comment" style="display:none">' + json.text;
+ html += json.user_url ? ' - <a href="' + json.user_url + '"' : '<span';
+ html += ' class="comment-user">' + json.user_display_name + (json.user_url ? '</a>' : '</span>');
+ html += ' (' + json.comment_age + ')';
+
+ if (json.delete_url){
+ html += renderDeleteCommentIcon(json.object_id, json.delete_url);
+ }
+
+ if (json.delete_url) {
+ }
+
+ html += '</p>';
+
+ jDiv.append(html);
+ };
+
+ var postComment = function(id, formId) {
+ //appendLoaderImg(id);
+
+ var formSelector = "#" + formId;
+ var textarea = $(formSelector + " textarea");
+
+ //todo fix url translations!!!
+ $.ajax({
+ type: "POST",
+ url: scriptUrl + objectType + "s/" + id + "/" + $.i18n._("comments/"),
+ dataType: "json",
+ data: { comment: textarea.val() },
+ success: function(json) {
+ showComments(id, json);
+ textarea.val("");
+ commentsFactory[objectType].updateTextCounter(textarea);
+ enableSubmitButton(formSelector);
+ },
+ error: function(res, textStatus, errorThrown) {
+ removeLoader();
+ showMessage(formSelector, res.responseText);
+ enableSubmitButton(formSelector);
+ }
+ });
+ };
+
+ // public methods..
+ return {
+
+ init: function() {
+ // Setup "show comments" clicks..
+ $("a[id^='comments-link-" + objectType + "-" + "']").unbind("click").click(function() {
+ commentsFactory[objectType].show($(this).attr("id").substr(("comments-link-" + objectType + "-").length));
+ });
+
+ var cBox = $("[id^='comments-container-" + objectType + "']");
+ cBox.each( function(i){
+ var post_id = $(this).attr('id').replace('comments-container-' + objectType + '-', '');
+ $(this).children().each(
+ function(i){
+ var comment_id = $(this).attr('id').replace('comment-','');
+ var delete_url = scriptUrl + objectType + 's/' + post_id + '/'
+ + $.i18n._('comments/') + comment_id + '/' + $.i18n._('delete/');
+ var html = $(this).html();
+ var CommentsClass;
+ if (objectType == 'question'){
+ CommentsClass = questionComments;
+ }
+ else if (objectType == 'answer') {
+ CommentsClass = answerComments;
+ }
+ var delete_icon = $(this).find('img.delete-icon');
+ delete_icon.click(function(){CommentsClass.deleteComment($(this),comment_id,delete_url);});
+ delete_icon.unbind('mouseover').bind('mouseover',
+ function(){
+ $(this).attr('src',scriptUrl + 'content/images/close-small-hover.png');
+ }
+ );
+ delete_icon.unbind('mouseout').bind('mouseout',
+ function(){
+ $(this).attr('src',scriptUrl + 'content/images/close-small.png');
+ }
+ );
+ }
+ );
+ });
+ },
+
+ show: function(id) {
+ var jDiv = jDivInit(id);
+ getComments(id, jDiv);
+ renderForm(id);
+ jDiv.show();
+
+ var link = $('#comments-link-' + objectType + '-' + id);
+ if (canPostComments(id)) link.parent().find("textarea").get(0).focus();
+ link.remove();
+ },
+
+ hide: function(id) {
+ var jDiv = jDivInit(id);
+ var len = jDiv.children("div.comments").children().length;
+ var anchorText = len == 0 ? $.i18n._('add a comment') : $.i18n._('comments') + ' (<b>' + len + "</b>)";
+
+ jDiv.hide();
+ jDiv.siblings("a").unbind("click").click(function() { commentsFactory[objectType].show(id); }).html(anchorText);
+ jDiv.children("div.comments").children().hide();
+ },
+
+ deleteComment: function(jImg, id, deleteUrl) {
+ if (confirm($.i18n._('confirm delete comment'))) {
+ jImg.hide();
+ $.post(deleteUrl, { dataNeeded: "forIIS7" }, function(json) {
+ var par = jImg.parent();
+ par.remove();
+ }, "json");
+ }
+ },
+
+ updateTextCounter: function(textarea) {
+ var length = textarea.value ? textarea.value.length : 0;
+ var color = length > 270 ? "#f00" : length > 200 ? "#f60" : "#999";
+ var jSpan = $(textarea).siblings("span.text-counter");
+ jSpan.html($.i18n._('can write')
+ + (300 - length) + ' '
+ + $.i18n._('characters')).css("color", color);
+ }
+ };
+}
+
+var questionComments = createComments('question');
+var answerComments = createComments('answer');
+
+$().ready(function() {
+ questionComments.init();
+ answerComments.init();
+});
+
+var commentsFactory = {'question' : questionComments, 'answer' : answerComments};
+
+/*
+Prettify
+http://www.apache.org/licenses/LICENSE-2.0
+*/
+var PR_SHOULD_USE_CONTINUATION = true; var PR_TAB_WIDTH = 8; var PR_normalizedHtml; var PR; var prettyPrintOne; var prettyPrint; function _pr_isIE6() { var isIE6 = navigator && navigator.userAgent && /\bMSIE 6\./.test(navigator.userAgent); _pr_isIE6 = function() { return isIE6; }; return isIE6; } (function() { function wordSet(words) { words = words.split(/ /g); var set = {}; for (var i = words.length; --i >= 0; ) { var w = words[i]; if (w) { set[w] = null; } } return set; } var FLOW_CONTROL_KEYWORDS = "break continue do else for if return while "; var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " + "double enum extern float goto int long register short signed sizeof " + "static struct switch typedef union unsigned void volatile "; var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " + "new operator private protected public this throw true try "; var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " + "concept concept_map const_cast constexpr decltype " + "dynamic_cast explicit export friend inline late_check " + "mutable namespace nullptr reinterpret_cast static_assert static_cast " + "template typeid typename typeof using virtual wchar_t where "; var JAVA_KEYWORDS = COMMON_KEYWORDS + "boolean byte extends final finally implements import instanceof null " + "native package strictfp super synchronized throws transient "; var CSHARP_KEYWORDS = JAVA_KEYWORDS + "as base by checked decimal delegate descending event " + "fixed foreach from group implicit in interface internal into is lock " + "object out override orderby params readonly ref sbyte sealed " + "stackalloc string select uint ulong unchecked unsafe ushort var "; var JSCRIPT_KEYWORDS = COMMON_KEYWORDS + "debugger eval export function get null set undefined var with " + "Infinity NaN "; var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " + "goto if import last local my next no our print package redo require " + "sub undef unless until use wantarray while BEGIN END "; var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " + "elif except exec finally from global import in is lambda " + "nonlocal not or pass print raise try with yield " + "False True None "; var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" + " defined elsif end ensure false in module next nil not or redo rescue " + "retry self super then true undef unless until when yield BEGIN END "; var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " + "function in local set then until "; var ALL_KEYWORDS = (CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS + PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS); var PR_STRING = 'str'; var PR_KEYWORD = 'kwd'; var PR_COMMENT = 'com'; var PR_TYPE = 'typ'; var PR_LITERAL = 'lit'; var PR_PUNCTUATION = 'pun'; var PR_PLAIN = 'pln'; var PR_TAG = 'tag'; var PR_DECLARATION = 'dec'; var PR_SOURCE = 'src'; var PR_ATTRIB_NAME = 'atn'; var PR_ATTRIB_VALUE = 'atv'; var PR_NOCODE = 'nocode'; function isWordChar(ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); } function spliceArrayInto(inserted, container, containerPosition, countReplaced) { inserted.unshift(containerPosition, countReplaced || 0); try { container.splice.apply(container, inserted); } finally { inserted.splice(0, 2); } } var REGEXP_PRECEDER_PATTERN = function() { var preceders = ["!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=", "&=", "(", "*", "*=", "+=", ",", "-=", "->", "/", "/=", ":", "::", ";", "<", "<<", "<<=", "<=", "=", "==", "===", ">", ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[", "^", "^=", "^^", "^^=", "{", "|", "|=", "||", "||=", "~", "break", "case", "continue", "delete", "do", "else", "finally", "instanceof", "return", "throw", "try", "typeof"]; var pattern = '(?:' + '(?:(?:^|[^0-9.])\\.{1,3})|' + '(?:(?:^|[^\\+])\\+)|' + '(?:(?:^|[^\\-])-)'; for (var i = 0; i < preceders.length; ++i) { var preceder = preceders[i]; if (isWordChar(preceder.charAt(0))) { pattern += '|\\b' + preceder; } else { pattern += '|' + preceder.replace(/([^=<>:&])/g, '\\$1'); } } pattern += '|^)\\s*$'; return new RegExp(pattern); } (); var pr_amp = /&/g; var pr_lt = /</g; var pr_gt = />/g; var pr_quot = /\"/g; function attribToHtml(str) { return str.replace(pr_amp, '&amp;').replace(pr_lt, '&lt;').replace(pr_gt, '&gt;').replace(pr_quot, '&quot;'); } function textToHtml(str) { return str.replace(pr_amp, '&amp;').replace(pr_lt, '&lt;').replace(pr_gt, '&gt;'); } var pr_ltEnt = /&lt;/g; var pr_gtEnt = /&gt;/g; var pr_aposEnt = /&apos;/g; var pr_quotEnt = /&quot;/g; var pr_ampEnt = /&amp;/g; var pr_nbspEnt = /&nbsp;/g; function htmlToText(html) { var pos = html.indexOf('&'); if (pos < 0) { return html; } for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0; ) { var end = html.indexOf(';', pos); if (end >= 0) { var num = html.substring(pos + 3, end); var radix = 10; if (num && num.charAt(0) === 'x') { num = num.substring(1); radix = 16; } var codePoint = parseInt(num, radix); if (!isNaN(codePoint)) { html = (html.substring(0, pos) + String.fromCharCode(codePoint) + html.substring(end + 1)); } } } return html.replace(pr_ltEnt, '<').replace(pr_gtEnt, '>').replace(pr_aposEnt, "'").replace(pr_quotEnt, '"').replace(pr_ampEnt, '&').replace(pr_nbspEnt, ' '); } function isRawContent(node) { return 'XMP' === node.tagName; } function normalizedHtml(node, out) { switch (node.nodeType) { case 1: var name = node.tagName.toLowerCase(); out.push('<', name); for (var i = 0; i < node.attributes.length; ++i) { var attr = node.attributes[i]; if (!attr.specified) { continue; } out.push(' '); normalizedHtml(attr, out); } out.push('>'); for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } if (node.firstChild || !/^(?:br|link|img)$/.test(name)) { out.push('<\/', name, '>'); } break; case 2: out.push(node.name.toLowerCase(), '="', attribToHtml(node.value), '"'); break; case 3: case 4: out.push(textToHtml(node.nodeValue)); break; } } var PR_innerHtmlWorks = null; function getInnerHtml(node) { if (null === PR_innerHtmlWorks) { var testNode = document.createElement('PRE'); testNode.appendChild(document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />')); PR_innerHtmlWorks = !/</.test(testNode.innerHTML); } if (PR_innerHtmlWorks) { var content = node.innerHTML; if (isRawContent(node)) { content = textToHtml(content); } return content; } var out = []; for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } return out.join(''); } function makeTabExpander(tabWidth) { var SPACES = ' '; var charInLine = 0; return function(plainText) { var out = null; var pos = 0; for (var i = 0, n = plainText.length; i < n; ++i) { var ch = plainText.charAt(i); switch (ch) { case '\t': if (!out) { out = []; } out.push(plainText.substring(pos, i)); var nSpaces = tabWidth - (charInLine % tabWidth); charInLine += nSpaces; for (; nSpaces >= 0; nSpaces -= SPACES.length) { out.push(SPACES.substring(0, nSpaces)); } pos = i + 1; break; case '\n': charInLine = 0; break; default: ++charInLine; } } if (!out) { return plainText; } out.push(plainText.substring(pos)); return out.join(''); }; } var pr_chunkPattern = /(?:[^<]+|<!--[\s\S]*?-->|<!\[CDATA\[([\s\S]*?)\]\]>|<\/?[a-zA-Z][^>]*>|<)/g; var pr_commentPrefix = /^<!--/; var pr_cdataPrefix = /^<\[CDATA\[/; var pr_brPrefix = /^<br\b/i; var pr_tagNameRe = /^<(\/?)([a-zA-Z]+)/; function extractTags(s) { var matches = s.match(pr_chunkPattern); var sourceBuf = []; var sourceBufLen = 0; var extractedTags = []; if (matches) { for (var i = 0, n = matches.length; i < n; ++i) { var match = matches[i]; if (match.length > 1 && match.charAt(0) === '<') { if (pr_commentPrefix.test(match)) { continue; } if (pr_cdataPrefix.test(match)) { sourceBuf.push(match.substring(9, match.length - 3)); sourceBufLen += match.length - 12; } else if (pr_brPrefix.test(match)) { sourceBuf.push('\n'); ++sourceBufLen; } else { if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) { var name = match.match(pr_tagNameRe)[2]; var depth = 1; end_tag_loop: for (var j = i + 1; j < n; ++j) { var name2 = matches[j].match(pr_tagNameRe); if (name2 && name2[2] === name) { if (name2[1] === '/') { if (--depth === 0) { break end_tag_loop; } } else { ++depth; } } } if (j < n) { extractedTags.push(sourceBufLen, matches.slice(i, j + 1).join('')); i = j; } else { extractedTags.push(sourceBufLen, match); } } else { extractedTags.push(sourceBufLen, match); } } } else { var literalText = htmlToText(match); sourceBuf.push(literalText); sourceBufLen += literalText.length; } } } return { source: sourceBuf.join(''), tags: extractedTags }; } function isNoCodeTag(tag) { return !!tag.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g, ' $1="$2$3$4"').match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/); } function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) { var shortcuts = {}; (function() { var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns); for (var i = allPatterns.length; --i >= 0; ) { var patternParts = allPatterns[i]; var shortcutChars = patternParts[3]; if (shortcutChars) { for (var c = shortcutChars.length; --c >= 0; ) { shortcuts[shortcutChars.charAt(c)] = patternParts; } } } })(); var nPatterns = fallthroughStylePatterns.length; var notWs = /\S/; return function(sourceCode, opt_basePos) { opt_basePos = opt_basePos || 0; var decorations = [opt_basePos, PR_PLAIN]; var lastToken = ''; var pos = 0; var tail = sourceCode; while (tail.length) { var style; var token = null; var match; var patternParts = shortcuts[tail.charAt(0)]; if (patternParts) { match = tail.match(patternParts[1]); token = match[0]; style = patternParts[0]; } else { for (var i = 0; i < nPatterns; ++i) { patternParts = fallthroughStylePatterns[i]; var contextPattern = patternParts[2]; if (contextPattern && !contextPattern.test(lastToken)) { continue; } match = tail.match(patternParts[1]); if (match) { token = match[0]; style = patternParts[0]; break; } } if (!token) { style = PR_PLAIN; token = tail.substring(0, 1); } } decorations.push(opt_basePos + pos, style); pos += token.length; tail = tail.substring(token.length); if (style !== PR_COMMENT && notWs.test(token)) { lastToken = token; } } return decorations; }; } var PR_MARKUP_LEXER = createSimpleLexer([], [[PR_PLAIN, /^[^<]+/, null], [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/, null], [PR_COMMENT, /^<!--[\s\S]*?(?:-->|$)/, null], [PR_SOURCE, /^<\?[\s\S]*?(?:\?>|$)/, null], [PR_SOURCE, /^<%[\s\S]*?(?:%>|$)/, null], [PR_SOURCE, /^<(script|style|xmp)\b[^>]*>[\s\S]*?<\/\1\b[^>]*>/i, null], [PR_TAG, /^<\/?\w[^<>]*>/, null]]); var PR_SOURCE_CHUNK_PARTS = /^(<[^>]*>)([\s\S]*)(<\/[^>]*>)$/; function tokenizeMarkup(source) { var decorations = PR_MARKUP_LEXER(source); for (var i = 0; i < decorations.length; i += 2) { if (decorations[i + 1] === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var sourceChunk = source.substring(start, end); var match = sourceChunk.match(PR_SOURCE_CHUNK_PARTS); if (match) { decorations.splice(i, 2, start, PR_TAG, start + match[1].length, PR_SOURCE, start + match[1].length + (match[2] || '').length, PR_TAG); } } } return decorations; } var PR_TAG_LEXER = createSimpleLexer([[PR_ATTRIB_VALUE, /^\'[^\']*(?:\'|$)/, null, "'"], [PR_ATTRIB_VALUE, /^\"[^\"]*(?:\"|$)/, null, '"'], [PR_PUNCTUATION, /^[<>\/=]+/, null, '<>/=']], [[PR_TAG, /^[\w:\-]+/, /^</], [PR_ATTRIB_VALUE, /^[\w\-]+/, /^=/], [PR_ATTRIB_NAME, /^[\w:\-]+/, null], [PR_PLAIN, /^\s+/, null, ' \t\r\n']]); function splitTagAttributes(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_TAG) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var chunk = source.substring(start, end); var subDecorations = PR_TAG_LEXER(chunk, start); spliceArrayInto(subDecorations, decorations, i, 2); i += subDecorations.length - 2; } } return decorations; } function sourceDecorator(options) { var shortcutStylePatterns = [], fallthroughStylePatterns = []; if (options.tripleQuotedStrings) { shortcutStylePatterns.push([PR_STRING, /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/, null, '\'"']); } else if (options.multiLineStrings) { shortcutStylePatterns.push([PR_STRING, /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/, null, '\'"`']); } else { shortcutStylePatterns.push([PR_STRING, /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/, null, '"\'']); } fallthroughStylePatterns.push([PR_PLAIN, /^(?:[^\'\"\`\/\#]+)/, null, ' \r\n']); if (options.hashComments) { shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']); } if (options.cStyleComments) { fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]); fallthroughStylePatterns.push([PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]); } if (options.regexLiterals) { var REGEX_LITERAL = ('^/(?=[^/*])' + '(?:[^/\\x5B\\x5C]' + '|\\x5C[\\s\\S]' + '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+' + '(?:/|$)'); fallthroughStylePatterns.push([PR_STRING, new RegExp(REGEX_LITERAL), REGEXP_PRECEDER_PATTERN]); } var keywords = wordSet(options.keywords); options = null; var splitStringAndCommentTokens = createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns); var styleLiteralIdentifierPuncRecognizer = createSimpleLexer([], [[PR_PLAIN, /^\s+/, null, ' \r\n'], [PR_PLAIN, /^[a-z_$@][a-z_$@0-9]*/i, null], [PR_LITERAL, /^0x[a-f0-9]+[a-z]/i, null], [PR_LITERAL, /^(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d+)(?:e[+\-]?\d+)?[a-z]*/i, null, '123456789'], [PR_PUNCTUATION, /^[^\s\w\.$@]+/, null]]); function splitNonStringNonCommentTokens(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_PLAIN) { var start, end, chunk, subDecs; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; chunk = source.substring(start, end); subDecs = styleLiteralIdentifierPuncRecognizer(chunk, start); for (var j = 0, m = subDecs.length; j < m; j += 2) { var subStyle = subDecs[j + 1]; if (subStyle === PR_PLAIN) { var subStart = subDecs[j]; var subEnd = j + 2 < m ? subDecs[j + 2] : chunk.length; var token = source.substring(subStart, subEnd); if (token === '.') { subDecs[j + 1] = PR_PUNCTUATION; } else if (token in keywords) { subDecs[j + 1] = PR_KEYWORD; } else if (/^@?[A-Z][A-Z$]*[a-z][A-Za-z$]*$/.test(token)) { subDecs[j + 1] = token.charAt(0) === '@' ? PR_LITERAL : PR_TYPE; } } } spliceArrayInto(subDecs, decorations, i, 2); i += subDecs.length - 2; } } return decorations; } return function(sourceCode) { var decorations = splitStringAndCommentTokens(sourceCode); decorations = splitNonStringNonCommentTokens(sourceCode, decorations); return decorations; }; } var decorateSource = sourceDecorator({ keywords: ALL_KEYWORDS, hashComments: true, cStyleComments: true, multiLineStrings: true, regexLiterals: true }); function splitSourceNodes(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var subDecorations = decorateSource(source.substring(start, end)); for (var j = 0, m = subDecorations.length; j < m; j += 2) { subDecorations[j] += start; } spliceArrayInto(subDecorations, decorations, i, 2); i += subDecorations.length - 2; } } return decorations; } function splitSourceAttributes(source, decorations) { var nextValueIsSource = false; for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; var start, end; if (style === PR_ATTRIB_NAME) { start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; nextValueIsSource = /^on|^style$/i.test(source.substring(start, end)); } else if (style === PR_ATTRIB_VALUE) { if (nextValueIsSource) { start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var attribValue = source.substring(start, end); var attribLen = attribValue.length; var quoted = (attribLen >= 2 && /^[\"\']/.test(attribValue) && attribValue.charAt(0) === attribValue.charAt(attribLen - 1)); var attribSource; var attribSourceStart; var attribSourceEnd; if (quoted) { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue; } else { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue.substring(1, attribValue.length - 1); } var attribSourceDecorations = decorateSource(attribSource); for (var j = 0, m = attribSourceDecorations.length; j < m; j += 2) { attribSourceDecorations[j] += attribSourceStart; } if (quoted) { attribSourceDecorations.push(attribSourceEnd, PR_ATTRIB_VALUE); spliceArrayInto(attribSourceDecorations, decorations, i + 2, 0); } else { spliceArrayInto(attribSourceDecorations, decorations, i, 2); } } nextValueIsSource = false; } } return decorations; } function decorateMarkup(sourceCode) { var decorations = tokenizeMarkup(sourceCode); decorations = splitTagAttributes(sourceCode, decorations); decorations = splitSourceNodes(sourceCode, decorations); decorations = splitSourceAttributes(sourceCode, decorations); return decorations; } function recombineTagsAndDecorations(sourceText, extractedTags, decorations) { var html = []; var outputIdx = 0; var openDecoration = null; var currentDecoration = null; var tagPos = 0; var decPos = 0; var tabExpander = makeTabExpander(PR_TAB_WIDTH); var adjacentSpaceRe = /([\r\n ]) /g; var startOrSpaceRe = /(^| ) /gm; var newlineRe = /\r\n?|\n/g; var trailingSpaceRe = /[ \r\n]$/; var lastWasSpace = true; function emitTextUpTo(sourceIdx) { if (sourceIdx > outputIdx) { if (openDecoration && openDecoration !== currentDecoration) { html.push('</span>'); openDecoration = null; } if (!openDecoration && currentDecoration) { openDecoration = currentDecoration; html.push('<span class="', openDecoration, '">'); } var htmlChunk = textToHtml(tabExpander(sourceText.substring(outputIdx, sourceIdx))).replace(lastWasSpace ? startOrSpaceRe : adjacentSpaceRe, '$1&nbsp;'); lastWasSpace = trailingSpaceRe.test(htmlChunk); html.push(htmlChunk.replace(newlineRe, '<br />')); outputIdx = sourceIdx; } } while (true) { var outputTag; if (tagPos < extractedTags.length) { if (decPos < decorations.length) { outputTag = extractedTags[tagPos] <= decorations[decPos]; } else { outputTag = true; } } else { outputTag = false; } if (outputTag) { emitTextUpTo(extractedTags[tagPos]); if (openDecoration) { html.push('</span>'); openDecoration = null; } html.push(extractedTags[tagPos + 1]); tagPos += 2; } else if (decPos < decorations.length) { emitTextUpTo(decorations[decPos]); currentDecoration = decorations[decPos + 1]; decPos += 2; } else { break; } } emitTextUpTo(sourceText.length); if (openDecoration) { html.push('</span>'); } return html.join(''); } var langHandlerRegistry = {}; function registerLangHandler(handler, fileExtensions) { for (var i = fileExtensions.length; --i >= 0; ) { var ext = fileExtensions[i]; if (!langHandlerRegistry.hasOwnProperty(ext)) { langHandlerRegistry[ext] = handler; } else if ('console' in window) { console.log('cannot override language handler %s', ext); } } } registerLangHandler(decorateSource, ['default-code']); registerLangHandler(decorateMarkup, ['default-markup', 'html', 'htm', 'xhtml', 'xml', 'xsl']); registerLangHandler(sourceDecorator({ keywords: CPP_KEYWORDS, hashComments: true, cStyleComments: true }), ['c', 'cc', 'cpp', 'cs', 'cxx', 'cyc']); registerLangHandler(sourceDecorator({ keywords: JAVA_KEYWORDS, cStyleComments: true }), ['java']); registerLangHandler(sourceDecorator({ keywords: SH_KEYWORDS, hashComments: true, multiLineStrings: true }), ['bsh', 'csh', 'sh']); registerLangHandler(sourceDecorator({ keywords: PYTHON_KEYWORDS, hashComments: true, multiLineStrings: true, tripleQuotedStrings: true }), ['cv', 'py']); registerLangHandler(sourceDecorator({ keywords: PERL_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['perl', 'pl', 'pm']); registerLangHandler(sourceDecorator({ keywords: RUBY_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['rb']); registerLangHandler(sourceDecorator({ keywords: JSCRIPT_KEYWORDS, cStyleComments: true, regexLiterals: true }), ['js']); function prettyPrintOne(sourceCodeHtml, opt_langExtension) { try { var sourceAndExtractedTags = extractTags(sourceCodeHtml); var source = sourceAndExtractedTags.source; var extractedTags = sourceAndExtractedTags.tags; if (!langHandlerRegistry.hasOwnProperty(opt_langExtension)) { opt_langExtension = /^\s*</.test(source) ? 'default-markup' : 'default-code'; } var decorations = langHandlerRegistry[opt_langExtension].call({}, source); return recombineTagsAndDecorations(source, extractedTags, decorations); } catch (e) { if ('console' in window) { console.log(e); console.trace(); } return sourceCodeHtml; } } function prettyPrint(opt_whenDone) { var isIE6 = _pr_isIE6(); var codeSegments = [document.getElementsByTagName('pre'), document.getElementsByTagName('code'), document.getElementsByTagName('xmp')]; var elements = []; for (var i = 0; i < codeSegments.length; ++i) { for (var j = 0; j < codeSegments[i].length; ++j) { elements.push(codeSegments[i][j]); } } codeSegments = null; var k = 0; function doWork() { var endTime = (PR_SHOULD_USE_CONTINUATION ? new Date().getTime() + 250 : Infinity); for (; k < elements.length && new Date().getTime() < endTime; k++) { var cs = elements[k]; if (cs.className && cs.className.indexOf('prettyprint') >= 0) { var langExtension = cs.className.match(/\blang-(\w+)\b/); if (langExtension) { langExtension = langExtension[1]; } var nested = false; for (var p = cs.parentNode; p; p = p.parentNode) { if ((p.tagName === 'pre' || p.tagName === 'code' || p.tagName === 'xmp') && p.className && p.className.indexOf('prettyprint') >= 0) { nested = true; break; } } if (!nested) { var content = getInnerHtml(cs); content = content.replace(/(?:\r\n?|\n)$/, ''); var newContent = prettyPrintOne(content, langExtension); if (!isRawContent(cs)) { cs.innerHTML = newContent; } else { var pre = document.createElement('PRE'); for (var i = 0; i < cs.attributes.length; ++i) { var a = cs.attributes[i]; if (a.specified) { var aname = a.name.toLowerCase(); if (aname === 'class') { pre.className = a.value; } else { pre.setAttribute(a.name, a.value); } } } pre.innerHTML = newContent; cs.parentNode.replaceChild(pre, cs); cs = pre; } if (isIE6 && cs.tagName === 'PRE') { var lineBreaks = cs.getElementsByTagName('br'); for (var j = lineBreaks.length; --j >= 0; ) { var lineBreak = lineBreaks[j]; lineBreak.parentNode.replaceChild(document.createTextNode('\r\n'), lineBreak); } } } } } if (k < elements.length) { setTimeout(doWork, 250); } else if (opt_whenDone) { opt_whenDone(); } } doWork(); } window['PR_normalizedHtml'] = normalizedHtml; window['prettyPrintOne'] = prettyPrintOne; window['prettyPrint'] = prettyPrint; window['PR'] = { 'createSimpleLexer': createSimpleLexer, 'registerLangHandler': registerLangHandler, 'sourceDecorator': sourceDecorator, 'PR_ATTRIB_NAME': PR_ATTRIB_NAME, 'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE, 'PR_COMMENT': PR_COMMENT, 'PR_DECLARATION': PR_DECLARATION, 'PR_KEYWORD': PR_KEYWORD, 'PR_LITERAL': PR_LITERAL, 'PR_NOCODE': PR_NOCODE, 'PR_PLAIN': PR_PLAIN, 'PR_PUNCTUATION': PR_PUNCTUATION, 'PR_SOURCE': PR_SOURCE, 'PR_STRING': PR_STRING, 'PR_TAG': PR_TAG, 'PR_TYPE': PR_TYPE }; })();