From 4347c2947834fe7f2edf2b457b2d513454fc6a03 Mon Sep 17 00:00:00 2001
From: Mike Chen
Date: Sun, 5 Jul 2009 10:23:30 +0800
Subject: initiliaze git rep
---
.gitignore | 2 +
INSTALL | 33 +
LICENSE | 12 +
__init__.py | 0
development.log | 0
django_authopenid/__init__.py | 40 +
django_authopenid/admin.py | 9 +
django_authopenid/forms.py | 435 +++
django_authopenid/middleware.py | 24 +
django_authopenid/mimeparse.py | 160 +
django_authopenid/models.py | 71 +
django_authopenid/urls.py | 27 +
django_authopenid/util.py | 145 +
django_authopenid/views.py | 787 +++++
forum/__init__.py | 0
forum/admin.py | 71 +
forum/auth.py | 443 +++
forum/const.py | 89 +
forum/diff.py | 66 +
forum/feed.py | 41 +
forum/forms.py | 194 ++
forum/management/__init__.py | 0
forum/management/commands/__init__.py | 0
forum/management/commands/base_command.py | 35 +
forum/management/commands/clean_award_badges.py | 58 +
forum/management/commands/multi_award_badges.py | 347 ++
forum/management/commands/once_award_badges.py | 350 ++
forum/management/commands/sample_command.py | 7 +
forum/managers.py | 259 ++
forum/models.py | 653 ++++
forum/templatetags/__init__.py | 0
forum/templatetags/extra_filters.py | 83 +
forum/templatetags/extra_tags.py | 232 ++
forum/user.py | 74 +
forum/views.py | 1945 +++++++++++
locale/zh_cn/LC_MESSAGES/django.mo | Bin 0 -> 6605 bytes
locale/zh_cn/LC_MESSAGES/django.po | 416 +++
manage.py | 11 +
middleware/__init__.py | 0
middleware/pagesize.py | 29 +
settings.py | 125 +
settings_local.py.dist | 21 +
sql_scripts/cnprog.xml | 1498 +++++++++
sql_scripts/cnprog_new_install.sql | 811 +++++
sql_scripts/cnprog_new_install_2009_02_28.sql | 456 +++
sql_scripts/cnprog_new_install_2009_03_31.sql | 891 +++++
sql_scripts/cnprog_new_install_2009_04_07.sql | 24 +
sql_scripts/cnprog_new_install_2009_04_09.sql | 904 +++++
sql_scripts/update_2009_01_13_001.sql | 62 +
sql_scripts/update_2009_01_13_002.sql | 1 +
sql_scripts/update_2009_01_18_001.sql | 62 +
sql_scripts/update_2009_01_24.sql | 2 +
sql_scripts/update_2009_01_25_001.sql | 2 +
sql_scripts/update_2009_02_26_001.sql | 19 +
sql_scripts/update_2009_04_10_001.sql | 3 +
sql_scripts/update_2009_12_24_001.sql | 5 +
sql_scripts/update_2009_12_27_001.sql | 3 +
sql_scripts/update_2009_12_27_002.sql | 1 +
templates/404.html | 48 +
templates/500.html | 33 +
templates/about.html | 71 +
templates/answer_edit.html | 139 +
templates/ask.html | 191 ++
templates/authopenid/changeemail.html | 39 +
templates/authopenid/changeopenid.html | 33 +
templates/authopenid/changepw.html | 33 +
templates/authopenid/complete.html | 66 +
templates/authopenid/confirm_email.txt | 12 +
templates/authopenid/delete.html | 38 +
templates/authopenid/failure.html | 13 +
templates/authopenid/sendpw.html | 33 +
templates/authopenid/sendpw_email.txt | 14 +
templates/authopenid/settings.html | 41 +
templates/authopenid/signin.html | 96 +
templates/authopenid/signup.html | 51 +
templates/authopenid/yadis.xrdf | 14 +
templates/badge.html | 38 +
templates/badges.html | 72 +
templates/base.html | 82 +
templates/base_content.html | 75 +
templates/book.html | 150 +
templates/close.html | 36 +
templates/content/images/box-arrow.gif | Bin 0 -> 69 bytes
templates/content/images/bullet_green.gif | Bin 0 -> 64 bytes
templates/content/images/cc-88x31.png | Bin 0 -> 5460 bytes
templates/content/images/cc-wiki.png | Bin 0 -> 2333 bytes
templates/content/images/close-small-hover.png | Bin 0 -> 337 bytes
templates/content/images/close-small.png | Bin 0 -> 293 bytes
templates/content/images/cnprog_logo_200_56.gif | Bin 0 -> 2114 bytes
templates/content/images/dash.gif | Bin 0 -> 44 bytes
templates/content/images/djangomade124x25_grey.gif | Bin 0 -> 2035 bytes
templates/content/images/dot-g.gif | Bin 0 -> 61 bytes
templates/content/images/dot-list.gif | Bin 0 -> 56 bytes
templates/content/images/edit.png | Bin 0 -> 758 bytes
templates/content/images/expander-arrow-hide.gif | Bin 0 -> 126 bytes
templates/content/images/expander-arrow-show.gif | Bin 0 -> 135 bytes
templates/content/images/favicon.gif | Bin 0 -> 3918 bytes
templates/content/images/favicon.ico | Bin 0 -> 3638 bytes
templates/content/images/feed-icon-small.png | Bin 0 -> 689 bytes
templates/content/images/grippie.png | Bin 0 -> 162 bytes
templates/content/images/indicator.gif | Bin 0 -> 2545 bytes
templates/content/images/logo.png | Bin 0 -> 3631 bytes
templates/content/images/logo1.png | Bin 0 -> 2752 bytes
templates/content/images/logo2.png | Bin 0 -> 2124 bytes
templates/content/images/medala.gif | Bin 0 -> 801 bytes
templates/content/images/medala_on.gif | Bin 0 -> 957 bytes
templates/content/images/new.gif | Bin 0 -> 635 bytes
templates/content/images/nophoto.png | Bin 0 -> 696 bytes
templates/content/images/openid.gif | Bin 0 -> 910 bytes
templates/content/images/openid/aol.gif | Bin 0 -> 2205 bytes
templates/content/images/openid/blogger.ico | Bin 0 -> 3638 bytes
templates/content/images/openid/claimid.ico | Bin 0 -> 3638 bytes
templates/content/images/openid/facebook.gif | Bin 0 -> 2075 bytes
templates/content/images/openid/flickr.ico | Bin 0 -> 1150 bytes
templates/content/images/openid/google.gif | Bin 0 -> 1596 bytes
templates/content/images/openid/livejournal.ico | Bin 0 -> 5222 bytes
templates/content/images/openid/myopenid.ico | Bin 0 -> 2862 bytes
.../content/images/openid/openid-inputicon.gif | Bin 0 -> 237 bytes
templates/content/images/openid/openid.gif | Bin 0 -> 740 bytes
templates/content/images/openid/technorati.ico | Bin 0 -> 2294 bytes
templates/content/images/openid/verisign.ico | Bin 0 -> 4710 bytes
templates/content/images/openid/vidoop.ico | Bin 0 -> 1406 bytes
templates/content/images/openid/wordpress.ico | Bin 0 -> 1150 bytes
templates/content/images/openid/yahoo.gif | Bin 0 -> 1682 bytes
templates/content/images/quest-bg.gif | Bin 0 -> 294 bytes
templates/content/images/vote-accepted-on.png | Bin 0 -> 1124 bytes
templates/content/images/vote-accepted.png | Bin 0 -> 1058 bytes
templates/content/images/vote-arrow-down-on.png | Bin 0 -> 905 bytes
templates/content/images/vote-arrow-down.png | Bin 0 -> 876 bytes
templates/content/images/vote-arrow-up-on.png | Bin 0 -> 906 bytes
templates/content/images/vote-arrow-up.png | Bin 0 -> 843 bytes
templates/content/images/vote-favorite-off.png | Bin 0 -> 930 bytes
templates/content/images/vote-favorite-on.png | Bin 0 -> 1023 bytes
templates/content/js/com.cnprog.editor.js | 68 +
templates/content/js/com.cnprog.post.js | 573 ++++
templates/content/js/com.cnprog.post.pack.js | 1 +
templates/content/js/com.cnprog.utils.js | 116 +
templates/content/js/compress.bat | 6 +
templates/content/js/excanvas.pack.js | 1 +
templates/content/js/flot-build.bat | 3 +
templates/content/js/jquery-1.2.6.js | 3549 ++++++++++++++++++++
templates/content/js/jquery-1.2.6.min.js | 32 +
templates/content/js/jquery.ajaxfileupload.js | 195 ++
templates/content/js/jquery.flot.js | 2421 +++++++++++++
templates/content/js/jquery.flot.pack.js | 1 +
templates/content/js/jquery.openid.js | 176 +
templates/content/js/jquery.validate.pack.js | 15 +
templates/content/js/se_hilite.js | 1 +
templates/content/js/se_hilite_src.js | 273 ++
templates/content/js/wmd/images/wmd-buttons.png | Bin 0 -> 7465 bytes
templates/content/js/wmd/showdown-min.js | 1 +
templates/content/js/wmd/showdown.js | 1309 ++++++++
templates/content/js/wmd/wmd-min.js | 1 +
templates/content/js/wmd/wmd-test.html | 158 +
templates/content/js/wmd/wmd.css | 129 +
templates/content/js/wmd/wmd.js | 2390 +++++++++++++
templates/content/js/yuicompressor-2.4.2.jar | Bin 0 -> 851219 bytes
templates/content/style/default.css | 1753 ++++++++++
templates/content/style/jquery.autocomplete.css | 49 +
templates/content/style/openid.css | 45 +
templates/content/style/prettify.css | 27 +
templates/content/style/style.css | 999 ++++++
templates/faq.html | 114 +
templates/feeds/rss_description.html | 1 +
templates/feeds/rss_title.html | 1 +
templates/footer.html | 30 +
templates/header.html | 64 +
templates/index.html | 121 +
templates/logout.html | 26 +
templates/pagesize.html | 24 +
templates/paginator.html | 35 +
templates/privacy.html | 40 +
templates/question.html | 469 +++
templates/question_edit.html | 185 +
templates/question_retag.html | 109 +
templates/questions.html | 153 +
templates/reopen.html | 37 +
templates/revisions_answer.html | 100 +
templates/revisions_question.html | 102 +
templates/tags.html | 61 +
templates/unanswered.html | 115 +
templates/user.html | 34 +
templates/user_edit.html | 90 +
templates/user_favorites.html | 7 +
templates/user_footer.html | 3 +
templates/user_info.html | 86 +
templates/user_preferences.html | 20 +
templates/user_recent.html | 25 +
templates/user_reputation.html | 40 +
templates/user_responses.html | 21 +
templates/user_stats.html | 127 +
templates/user_tabs.html | 20 +
templates/user_votes.html | 28 +
templates/users.html | 70 +
templates/users_questions.html | 60 +
urls.py | 63 +
utils/__init__.py | 0
utils/cache.py | 92 +
utils/html.py | 51 +
utils/lists.py | 86 +
200 files changed, 30458 insertions(+)
create mode 100644 .gitignore
create mode 100644 INSTALL
create mode 100644 LICENSE
create mode 100644 __init__.py
create mode 100644 development.log
create mode 100644 django_authopenid/__init__.py
create mode 100644 django_authopenid/admin.py
create mode 100644 django_authopenid/forms.py
create mode 100644 django_authopenid/middleware.py
create mode 100644 django_authopenid/mimeparse.py
create mode 100644 django_authopenid/models.py
create mode 100644 django_authopenid/urls.py
create mode 100644 django_authopenid/util.py
create mode 100644 django_authopenid/views.py
create mode 100644 forum/__init__.py
create mode 100644 forum/admin.py
create mode 100644 forum/auth.py
create mode 100644 forum/const.py
create mode 100644 forum/diff.py
create mode 100644 forum/feed.py
create mode 100644 forum/forms.py
create mode 100644 forum/management/__init__.py
create mode 100644 forum/management/commands/__init__.py
create mode 100644 forum/management/commands/base_command.py
create mode 100644 forum/management/commands/clean_award_badges.py
create mode 100644 forum/management/commands/multi_award_badges.py
create mode 100644 forum/management/commands/once_award_badges.py
create mode 100644 forum/management/commands/sample_command.py
create mode 100644 forum/managers.py
create mode 100644 forum/models.py
create mode 100644 forum/templatetags/__init__.py
create mode 100644 forum/templatetags/extra_filters.py
create mode 100644 forum/templatetags/extra_tags.py
create mode 100644 forum/user.py
create mode 100644 forum/views.py
create mode 100644 locale/zh_cn/LC_MESSAGES/django.mo
create mode 100644 locale/zh_cn/LC_MESSAGES/django.po
create mode 100644 manage.py
create mode 100644 middleware/__init__.py
create mode 100644 middleware/pagesize.py
create mode 100644 settings.py
create mode 100644 settings_local.py.dist
create mode 100644 sql_scripts/cnprog.xml
create mode 100644 sql_scripts/cnprog_new_install.sql
create mode 100644 sql_scripts/cnprog_new_install_2009_02_28.sql
create mode 100644 sql_scripts/cnprog_new_install_2009_03_31.sql
create mode 100644 sql_scripts/cnprog_new_install_2009_04_07.sql
create mode 100644 sql_scripts/cnprog_new_install_2009_04_09.sql
create mode 100644 sql_scripts/update_2009_01_13_001.sql
create mode 100644 sql_scripts/update_2009_01_13_002.sql
create mode 100644 sql_scripts/update_2009_01_18_001.sql
create mode 100644 sql_scripts/update_2009_01_24.sql
create mode 100644 sql_scripts/update_2009_01_25_001.sql
create mode 100644 sql_scripts/update_2009_02_26_001.sql
create mode 100644 sql_scripts/update_2009_04_10_001.sql
create mode 100644 sql_scripts/update_2009_12_24_001.sql
create mode 100644 sql_scripts/update_2009_12_27_001.sql
create mode 100644 sql_scripts/update_2009_12_27_002.sql
create mode 100644 templates/404.html
create mode 100644 templates/500.html
create mode 100644 templates/about.html
create mode 100644 templates/answer_edit.html
create mode 100644 templates/ask.html
create mode 100644 templates/authopenid/changeemail.html
create mode 100644 templates/authopenid/changeopenid.html
create mode 100644 templates/authopenid/changepw.html
create mode 100644 templates/authopenid/complete.html
create mode 100644 templates/authopenid/confirm_email.txt
create mode 100644 templates/authopenid/delete.html
create mode 100644 templates/authopenid/failure.html
create mode 100644 templates/authopenid/sendpw.html
create mode 100644 templates/authopenid/sendpw_email.txt
create mode 100644 templates/authopenid/settings.html
create mode 100644 templates/authopenid/signin.html
create mode 100644 templates/authopenid/signup.html
create mode 100644 templates/authopenid/yadis.xrdf
create mode 100644 templates/badge.html
create mode 100644 templates/badges.html
create mode 100644 templates/base.html
create mode 100644 templates/base_content.html
create mode 100644 templates/book.html
create mode 100644 templates/close.html
create mode 100644 templates/content/images/box-arrow.gif
create mode 100644 templates/content/images/bullet_green.gif
create mode 100644 templates/content/images/cc-88x31.png
create mode 100644 templates/content/images/cc-wiki.png
create mode 100644 templates/content/images/close-small-hover.png
create mode 100644 templates/content/images/close-small.png
create mode 100644 templates/content/images/cnprog_logo_200_56.gif
create mode 100644 templates/content/images/dash.gif
create mode 100644 templates/content/images/djangomade124x25_grey.gif
create mode 100644 templates/content/images/dot-g.gif
create mode 100644 templates/content/images/dot-list.gif
create mode 100644 templates/content/images/edit.png
create mode 100644 templates/content/images/expander-arrow-hide.gif
create mode 100644 templates/content/images/expander-arrow-show.gif
create mode 100644 templates/content/images/favicon.gif
create mode 100644 templates/content/images/favicon.ico
create mode 100644 templates/content/images/feed-icon-small.png
create mode 100644 templates/content/images/grippie.png
create mode 100644 templates/content/images/indicator.gif
create mode 100644 templates/content/images/logo.png
create mode 100644 templates/content/images/logo1.png
create mode 100644 templates/content/images/logo2.png
create mode 100644 templates/content/images/medala.gif
create mode 100644 templates/content/images/medala_on.gif
create mode 100644 templates/content/images/new.gif
create mode 100644 templates/content/images/nophoto.png
create mode 100644 templates/content/images/openid.gif
create mode 100644 templates/content/images/openid/aol.gif
create mode 100644 templates/content/images/openid/blogger.ico
create mode 100644 templates/content/images/openid/claimid.ico
create mode 100644 templates/content/images/openid/facebook.gif
create mode 100644 templates/content/images/openid/flickr.ico
create mode 100644 templates/content/images/openid/google.gif
create mode 100644 templates/content/images/openid/livejournal.ico
create mode 100644 templates/content/images/openid/myopenid.ico
create mode 100644 templates/content/images/openid/openid-inputicon.gif
create mode 100644 templates/content/images/openid/openid.gif
create mode 100644 templates/content/images/openid/technorati.ico
create mode 100644 templates/content/images/openid/verisign.ico
create mode 100644 templates/content/images/openid/vidoop.ico
create mode 100644 templates/content/images/openid/wordpress.ico
create mode 100644 templates/content/images/openid/yahoo.gif
create mode 100644 templates/content/images/quest-bg.gif
create mode 100644 templates/content/images/vote-accepted-on.png
create mode 100644 templates/content/images/vote-accepted.png
create mode 100644 templates/content/images/vote-arrow-down-on.png
create mode 100644 templates/content/images/vote-arrow-down.png
create mode 100644 templates/content/images/vote-arrow-up-on.png
create mode 100644 templates/content/images/vote-arrow-up.png
create mode 100644 templates/content/images/vote-favorite-off.png
create mode 100644 templates/content/images/vote-favorite-on.png
create mode 100644 templates/content/js/com.cnprog.editor.js
create mode 100644 templates/content/js/com.cnprog.post.js
create mode 100644 templates/content/js/com.cnprog.post.pack.js
create mode 100644 templates/content/js/com.cnprog.utils.js
create mode 100644 templates/content/js/compress.bat
create mode 100644 templates/content/js/excanvas.pack.js
create mode 100644 templates/content/js/flot-build.bat
create mode 100644 templates/content/js/jquery-1.2.6.js
create mode 100644 templates/content/js/jquery-1.2.6.min.js
create mode 100644 templates/content/js/jquery.ajaxfileupload.js
create mode 100644 templates/content/js/jquery.flot.js
create mode 100644 templates/content/js/jquery.flot.pack.js
create mode 100644 templates/content/js/jquery.openid.js
create mode 100644 templates/content/js/jquery.validate.pack.js
create mode 100644 templates/content/js/se_hilite.js
create mode 100644 templates/content/js/se_hilite_src.js
create mode 100644 templates/content/js/wmd/images/wmd-buttons.png
create mode 100644 templates/content/js/wmd/showdown-min.js
create mode 100644 templates/content/js/wmd/showdown.js
create mode 100644 templates/content/js/wmd/wmd-min.js
create mode 100644 templates/content/js/wmd/wmd-test.html
create mode 100644 templates/content/js/wmd/wmd.css
create mode 100644 templates/content/js/wmd/wmd.js
create mode 100644 templates/content/js/yuicompressor-2.4.2.jar
create mode 100644 templates/content/style/default.css
create mode 100644 templates/content/style/jquery.autocomplete.css
create mode 100644 templates/content/style/openid.css
create mode 100644 templates/content/style/prettify.css
create mode 100644 templates/content/style/style.css
create mode 100644 templates/faq.html
create mode 100644 templates/feeds/rss_description.html
create mode 100644 templates/feeds/rss_title.html
create mode 100644 templates/footer.html
create mode 100644 templates/header.html
create mode 100644 templates/index.html
create mode 100644 templates/logout.html
create mode 100644 templates/pagesize.html
create mode 100644 templates/paginator.html
create mode 100644 templates/privacy.html
create mode 100644 templates/question.html
create mode 100644 templates/question_edit.html
create mode 100644 templates/question_retag.html
create mode 100644 templates/questions.html
create mode 100644 templates/reopen.html
create mode 100644 templates/revisions_answer.html
create mode 100644 templates/revisions_question.html
create mode 100644 templates/tags.html
create mode 100644 templates/unanswered.html
create mode 100644 templates/user.html
create mode 100644 templates/user_edit.html
create mode 100644 templates/user_favorites.html
create mode 100644 templates/user_footer.html
create mode 100644 templates/user_info.html
create mode 100644 templates/user_preferences.html
create mode 100644 templates/user_recent.html
create mode 100644 templates/user_reputation.html
create mode 100644 templates/user_responses.html
create mode 100644 templates/user_stats.html
create mode 100644 templates/user_tabs.html
create mode 100644 templates/user_votes.html
create mode 100644 templates/users.html
create mode 100644 templates/users_questions.html
create mode 100644 urls.py
create mode 100644 utils/__init__.py
create mode 100644 utils/cache.py
create mode 100644 utils/html.py
create mode 100644 utils/lists.py
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..b7a3ffac
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+settings_local.py
+*.pyc
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 00000000..59f2dd0b
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,33 @@
+PRE-REQUIREMENTS:
+-----------------------------------------------
+1. Python2.5, MySQL, Django v1.0+
+
+2. Python-openid v2.2
+http://openidenabled.com/python-openid/
+
+3. django-authopenid(Included in project already)
+http://code.google.com/p/django-authopenid/
+
+4. html5lib
+http://code.google.com/p/html5lib/
+Used for HTML sanitizer
+
+5. Markdown2
+http://code.google.com/p/python-markdown2/
+
+
+INSTALL STEPS:
+-----------------------------------------------
+1. Copy settings_local.py.dist to settings_local.py and
+update all your settings. Check settings.py and update
+it as well if necessory.
+
+2. Prepare your database by using the same database/account
+configuration from above.
+
+3. Run "python manager.py runserver" to startup django
+development environment.
+
+4. 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.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..e23e4b67
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,12 @@
+版权所有(c) 2008 CNProg.com
+
+根据2.0版本Apache许可证("许可证")授权;
+根据本许可证,用户可以不使用此文件。
+用户可从下列网址获得许可证副本:
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+除非因适用法律需要或书面同意,
+根据许可证分发的软件是基于"按原样"基础提供,
+无任何明示的或暗示的保证或条件。
+详见根据许可证许可下,特定语言的管辖权限和限制。
\ No newline at end of file
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/development.log b/development.log
new file mode 100644
index 00000000..e69de29b
diff --git a/django_authopenid/__init__.py b/django_authopenid/__init__.py
new file mode 100644
index 00000000..ff040ed7
--- /dev/null
+++ b/django_authopenid/__init__.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2007, 2008, Beno卯t Chesneau
+#
+# 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 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.
+
+"""
+Django authentification application to *with openid using django auth contrib/.
+
+This application allow a user to connect to you website with :
+ * legacy account : username/password
+ * openid url
+"""
+
+__version__ = "0.9.4"
diff --git a/django_authopenid/admin.py b/django_authopenid/admin.py
new file mode 100644
index 00000000..f64ee579
--- /dev/null
+++ b/django_authopenid/admin.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+from django.contrib import admin
+from django_authopenid.models import UserAssociation
+
+
+class UserAssociationAdmin(admin.ModelAdmin):
+ """User association admin class"""
+admin.site.register(UserAssociation, UserAssociationAdmin)
\ No newline at end of file
diff --git a/django_authopenid/forms.py b/django_authopenid/forms.py
new file mode 100644
index 00000000..9c519d74
--- /dev/null
+++ b/django_authopenid/forms.py
@@ -0,0 +1,435 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2007, 2008, Beno卯t Chesneau
+#
+# 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 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 import forms
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate
+from django.utils.translation import ugettext as _
+from django.conf import settings
+
+import re
+
+
+# needed for some linux distributions like debian
+try:
+ from openid.yadis import xri
+except ImportError:
+ from yadis import xri
+
+from django_authopenid.util import clean_next
+
+__all__ = ['OpenidSigninForm', 'OpenidAuthForm', 'OpenidVerifyForm',
+ 'OpenidRegisterForm', 'RegistrationForm', 'ChangepwForm',
+ 'ChangeemailForm', 'EmailPasswordForm', 'DeleteForm',
+ 'ChangeOpenidForm', 'ChangeEmailForm', 'ChangepwForm']
+
+class OpenidSigninForm(forms.Form):
+ """ signin form """
+ openid_url = forms.CharField(max_length=255, widget=forms.widgets.TextInput(attrs={'class': 'openid-login-input', 'size':80}))
+ next = forms.CharField(max_length=255, widget=forms.HiddenInput(), required=False)
+
+ def clean_openid_url(self):
+ """ test if openid is accepted """
+ if 'openid_url' in self.cleaned_data:
+ openid_url = self.cleaned_data['openid_url']
+ if xri.identifierScheme(openid_url) == 'XRI' and getattr(
+ settings, 'OPENID_DISALLOW_INAMES', False
+ ):
+ raise forms.ValidationError(_('i-names are not supported'))
+ return self.cleaned_data['openid_url']
+
+ def clean_next(self):
+ """ validate next """
+ if 'next' in self.cleaned_data and self.cleaned_data['next'] != "":
+ self.cleaned_data['next'] = clean_next(self.cleaned_data['next'])
+ return self.cleaned_data['next']
+
+
+attrs_dict = { 'class': 'required login' }
+username_re = re.compile(r'^\w+$')
+RESERVED_NAMES = (u'fuck', u'shit', u'ass', u'sex', u'add',
+ u'edit', u'save', u'delete', u'manage', u'update', 'remove', 'new')
+
+class OpenidAuthForm(forms.Form):
+ """ legacy account signin form """
+ next = forms.CharField(max_length=255, widget=forms.HiddenInput(),
+ required=False)
+ username = forms.CharField(max_length=30,
+ widget=forms.widgets.TextInput(attrs=attrs_dict))
+ password = forms.CharField(max_length=128,
+ widget=forms.widgets.PasswordInput(attrs=attrs_dict))
+
+ def __init__(self, data=None, files=None, auto_id='id_%s',
+ prefix=None, initial=None):
+ super(OpenidAuthForm, self).__init__(data, files, auto_id,
+ prefix, initial)
+ self.user_cache = None
+
+ def clean_username(self):
+ """ validate username and test if it exists."""
+ if 'username' in self.cleaned_data and \
+ 'openid_url' not in self.cleaned_data:
+ if not username_re.search(self.cleaned_data['username']):
+ raise forms.ValidationError(_("Usernames can only contain \
+ letters, numbers and underscores"))
+ try:
+ user = User.objects.get(
+ username__exact = self.cleaned_data['username']
+ )
+ except User.DoesNotExist:
+ raise forms.ValidationError(_("This username does not exist \
+ in our database. Please choose another."))
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'There is already more than one \
+ account registered with that username. Please try \
+ another.')
+ return self.cleaned_data['username']
+
+ def clean_password(self):
+ """" test if password is valid for this username """
+ if 'username' in self.cleaned_data and \
+ 'password' in self.cleaned_data:
+ self.user_cache = authenticate(
+ username=self.cleaned_data['username'],
+ password=self.cleaned_data['password']
+ )
+ if self.user_cache is None:
+ raise forms.ValidationError(_("Please enter a valid \
+ username and password. Note that both fields are \
+ case-sensitive."))
+ elif self.user_cache.is_active == False:
+ raise forms.ValidationError(_("This account is inactive."))
+ return self.cleaned_data['password']
+
+ def clean_next(self):
+ """ validate next url """
+ if 'next' in self.cleaned_data and \
+ self.cleaned_data['next'] != "":
+ self.cleaned_data['next'] = clean_next(self.cleaned_data['next'])
+ return self.cleaned_data['next']
+
+ def get_user(self):
+ """ get authenticated user """
+ return self.user_cache
+
+
+class OpenidRegisterForm(forms.Form):
+ """ openid signin form """
+ next = forms.CharField(max_length=255, widget=forms.HiddenInput(),
+ required=False)
+ username = forms.CharField(max_length=30,
+ widget=forms.widgets.TextInput(attrs=attrs_dict))
+ email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
+ maxlength=200)), label=u'Email address')
+
+ def clean_username(self):
+ """ test if username is valid and exist in database """
+ if 'username' in self.cleaned_data:
+ if not username_re.search(self.cleaned_data['username']):
+ raise forms.ValidationError(u"鐢ㄦ埛鍚嶅彧鑳藉寘鍚嫳鏂囧瓧姣嶃佹暟瀛楀拰涓嬪垝绾")
+ if self.cleaned_data['username'] in RESERVED_NAMES:
+ raise forms.ValidationError(u'瀵逛笉璧凤紝鎮ㄤ笉鑳芥敞鍐岃鐢ㄦ埛鍚嶏紝璇锋崲涓涓瘯璇')
+ if len(self.cleaned_data['username']) < 3:
+ raise forms.ValidationError(u'鐢ㄦ埛鍚嶅お鐭紝璇蜂娇鐢ㄤ笁涓垨涓変釜浠ヤ笂瀛楃')
+ try:
+ user = User.objects.get(
+ username__exact = self.cleaned_data['username']
+ )
+ except User.DoesNotExist:
+ return self.cleaned_data['username']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'璇ョ敤鎴峰悕宸茶娉ㄥ唽锛岃鎹竴涓瘯璇')
+ raise forms.ValidationError(u'璇ョ敤鎴峰悕宸茶娉ㄥ唽锛岃鎹釜璇曡瘯')
+
+ def clean_email(self):
+ """For security reason one unique email in database"""
+ 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(u'There is already more than one \
+ account registered with that e-mail address. Please try \
+ another.')
+ raise forms.ValidationError(_("This email is already \
+ registered in our database. Please choose another."))
+
+
+class OpenidVerifyForm(forms.Form):
+ """ openid verify form (associate an openid with an account) """
+ next = forms.CharField(max_length=255, widget = forms.HiddenInput(),
+ required=False)
+ username = forms.CharField(max_length=30,
+ widget=forms.widgets.TextInput(attrs=attrs_dict))
+ password = forms.CharField(max_length=128,
+ widget=forms.widgets.PasswordInput(attrs=attrs_dict))
+
+ def __init__(self, data=None, files=None, auto_id='id_%s',
+ prefix=None, initial=None):
+ super(OpenidVerifyForm, self).__init__(data, files, auto_id,
+ prefix, initial)
+ self.user_cache = None
+
+ def clean_username(self):
+ """ validate username """
+ if 'username' in self.cleaned_data:
+ if not username_re.search(self.cleaned_data['username']):
+ raise forms.ValidationError(_("Usernames can only contain \
+ letters, numbers and underscores"))
+ try:
+ user = User.objects.get(
+ username__exact = self.cleaned_data['username']
+ )
+ except User.DoesNotExist:
+ raise forms.ValidationError(_("This username don't exist. \
+ Please choose another."))
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'Somehow, that username is in \
+ use for multiple accounts. Please contact us to get this \
+ problem resolved.')
+ return self.cleaned_data['username']
+
+ def clean_password(self):
+ """ test if password is valid for this user """
+ if 'username' in self.cleaned_data and \
+ 'password' in self.cleaned_data:
+ self.user_cache = authenticate(
+ username = self.cleaned_data['username'],
+ password = self.cleaned_data['password']
+ )
+ if self.user_cache is None:
+ raise forms.ValidationError(_("Please enter a valid \
+ username and password. Note that both fields are \
+ case-sensitive."))
+ elif self.user_cache.is_active == False:
+ raise forms.ValidationError(_("This account is inactive."))
+ return self.cleaned_data['password']
+
+ def get_user(self):
+ """ get authenticated user """
+ return self.user_cache
+
+
+attrs_dict = { 'class': 'required' }
+username_re = re.compile(r'^\w+$')
+
+class RegistrationForm(forms.Form):
+ """ legacy registration form """
+
+ next = forms.CharField(max_length=255, widget=forms.HiddenInput(),
+ required=False)
+ username = forms.CharField(max_length=30,
+ widget=forms.TextInput(attrs=attrs_dict),
+ label=u'Username')
+ email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
+ maxlength=200)), label=u'Email address')
+ password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
+ label=u'Password')
+ password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
+ label=u'Password (again, to catch typos)')
+
+ def clean_username(self):
+ """
+ Validates that the username is alphanumeric and is not already
+ in use.
+
+ """
+ if 'username' in self.cleaned_data:
+ if not username_re.search(self.cleaned_data['username']):
+ raise forms.ValidationError(u'Usernames can only contain \
+ letters, numbers and underscores')
+ try:
+ user = User.objects.get(
+ username__exact = self.cleaned_data['username']
+ )
+
+ except User.DoesNotExist:
+ return self.cleaned_data['username']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'Somehow, that username is in \
+ use for multiple accounts. Please contact us to get this \
+ problem resolved.')
+ raise forms.ValidationError(u'This username is already taken. \
+ Please choose another.')
+
+ def clean_email(self):
+ """ validate if email exist in database
+ :return: raise error if it exist """
+ 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(u'There is already more than one \
+ account registered with that e-mail address. Please try \
+ another.')
+ raise forms.ValidationError(u'This email is already registered \
+ in our database. Please choose another.')
+ return self.cleaned_data['email']
+
+ def clean_password2(self):
+ """
+ Validates that the two password inputs match.
+
+ """
+ if 'password1' in self.cleaned_data and \
+ 'password2' in self.cleaned_data and \
+ self.cleaned_data['password1'] == \
+ self.cleaned_data['password2']:
+ return self.cleaned_data['password2']
+ raise forms.ValidationError(u'You must type the same password each \
+ time')
+
+
+class ChangepwForm(forms.Form):
+ """ change password form """
+ oldpw = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+ password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+ password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+
+ def __init__(self, data=None, user=None, *args, **kwargs):
+ if user is None:
+ raise TypeError("Keyword argument 'user' must be supplied")
+ super(ChangepwForm, self).__init__(data, *args, **kwargs)
+ self.user = user
+
+ def clean_oldpw(self):
+ """ test old password """
+ if not self.user.check_password(self.cleaned_data['oldpw']):
+ raise forms.ValidationError(_("Old password is incorrect. \
+ Please enter the correct password."))
+ return self.cleaned_data['oldpw']
+
+ def clean_password2(self):
+ """
+ Validates that the two password inputs match.
+ """
+ if 'password1' in self.cleaned_data and \
+ 'password2' in self.cleaned_data and \
+ self.cleaned_data['password1'] == self.cleaned_data['password2']:
+ return self.cleaned_data['password2']
+ raise forms.ValidationError(_("new passwords do not match"))
+
+
+class ChangeemailForm(forms.Form):
+ """ change email form """
+ email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
+ maxlength=200)), label=u'Email address')
+ password = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+
+ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, \
+ initial=None, user=None):
+ if user is None:
+ raise TypeError("Keyword argument 'user' must be supplied")
+ super(ChangeemailForm, self).__init__(data, files, auto_id,
+ prefix, initial)
+ self.test_openid = False
+ self.user = user
+
+
+ def clean_email(self):
+ """ check if email don't exist """
+ if 'email' in self.cleaned_data:
+ if self.user.email != self.cleaned_data['email']:
+ try:
+ user = User.objects.get(email = self.cleaned_data['email'])
+ except User.DoesNotExist:
+ return self.cleaned_data['email']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'There is already more than one \
+ account registered with that e-mail address. Please try \
+ another.')
+ raise forms.ValidationError(u'This email is already registered \
+ in our database. Please choose another.')
+ return self.cleaned_data['email']
+
+
+ def clean_password(self):
+ """ check if we have to test a legacy account or not """
+ if 'password' in self.cleaned_data:
+ if not self.user.check_password(self.cleaned_data['password']):
+ self.test_openid = True
+ return self.cleaned_data['password']
+
+class ChangeopenidForm(forms.Form):
+ """ change openid form """
+ openid_url = forms.CharField(max_length=255,
+ widget=forms.TextInput(attrs={'class': "required" }))
+
+ def __init__(self, data=None, user=None, *args, **kwargs):
+ if user is None:
+ raise TypeError("Keyword argument 'user' must be supplied")
+ super(ChangeopenidForm, self).__init__(data, *args, **kwargs)
+ self.user = user
+
+class DeleteForm(forms.Form):
+ """ confirm form to delete an account """
+ confirm = forms.CharField(widget=forms.CheckboxInput(attrs=attrs_dict))
+ password = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+
+ def __init__(self, data=None, files=None, auto_id='id_%s',
+ prefix=None, initial=None, user=None):
+ super(DeleteForm, self).__init__(data, files, auto_id, prefix, initial)
+ self.test_openid = False
+ self.user = user
+
+ def clean_password(self):
+ """ check if we have to test a legacy account or not """
+ if 'password' in self.cleaned_data:
+ if not self.user.check_password(self.cleaned_data['password']):
+ self.test_openid = True
+ return self.cleaned_data['password']
+
+
+class EmailPasswordForm(forms.Form):
+ """ send new password form """
+ username = forms.CharField(max_length=30,
+ widget=forms.TextInput(attrs={'class': "required" }))
+
+ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
+ initial=None):
+ super(EmailPasswordForm, self).__init__(data, files, auto_id,
+ prefix, initial)
+ self.user_cache = None
+
+
+ def clean_username(self):
+ """ get user for this username """
+ if 'username' in self.cleaned_data:
+ try:
+ self.user_cache = User.objects.get(
+ username = self.cleaned_data['username'])
+ except:
+ raise forms.ValidationError(_("Incorrect username."))
+ return self.cleaned_data['username']
diff --git a/django_authopenid/middleware.py b/django_authopenid/middleware.py
new file mode 100644
index 00000000..c0572c6e
--- /dev/null
+++ b/django_authopenid/middleware.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+from django_authopenid import mimeparse
+from django.http import HttpResponseRedirect
+from django.core.urlresolvers import reverse
+
+__all__ = ["OpenIDMiddleware"]
+
+class OpenIDMiddleware(object):
+ """
+ Populate request.openid. This comes either from cookie or from
+ session, depending on the presence of OPENID_USE_SESSIONS.
+ """
+ def process_request(self, request):
+ request.openid = request.session.get('openid', None)
+
+ def process_response(self, request, response):
+ if response.status_code != 200 or len(response.content) < 200:
+ return response
+ path = request.get_full_path()
+ if path == "/" and request.META.has_key('HTTP_ACCEPT') and \
+ mimeparse.best_match(['text/html', 'application/xrds+xml'],
+ request.META['HTTP_ACCEPT']) == 'application/xrds+xml':
+ return HttpResponseRedirect(reverse('yadis_xrdf'))
+ return response
\ No newline at end of file
diff --git a/django_authopenid/mimeparse.py b/django_authopenid/mimeparse.py
new file mode 100644
index 00000000..ab02eab0
--- /dev/null
+++ b/django_authopenid/mimeparse.py
@@ -0,0 +1,160 @@
+"""MIME-Type Parser
+
+This module provides basic functions for handling mime-types. It can handle
+matching mime-types against a list of media-ranges. See section 14.1 of
+the HTTP specification [RFC 2616] for a complete explaination.
+
+ http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
+
+Contents:
+ - parse_mime_type(): Parses a mime-type into it's component parts.
+ - parse_media_range(): Media-ranges are mime-types with wild-cards and a 'q' quality parameter.
+ - quality(): Determines the quality ('q') of a mime-type when compared against a list of media-ranges.
+ - quality_parsed(): Just like quality() except the second parameter must be pre-parsed.
+ - best_match(): Choose the mime-type with the highest quality ('q') from a list of candidates.
+"""
+
+__version__ = "0.1.1"
+__author__ = 'Joe Gregorio'
+__email__ = "joe@bitworking.org"
+__credits__ = ""
+
+def parse_mime_type(mime_type):
+ """Carves up a mime_type and returns a tuple of the
+ (type, subtype, params) where 'params' is a dictionary
+ of all the parameters for the media range.
+ For example, the media range 'application/xhtml;q=0.5' would
+ get parsed into:
+
+ ('application', 'xhtml', {'q', '0.5'})
+ """
+ parts = mime_type.split(";")
+ params = dict([tuple([s.strip() for s in param.split("=")])\
+ for param in parts[1:] ])
+ (type, subtype) = parts[0].split("/")
+ return (type.strip(), subtype.strip(), params)
+
+def parse_media_range(range):
+ """Carves up a media range and returns a tuple of the
+ (type, subtype, params) where 'params' is a dictionary
+ of all the parameters for the media range.
+ For example, the media range 'application/*;q=0.5' would
+ get parsed into:
+
+ ('application', '*', {'q', '0.5'})
+
+ In addition this function also guarantees that there
+ is a value for 'q' in the params dictionary, filling it
+ in with a proper default if necessary.
+ """
+ (type, subtype, params) = parse_mime_type(range)
+ if not params.has_key('q') or not params['q'] or \
+ not float(params['q']) or float(params['q']) > 1\
+ or float(params['q']) < 0:
+ params['q'] = '1'
+ return (type, subtype, params)
+
+def quality_parsed(mime_type, parsed_ranges):
+ """Find the best match for a given mime_type against
+ a list of media_ranges that have already been
+ parsed by parse_media_range(). Returns the
+ 'q' quality parameter of the best match, 0 if no
+ match was found. This function bahaves the same as quality()
+ except that 'parsed_ranges' must be a list of
+ parsed media ranges. """
+ best_fitness = -1
+ best_match = ""
+ best_fit_q = 0
+ (target_type, target_subtype, target_params) =\
+ parse_media_range(mime_type)
+ for (type, subtype, params) in parsed_ranges:
+ param_matches = reduce(lambda x, y: x+y, [1 for (key, value) in \
+ target_params.iteritems() if key != 'q' and \
+ params.has_key(key) and value == params[key]], 0)
+ if (type == target_type or type == '*' or target_type == '*') and \
+ (subtype == target_subtype or subtype == '*' or target_subtype == '*'):
+ fitness = (type == target_type) and 100 or 0
+ fitness += (subtype == target_subtype) and 10 or 0
+ fitness += param_matches
+ if fitness > best_fitness:
+ best_fitness = fitness
+ best_fit_q = params['q']
+
+ return float(best_fit_q)
+
+def quality(mime_type, ranges):
+ """Returns the quality 'q' of a mime_type when compared
+ against the media-ranges in ranges. For example:
+
+ >>> quality('text/html','text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5')
+ 0.7
+
+ """
+ parsed_ranges = [parse_media_range(r) for r in ranges.split(",")]
+ return quality_parsed(mime_type, parsed_ranges)
+
+def best_match(supported, header):
+ """Takes a list of supported mime-types and finds the best
+ match for all the media-ranges listed in header. The value of
+ header must be a string that conforms to the format of the
+ HTTP Accept: header. The value of 'supported' is a list of
+ mime-types.
+
+ >>> best_match(['application/xbel+xml', 'text/xml'], 'text/*;q=0.5,*/*; q=0.1')
+ 'text/xml'
+ """
+ parsed_header = [parse_media_range(r) for r in header.split(",")]
+ weighted_matches = [(quality_parsed(mime_type, parsed_header), mime_type)\
+ for mime_type in supported]
+ weighted_matches.sort()
+ return weighted_matches[-1][0] and weighted_matches[-1][1] or ''
+
+if __name__ == "__main__":
+ import unittest
+
+ class TestMimeParsing(unittest.TestCase):
+
+ def test_parse_media_range(self):
+ self.assert_(('application', 'xml', {'q': '1'}) == parse_media_range('application/xml;q=1'))
+ self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml'))
+ self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml;q='))
+ self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml ; q='))
+ self.assertEqual(('application', 'xml', {'q': '1', 'b': 'other'}), parse_media_range('application/xml ; q=1;b=other'))
+ self.assertEqual(('application', 'xml', {'q': '1', 'b': 'other'}), parse_media_range('application/xml ; q=2;b=other'))
+
+ def test_rfc_2616_example(self):
+ accept = "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5"
+ self.assertEqual(1, quality("text/html;level=1", accept))
+ self.assertEqual(0.7, quality("text/html", accept))
+ self.assertEqual(0.3, quality("text/plain", accept))
+ self.assertEqual(0.5, quality("image/jpeg", accept))
+ self.assertEqual(0.4, quality("text/html;level=2", accept))
+ self.assertEqual(0.7, quality("text/html;level=3", accept))
+
+ def test_best_match(self):
+ mime_types_supported = ['application/xbel+xml', 'application/xml']
+ # direct match
+ self.assertEqual(best_match(mime_types_supported, 'application/xbel+xml'), 'application/xbel+xml')
+ # direct match with a q parameter
+ self.assertEqual(best_match(mime_types_supported, 'application/xbel+xml; q=1'), 'application/xbel+xml')
+ # direct match of our second choice with a q parameter
+ self.assertEqual(best_match(mime_types_supported, 'application/xml; q=1'), 'application/xml')
+ # match using a subtype wildcard
+ self.assertEqual(best_match(mime_types_supported, 'application/*; q=1'), 'application/xml')
+ # match using a type wildcard
+ self.assertEqual(best_match(mime_types_supported, '*/*'), 'application/xml')
+
+ mime_types_supported = ['application/xbel+xml', 'text/xml']
+ # match using a type versus a lower weighted subtype
+ self.assertEqual(best_match(mime_types_supported, 'text/*;q=0.5,*/*; q=0.1'), 'text/xml')
+ # fail to match anything
+ self.assertEqual(best_match(mime_types_supported, 'text/html,application/atom+xml; q=0.9'), '')
+
+ def test_support_wildcards(self):
+ mime_types_supported = ['image/*', 'application/xml']
+ # match using a type wildcard
+ self.assertEqual(best_match(mime_types_supported, 'image/png'), 'image/*')
+ # match using a wildcard for both requested and supported
+ self.assertEqual(best_match(mime_types_supported, 'image/*'), 'image/*')
+
+ unittest.main()
\ No newline at end of file
diff --git a/django_authopenid/models.py b/django_authopenid/models.py
new file mode 100644
index 00000000..9826c452
--- /dev/null
+++ b/django_authopenid/models.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.db import models
+
+import md5, random, sys, os, time
+
+__all__ = ['Nonce', 'Association', 'UserAssociation',
+ 'UserPasswordQueueManager', 'UserPasswordQueue']
+
+class Nonce(models.Model):
+ """ openid nonce """
+ server_url = models.CharField(max_length=255)
+ timestamp = models.IntegerField()
+ salt = models.CharField(max_length=40)
+
+ def __unicode__(self):
+ return u"Nonce: %s" % self.id
+
+
+class Association(models.Model):
+ """ association openid url and lifetime """
+ server_url = models.TextField(max_length=2047)
+ handle = models.CharField(max_length=255)
+ secret = models.TextField(max_length=255) # Stored base64 encoded
+ issued = models.IntegerField()
+ lifetime = models.IntegerField()
+ assoc_type = models.TextField(max_length=64)
+
+ def __unicode__(self):
+ return u"Association: %s, %s" % (self.server_url, self.handle)
+
+class UserAssociation(models.Model):
+ """
+ model to manage association between openid and user
+ """
+ openid_url = models.CharField(blank=False, max_length=255)
+ user = models.ForeignKey(User, unique=True)
+
+ def __unicode__(self):
+ return "Openid %s with user %s" % (self.openid_url, self.user)
+
+class UserPasswordQueueManager(models.Manager):
+ """ manager for UserPasswordQueue object """
+ def get_new_confirm_key(self):
+ "Returns key that isn't being used."
+ # The random module is seeded when this Apache child is created.
+ # Use SECRET_KEY as added salt.
+ while 1:
+ confirm_key = md5.new("%s%s%s%s" % (
+ random.randint(0, sys.maxint - 1), os.getpid(),
+ time.time(), settings.SECRET_KEY)).hexdigest()
+ try:
+ self.get(confirm_key=confirm_key)
+ except self.model.DoesNotExist:
+ break
+ return confirm_key
+
+
+class UserPasswordQueue(models.Model):
+ """
+ model for new password queue.
+ """
+ user = models.ForeignKey(User, unique=True)
+ new_password = models.CharField(max_length=30)
+ confirm_key = models.CharField(max_length=40)
+
+ objects = UserPasswordQueueManager()
+
+ def __unicode__(self):
+ return self.user.username
diff --git a/django_authopenid/urls.py b/django_authopenid/urls.py
new file mode 100644
index 00000000..1ab65941
--- /dev/null
+++ b/django_authopenid/urls.py
@@ -0,0 +1,27 @@
+# -*- 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$' % _('signout/'), 'signout', name='user_signout'),
+ url(r'^%s%s$' % (_('signin/'), _('complete/')), 'complete_signin',
+ name='user_complete_signin'),
+ url(r'^%s$' % _('register/'), 'register', name='user_register'),
+ url(r'^%s$' % _('signup/'), 'signup', name='user_signup'),
+ #disable current sendpw function
+ url(r'^%s$' % _('sendpw/'), 'signin', name='user_sendpw'),
+ #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$' % _('email/'), 'changeemail', name='user_changeemail'),
+ #url(r'^%s$' % _('openid/'), 'changeopenid', name='user_changeopenid'),
+ url(r'^%s$' % _('delete/'), 'delete', name='user_delete'),
+)
diff --git a/django_authopenid/util.py b/django_authopenid/util.py
new file mode 100644
index 00000000..841a81c7
--- /dev/null
+++ b/django_authopenid/util.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+from openid.store.interface import OpenIDStore
+from openid.association import Association as OIDAssociation
+from openid.extensions import sreg
+import openid.store
+
+from django.db.models.query import Q
+from django.conf import settings
+from django.http import str_to_unicode
+
+
+# needed for some linux distributions like debian
+try:
+ from openid.yadis import xri
+except:
+ from yadis import xri
+
+import time, base64, md5, operator
+import urllib
+
+from models import Association, Nonce
+
+__all__ = ['OpenID', 'DjangoOpenIDStore', 'from_openid_response', 'clean_next']
+
+DEFAULT_NEXT = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+def clean_next(next):
+ if next is None:
+ return DEFAULT_NEXT
+ next = str_to_unicode(urllib.unquote(next), 'utf-8')
+ next = next.strip()
+ if next.startswith('/'):
+ return next
+ return DEFAULT_NEXT
+
+class OpenID:
+ def __init__(self, openid_, issued, attrs=None, sreg_=None):
+ self.openid = openid_
+ self.issued = issued
+ self.attrs = attrs or {}
+ self.sreg = sreg_ or {}
+ self.is_iname = (xri.identifierScheme(openid_) == 'XRI')
+
+ def __repr__(self):
+ return '' % self.openid
+
+ def __str__(self):
+ return self.openid
+
+class DjangoOpenIDStore(OpenIDStore):
+ def __init__(self):
+ self.max_nonce_age = 6 * 60 * 60 # Six hours
+
+ def storeAssociation(self, server_url, association):
+ assoc = Association(
+ server_url = server_url,
+ handle = association.handle,
+ secret = base64.encodestring(association.secret),
+ issued = association.issued,
+ lifetime = association.issued,
+ assoc_type = association.assoc_type
+ )
+ assoc.save()
+
+ def getAssociation(self, server_url, handle=None):
+ assocs = []
+ if handle is not None:
+ assocs = Association.objects.filter(
+ server_url = server_url, handle = handle
+ )
+ else:
+ assocs = Association.objects.filter(
+ server_url = server_url
+ )
+ if not assocs:
+ return None
+ associations = []
+ for assoc in assocs:
+ association = OIDAssociation(
+ assoc.handle, base64.decodestring(assoc.secret), assoc.issued,
+ assoc.lifetime, assoc.assoc_type
+ )
+ if association.getExpiresIn() == 0:
+ self.removeAssociation(server_url, assoc.handle)
+ else:
+ associations.append((association.issued, association))
+ if not associations:
+ return None
+ return associations[-1][1]
+
+ def removeAssociation(self, server_url, handle):
+ assocs = list(Association.objects.filter(
+ server_url = server_url, handle = handle
+ ))
+ assocs_exist = len(assocs) > 0
+ for assoc in assocs:
+ assoc.delete()
+ return assocs_exist
+
+ def useNonce(self, server_url, timestamp, salt):
+ if abs(timestamp - time.time()) > openid.store.nonce.SKEW:
+ return False
+
+ query = [
+ Q(server_url__exact=server_url),
+ Q(timestamp__exact=timestamp),
+ Q(salt__exact=salt),
+ ]
+ try:
+ ononce = Nonce.objects.get(reduce(operator.and_, query))
+ except Nonce.DoesNotExist:
+ ononce = Nonce(
+ server_url=server_url,
+ timestamp=timestamp,
+ salt=salt
+ )
+ ononce.save()
+ return True
+
+ ononce.delete()
+
+ return False
+
+ def cleanupNonce(self):
+ Nonce.objects.filter(timestamp 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
+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 import login, logout
+from django.contrib.auth.decorators import login_required
+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.contrib.sites.models import Site
+from django.utils.http import urlquote_plus
+from django.core.mail import send_mail
+
+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 django_authopenid.util import OpenID, DjangoOpenIDStore, from_openid_response, clean_next
+from django_authopenid.models import UserAssociation, UserPasswordQueue
+from django_authopenid.forms import OpenidSigninForm, OpenidAuthForm, OpenidRegisterForm, \
+ OpenidVerifyForm, RegistrationForm, ChangepwForm, ChangeemailForm, \
+ ChangeopenidForm, DeleteForm, EmailPasswordForm
+
+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鍦板潃锛 %s" % 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(clean_next(request.GET.get('next')))
+
+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():
+ next = request.GET.get("next", "/")
+ return HttpResponseRedirect(next)
+ return func(request, *args, **kwargs)
+ return decorated
+
+@not_authenticated
+def signin(request):
+ """
+ signin page. It manage the legacy authentification (user/password)
+ and authentification with openid.
+
+ url: /signin/
+
+ template : authopenid/signin.htm
+ """
+ request.encoding = 'UTF-8'
+ on_failure = signin_failure
+ next = clean_next(request.GET.get('next'))
+
+ form_signin = OpenidSigninForm(initial={'next':next})
+ form_auth = OpenidAuthForm(initial={'next':next})
+
+ if request.POST:
+
+ if 'bsignin' in request.POST.keys():
+
+ form_signin = OpenidSigninForm(request.POST)
+ if form_signin.is_valid():
+ next = clean_next(form_signin.cleaned_data.get('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)
+
+ elif 'blogin' in request.POST.keys():
+ # perform normal django authentification
+ form_auth = OpenidAuthForm(request.POST)
+ if form_auth.is_valid():
+ user_ = form_auth.get_user()
+ login(request, user_)
+ next = clean_next(form_auth.cleaned_data.get('next'))
+ return HttpResponseRedirect(next)
+
+
+ return render('authopenid/signin.html', {
+ 'form1': form_auth,
+ 'form2': form_signin,
+ 'msg': request.GET.get('msg',''),
+ 'sendpw_url': reverse('user_sendpw'),
+ }, 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)
+ 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_)
+
+ next = clean_next(request.GET.get('next'))
+ return HttpResponseRedirect(next)
+
+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
+ """
+
+ is_redirect = False
+ next = clean_next(request.GET.get('next'))
+ openid_ = request.session.get('openid', None)
+ if not openid_:
+ return HttpResponseRedirect(reverse('user_signin') + 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,
+ })
+
+ if request.POST:
+ just_completed = False
+ if 'bnewaccount' in request.POST.keys():
+ form1 = OpenidRegisterForm(request.POST)
+ if form1.is_valid():
+ next = clean_next(form1.cleaned_data.get('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)
+
+ # 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_)
+ elif 'bverify' in request.POST.keys():
+ form2 = OpenidVerifyForm(request.POST)
+ if form2.is_valid():
+ is_redirect = True
+ next = clean_next(form2.cleaned_data.get('next'))
+ user_ = form2.get_user()
+
+ uassoc = UserAssociation(openid_url=str(openid_),
+ user_id=user_.id)
+ uassoc.save()
+ login(request, user_)
+
+ # redirect, can redirect only if forms are valid.
+ if is_redirect:
+ return HttpResponseRedirect(next)
+
+ return render('authopenid/complete.html', {
+ 'form1': form1,
+ 'form2': form2,
+ 'nickname': nickname,
+ 'email': email
+ }, context_instance=RequestContext(request))
+
+def signin_failure(request, message):
+ """
+ falure with openid signin. Go back to signin page.
+
+ template : "authopenid/signin.html"
+ """
+ next = clean_next(request.GET.get('next'))
+ form_signin = OpenidSigninForm(initial={'next': next})
+ form_auth = OpenidAuthForm(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
+ """
+ action_signin = reverse('user_signin')
+ next = clean_next(request.GET.get('next'))
+ form = RegistrationForm(initial={'next':next})
+ form_signin = OpenidSigninForm(initial={'next':next})
+
+ if request.POST:
+ form = RegistrationForm(request.POST)
+ if form.is_valid():
+ next = clean_next(form.cleaned_data.get('next'))
+ user_ = User.objects.create_user( form.cleaned_data['username'],
+ form.cleaned_data['email'], form.cleaned_data['password1'])
+
+ user_.backend = "django.contrib.auth.backends.ModelBackend"
+ login(request, user_)
+
+ # send email
+ current_domain = Site.objects.get_current().domain
+ subject = _("Welcome")
+ message_template = loader.get_template(
+ 'authopenid/confirm_email.txt'
+ )
+ message_context = Context({
+ 'site_url': 'http://%s/' % current_domain,
+ 'username': form.cleaned_data['username'],
+ 'password': form.cleaned_data['password1']
+ })
+ message = message_template.render(message_context)
+ send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
+ [user_.email])
+
+ return HttpResponseRedirect(next)
+
+ return render('authopenid/signup.html', {
+ 'form': form,
+ 'form2': form_signin,
+ }, context_instance=RequestContext(request))
+
+@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
+ next = clean_next(request.GET.get('next'))
+ logout(request)
+
+ return HttpResponseRedirect(next)
+
+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 request.POST:
+ form = ChangepwForm(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 = ChangepwForm(user=user_)
+
+ return render('authopenid/changepw.html', {'form': form },
+ context_instance=RequestContext(request))
+
+@login_required
+def changeemail(request):
+ """
+ changeemail view. It require password or openid to allow change.
+
+ url: /changeemail/
+
+ template : authopenid/changeemail.html
+ """
+ msg = request.GET.get('msg', '')
+ extension_args = {}
+ user_ = request.user
+
+ redirect_to = get_url_host(request) + reverse('user_changeemail')
+
+ if request.POST:
+ form = ChangeemailForm(request.POST, user=user_)
+ if form.is_valid():
+ if not form.test_openid:
+ user_.email = form.cleaned_data['email']
+ user_.save()
+ msg = _("Email changed.")
+ redirect = "%s?msg=%s" % (reverse('user_account_settings'),
+ urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+ else:
+ request.session['new_email'] = form.cleaned_data['email']
+ return ask_openid(request, form.cleaned_data['password'],
+ redirect_to, on_failure=emailopenid_failure)
+ elif not request.POST and 'openid.mode' in request.GET:
+ return complete(request, emailopenid_success,
+ emailopenid_failure, redirect_to)
+ else:
+ form = ChangeemailForm(initial={'email': user_.email},
+ user=user_)
+
+ return render('authopenid/changeemail.html', {
+ 'form': form,
+ 'msg': msg
+ }, context_instance=RequestContext(request))
+
+
+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 = "/?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 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
+ """
+
+ 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
+ current_domain = Site.objects.get_current().domain
+ subject = _("Request for new password")
+ message_template = loader.get_template(
+ 'authopenid/sendpw_email.txt')
+ message_context = Context({
+ 'site_url': 'http://%s' % current_domain,
+ 'confirm_key': confirm_key,
+ 'username': form.user_cache.username,
+ 'password': new_pw,
+ 'url_confirm': reverse('user_confirmchangepw'),
+ })
+ message = message_template.render(message_context)
+ send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
+ [form.user_cache.email])
+ msg = _("A new password has been 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('/')
+
+ 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/forum/__init__.py b/forum/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/forum/admin.py b/forum/admin.py
new file mode 100644
index 00000000..438a99e7
--- /dev/null
+++ b/forum/admin.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+from django.contrib import admin
+from models import *
+
+
+class QuestionAdmin(admin.ModelAdmin):
+ """Question admin class"""
+
+class TagAdmin(admin.ModelAdmin):
+ """Tag admin class"""
+
+class Answerdmin(admin.ModelAdmin):
+ """Answer admin class"""
+
+class CommentAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class VoteAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class FlaggedItemAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class FavoriteQuestionAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class QuestionRevisionAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class AnswerRevisionAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class AwardAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class BadgeAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class ReputeAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class ActivityAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class BookAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class BookAuthorInfoAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class BookAuthorRssAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+
+admin.site.register(Question, QuestionAdmin)
+admin.site.register(Tag, TagAdmin)
+admin.site.register(Answer, Answerdmin)
+admin.site.register(Comment, CommentAdmin)
+admin.site.register(Vote, VoteAdmin)
+admin.site.register(FlaggedItem, FlaggedItemAdmin)
+admin.site.register(FavoriteQuestion, FavoriteQuestionAdmin)
+admin.site.register(QuestionRevision, QuestionRevisionAdmin)
+admin.site.register(AnswerRevision, AnswerRevisionAdmin)
+admin.site.register(Badge, BadgeAdmin)
+admin.site.register(Award, AwardAdmin)
+admin.site.register(Repute, ReputeAdmin)
+admin.site.register(Activity, ActivityAdmin)
+admin.site.register(Book, BookAdmin)
+admin.site.register(BookAuthorInfo, BookAuthorInfoAdmin)
+admin.site.register(BookAuthorRss, BookAuthorRssAdmin)
\ No newline at end of file
diff --git a/forum/auth.py b/forum/auth.py
new file mode 100644
index 00000000..0608031a
--- /dev/null
+++ b/forum/auth.py
@@ -0,0 +1,443 @@
+锘"""
+Authorisation related functions.
+
+The actions a User is authorised to perform are dependent on their reputation
+and superuser status.
+"""
+import datetime
+from django.contrib.contenttypes.models import ContentType
+from django.db import transaction
+from models import Repute
+from models import Question
+from models import Answer
+from const import TYPE_REPUTATION
+question_type = ContentType.objects.get_for_model(Question)
+answer_type = ContentType.objects.get_for_model(Answer)
+
+VOTE_UP = 15
+FLAG_OFFENSIVE = 15
+POST_IMAGES = 15
+LEAVE_COMMENTS = 50
+UPLOAD_FILES = 60
+VOTE_DOWN = 100
+CLOSE_OWN_QUESTIONS = 250
+RETAG_OTHER_QUESTIONS = 500
+REOPEN_OWN_QUESTIONS = 500
+EDIT_COMMUNITY_WIKI_POSTS = 750
+EDIT_OTHER_POSTS = 2000
+DELETE_COMMENTS = 2000
+VIEW_OFFENSIVE_FLAGS = 2000
+DISABLE_URL_NOFOLLOW = 2000
+CLOSE_OTHER_QUESTIONS = 3000
+LOCK_POSTS = 4000
+
+VOTE_RULES = {
+ 'scope_votes_per_user_per_day' : 30, # how many votes of one user has everyday
+ 'scope_flags_per_user_per_day' : 5, # how many times user can flag posts everyday
+ 'scope_warn_votes_left' : 10, # start when to warn user how many votes left
+ 'scope_deny_unvote_days' : 1, # if 1 days passed, user can't cancel votes.
+ 'scope_flags_invisible_main_page' : 3, # post doesn't show on main page if has more than 3 offensive flags
+ 'scope_flags_delete_post' : 5, # post will be deleted if it has more than 5 offensive flags
+}
+
+REPUTATION_RULES = {
+ 'initial_score' : 1,
+ 'scope_per_day_by_upvotes' : 200,
+ 'gain_by_upvoted' : 10,
+ 'gain_by_answer_accepted' : 15,
+ 'gain_by_accepting_answer' : 2,
+ 'gain_by_downvote_canceled' : 2,
+ 'gain_by_canceling_downvote' : 1,
+ 'lose_by_canceling_accepted_answer' : -2,
+ 'lose_by_accepted_answer_cancled' : -15,
+ 'lose_by_downvoted' : -2,
+ 'lose_by_flagged' : -2,
+ 'lose_by_downvoting' : -1,
+ 'lose_by_flagged_lastrevision_3_times': -30,
+ 'lose_by_flagged_lastrevision_5_times': -100,
+ 'lose_by_upvote_canceled' : -10,
+}
+
+def can_vote_up(user):
+ """Determines if a User can vote Questions and Answers up."""
+ return user.is_authenticated() and (
+ user.reputation >= VOTE_UP or
+ user.is_superuser)
+
+def can_flag_offensive(user):
+ """Determines if a User can flag Questions and Answers as offensive."""
+ return user.is_authenticated() and (
+ user.reputation >= FLAG_OFFENSIVE or
+ user.is_superuser)
+
+def can_add_comments(user):
+ """Determines if a User can add comments to Questions and Answers."""
+ return user.is_authenticated() and (
+ user.reputation >= LEAVE_COMMENTS or
+ user.is_superuser)
+
+def can_vote_down(user):
+ """Determines if a User can vote Questions and Answers down."""
+ return user.is_authenticated() and (
+ user.reputation >= VOTE_DOWN or
+ user.is_superuser)
+
+def can_retag_questions(user):
+ """Determines if a User can retag Questions."""
+ return user.is_authenticated() and (
+ RETAG_OTHER_QUESTIONS <= user.reputation < EDIT_OTHER_POSTS or
+ user.is_superuser)
+
+def can_edit_post(user, post):
+ """Determines if a User can edit the given Question or Answer."""
+ return user.is_authenticated() and (
+ user.id == post.author_id or
+ (post.wiki and user.reputation >= EDIT_COMMUNITY_WIKI_POSTS) or
+ user.reputation >= EDIT_OTHER_POSTS or
+ user.is_superuser)
+
+def can_delete_comment(user, comment):
+ """Determines if a User can delete the given Comment."""
+ return user.is_authenticated() and (
+ user.id == comment.user_id or
+ user.reputation >= DELETE_COMMENTS or
+ user.is_superuser)
+
+def can_view_offensive_flags(user):
+ """Determines if a User can view offensive flag counts."""
+ return user.is_authenticated() and (
+ user.reputation >= VIEW_OFFENSIVE_FLAGS or
+ user.is_superuser)
+
+def can_close_question(user, question):
+ """Determines if a User can close the given Question."""
+ return user.is_authenticated() and (
+ (user.id == question.author_id and
+ user.reputation >= CLOSE_OWN_QUESTIONS) or
+ user.reputation >= CLOSE_OTHER_QUESTIONS or
+ user.is_superuser)
+
+def can_lock_posts(user):
+ """Determines if a User can lock Questions or Answers."""
+ return user.is_authenticated() and (
+ user.reputation >= LOCK_POSTS or
+ user.is_superuser)
+
+def can_follow_url(user):
+ """Determines if the URL link can be followed by Google search engine."""
+ return user.reputation >= DISABLE_URL_NOFOLLOW
+
+def can_accept_answer(user, question, answer):
+ return (user.is_authenticated() and
+ question.author != answer.author and
+ question.author == user) or user.is_superuser
+
+# now only support to reopen own question except superuser
+def can_reopen_question(user, question):
+ return (user.is_authenticated() and
+ user.id == question.author_id and
+ user.reputation >= REOPEN_OWN_QUESTIONS) or user.is_superuser
+
+def can_delete_post(user, post):
+ return (user.is_authenticated() and
+ user.id == post.author_id) or user.is_superuser
+
+def can_view_deleted_post(user, post):
+ return user.is_superuser
+
+# user preferences view permissions
+def is_user_self(request_user, target_user):
+ return (request_user.is_authenticated() and request_user == target_user)
+
+def can_view_user_votes(request_user, target_user):
+ return (request_user.is_authenticated() and request_user == target_user)
+
+def can_view_user_preferences(request_user, target_user):
+ return (request_user.is_authenticated() and request_user == target_user)
+
+def can_view_user_edit(request_user, target_user):
+ return (request_user.is_authenticated() and request_user == target_user)
+
+def can_upload_files(request_user):
+ return (request_user.is_authenticated() and request_user.reputation >= UPLOAD_FILES) or \
+ request_user.is_superuser
+
+###########################################
+## actions and reputation changes event
+###########################################
+def calculate_reputation(origin, offset):
+ result = int(origin) + int(offset)
+ return result if result > 0 else 1
+
+@transaction.commit_on_success
+def onFlaggedItem(item, post, user):
+
+ item.save()
+ post.offensive_flag_count = post.offensive_flag_count + 1
+ post.save()
+
+ post.author.reputation = calculate_reputation(post.author.reputation,
+ int(REPUTATION_RULES['lose_by_flagged']))
+ post.author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=post.author,
+ negative=int(REPUTATION_RULES['lose_by_flagged']),
+ question=question, reputed_at=datetime.datetime.now(),
+ reputation_type=-4,
+ reputation=post.author.reputation)
+ reputation.save()
+
+ #todo: These should be updated to work on same revisions.
+ if post.offensive_flag_count == VOTE_RULES['scope_flags_invisible_main_page'] :
+ post.author.reputation = calculate_reputation(post.author.reputation,
+ int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']))
+ post.author.save()
+
+ reputation = Repute(user=post.author,
+ negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-6,
+ reputation=post.author.reputation)
+ reputation.save()
+
+ elif post.offensive_flag_count == VOTE_RULES['scope_flags_delete_post']:
+ post.author.reputation = calculate_reputation(post.author.reputation,
+ int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']))
+ post.author.save()
+
+ reputation = Repute(user=post.author,
+ negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-7,
+ reputation=post.author.reputation)
+ reputation.save()
+
+ post.deleted = True
+ #post.deleted_at = datetime.datetime.now()
+ #post.deleted_by = Admin
+ post.save()
+
+
+@transaction.commit_on_success
+def onAnswerAccept(answer, user):
+ answer.accepted = True
+ answer.accepted_at = datetime.datetime.now()
+ answer.question.answer_accepted = True
+ answer.save()
+ answer.question.save()
+
+ answer.author.reputation = calculate_reputation(answer.author.reputation,
+ int(REPUTATION_RULES['gain_by_answer_accepted']))
+ answer.author.save()
+ reputation = Repute(user=answer.author,
+ positive=int(REPUTATION_RULES['gain_by_answer_accepted']),
+ question=answer.question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=2,
+ reputation=answer.author.reputation)
+ reputation.save()
+
+ user.reputation = calculate_reputation(user.reputation,
+ int(REPUTATION_RULES['gain_by_accepting_answer']))
+ user.save()
+ reputation = Repute(user=user,
+ positive=int(REPUTATION_RULES['gain_by_accepting_answer']),
+ question=answer.question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=3,
+ reputation=user.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onAnswerAcceptCanceled(answer, user):
+ answer.accepted = False
+ answer.accepted_at = None
+ answer.question.answer_accepted = False
+ answer.save()
+ answer.question.save()
+
+ answer.author.reputation = calculate_reputation(answer.author.reputation,
+ int(REPUTATION_RULES['lose_by_accepted_answer_cancled']))
+ answer.author.save()
+ reputation = Repute(user=answer.author,
+ negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']),
+ question=answer.question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-2,
+ reputation=answer.author.reputation)
+ reputation.save()
+
+ user.reputation = calculate_reputation(user.reputation,
+ int(REPUTATION_RULES['lose_by_canceling_accepted_answer']))
+ user.save()
+ reputation = Repute(user=user,
+ negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']),
+ question=answer.question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-1,
+ reputation=user.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onUpVoted(vote, post, user):
+ vote.save()
+
+ post.vote_up_count = int(post.vote_up_count) + 1
+ post.score = int(post.score) + 1
+ post.save()
+
+ if not post.wiki:
+ author = post.author
+ if Repute.objects.get_reputation_by_upvoted_today(author) < int(REPUTATION_RULES['scope_per_day_by_upvotes']):
+ author.reputation = calculate_reputation(author.reputation,
+ int(REPUTATION_RULES['gain_by_upvoted']))
+ author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=author,
+ positive=int(REPUTATION_RULES['gain_by_upvoted']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=1,
+ reputation=author.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onUpVotedCanceled(vote, post, user):
+ vote.delete()
+
+ post.vote_up_count = int(post.vote_up_count) - 1
+ if post.vote_up_count < 0:
+ post.vote_up_count = 0
+ post.score = int(post.score) - 1
+ post.save()
+
+ if not post.wiki:
+ author = post.author
+ author.reputation = calculate_reputation(author.reputation,
+ int(REPUTATION_RULES['lose_by_upvote_canceled']))
+ author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=author,
+ negative=int(REPUTATION_RULES['lose_by_upvote_canceled']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-8,
+ reputation=author.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onDownVoted(vote, post, user):
+ vote.save()
+
+ post.vote_down_count = int(post.vote_down_count) + 1
+ post.score = int(post.score) - 1
+ post.save()
+
+ if not post.wiki:
+ author = post.author
+ author.reputation = calculate_reputation(author.reputation,
+ int(REPUTATION_RULES['lose_by_downvoted']))
+ author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=author,
+ negative=int(REPUTATION_RULES['lose_by_downvoted']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-3,
+ reputation=author.reputation)
+ reputation.save()
+
+ user.reputation = calculate_reputation(user.reputation,
+ int(REPUTATION_RULES['lose_by_downvoting']))
+ user.save()
+
+ reputation = Repute(user=user,
+ negative=int(REPUTATION_RULES['lose_by_downvoting']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-5,
+ reputation=user.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onDownVotedCanceled(vote, post, user):
+ vote.delete()
+
+ post.vote_down_count = int(post.vote_down_count) - 1
+ if post.vote_down_count < 0:
+ post.vote_down_count = 0
+ post.score = post.score + 1
+ post.save()
+
+ if not post.wiki:
+ author = post.author
+ author.reputation = calculate_reputation(author.reputation,
+ int(REPUTATION_RULES['gain_by_downvote_canceled']))
+ author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=author,
+ positive=int(REPUTATION_RULES['gain_by_downvote_canceled']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=4,
+ reputation=author.reputation)
+ reputation.save()
+
+ user.reputation = calculate_reputation(user.reputation,
+ int(REPUTATION_RULES['gain_by_canceling_downvote']))
+ user.save()
+
+ reputation = Repute(user=user,
+ positive=int(REPUTATION_RULES['gain_by_canceling_downvote']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=5,
+ reputation=user.reputation)
+ reputation.save()
+
+def onDeleteCanceled(post, user):
+ post.deleted = False
+ post.deleted_by = None
+ post.deleted_at = None
+ post.save()
+ for tag in list(post.tags.all()):
+ if tag.used_count == 1 and tag.deleted:
+ tag.deleted = False
+ tag.deleted_by = None
+ tag.deleted_at = None
+ tag.save()
+
+def onDeleted(post, user):
+ post.deleted = True
+ post.deleted_by = user
+ post.deleted_at = datetime.datetime.now()
+ post.save()
+
+ for tag in list(post.tags.all()):
+ if tag.used_count == 1:
+ tag.deleted = True
+ tag.deleted_by = user
+ tag.deleted_at = datetime.datetime.now()
+ tag.save()
diff --git a/forum/const.py b/forum/const.py
new file mode 100644
index 00000000..d285de7d
--- /dev/null
+++ b/forum/const.py
@@ -0,0 +1,89 @@
+锘# encoding:utf-8
+"""
+All constants could be used in other modules
+For reasons that models, views can't have unicode text in this project, all unicode text go here.
+"""
+CLOSE_REASONS = (
+ (1, u'瀹屽叏閲嶅鐨勯棶棰'),
+ (2, u'涓嶆槸缂栫▼鎶鏈棶棰'),
+ (3, u'澶富瑙傛с佸紩璧蜂簤鍚电殑闂'),
+ (4, u'涓嶆槸涓涓彲浠ュ洖绛旂殑鈥滈棶棰樷'),
+ (5, u'闂宸茬粡瑙e喅锛屽凡寰楀埌姝g‘绛旀'),
+ (6, u'宸茬粡杩囨椂銆佷笉鍙噸鐜扮殑闂'),
+ (7, u'澶眬閮ㄣ佹湰鍦板寲鐨勯棶棰'),
+ (8, u'鎭舵剰瑷璁'),
+ (9, u'鍨冨溇骞垮憡'),
+)
+
+TYPE_REPUTATION = (
+ (1, 'gain_by_upvoted'),
+ (2, 'gain_by_answer_accepted'),
+ (3, 'gain_by_accepting_answer'),
+ (4, 'gain_by_downvote_canceled'),
+ (5, 'gain_by_canceling_downvote'),
+ (-1, 'lose_by_canceling_accepted_answer'),
+ (-2, 'lose_by_accepted_answer_cancled'),
+ (-3, 'lose_by_downvoted'),
+ (-4, 'lose_by_flagged'),
+ (-5, 'lose_by_downvoting'),
+ (-6, 'lose_by_flagged_lastrevision_3_times'),
+ (-7, 'lose_by_flagged_lastrevision_5_times'),
+ (-8, 'lose_by_upvote_canceled'),
+)
+
+TYPE_ACTIVITY_ASK_QUESTION=1
+TYPE_ACTIVITY_ANSWER=2
+TYPE_ACTIVITY_COMMENT_QUESTION=3
+TYPE_ACTIVITY_COMMENT_ANSWER=4
+TYPE_ACTIVITY_UPDATE_QUESTION=5
+TYPE_ACTIVITY_UPDATE_ANSWER=6
+TYPE_ACTIVITY_PRIZE=7
+TYPE_ACTIVITY_MARK_ANSWER=8
+TYPE_ACTIVITY_VOTE_UP=9
+TYPE_ACTIVITY_VOTE_DOWN=10
+TYPE_ACTIVITY_CANCEL_VOTE=11
+TYPE_ACTIVITY_DELETE_QUESTION=12
+TYPE_ACTIVITY_DELETE_ANSWER=13
+TYPE_ACTIVITY_MARK_OFFENSIVE=14
+TYPE_ACTIVITY_UPDATE_TAGS=15
+TYPE_ACTIVITY_FAVORITE=16
+TYPE_ACTIVITY_USER_FULL_UPDATED = 17
+#TYPE_ACTIVITY_EDIT_QUESTION=17
+#TYPE_ACTIVITY_EDIT_ANSWER=18
+
+TYPE_ACTIVITY = (
+ (TYPE_ACTIVITY_ASK_QUESTION, u'鎻愰棶'),
+ (TYPE_ACTIVITY_ANSWER, u'鍥炵瓟'),
+ (TYPE_ACTIVITY_COMMENT_QUESTION, u'璇勮闂'),
+ (TYPE_ACTIVITY_COMMENT_ANSWER, u'璇勮鍥炵瓟'),
+ (TYPE_ACTIVITY_UPDATE_QUESTION, u'淇敼闂'),
+ (TYPE_ACTIVITY_UPDATE_ANSWER, u'淇敼鍥炵瓟'),
+ (TYPE_ACTIVITY_PRIZE, u'鑾峰'),
+ (TYPE_ACTIVITY_MARK_ANSWER, u'鏍囪鏈浣崇瓟妗'),
+ (TYPE_ACTIVITY_VOTE_UP, u'鎶曡禐鎴愮エ'),
+ (TYPE_ACTIVITY_VOTE_DOWN, u'鎶曞弽瀵圭エ'),
+ (TYPE_ACTIVITY_CANCEL_VOTE, u'鎾ら攢鎶曠エ'),
+ (TYPE_ACTIVITY_DELETE_QUESTION, u'鍒犻櫎闂'),
+ (TYPE_ACTIVITY_DELETE_ANSWER, u'鍒犻櫎鍥炵瓟'),
+ (TYPE_ACTIVITY_MARK_OFFENSIVE, u'鏍囪鍨冨溇甯'),
+ (TYPE_ACTIVITY_UPDATE_TAGS, u'鏇存柊鏍囩'),
+ (TYPE_ACTIVITY_FAVORITE, u'鏀惰棌'),
+ (TYPE_ACTIVITY_USER_FULL_UPDATED, u'瀹屾垚涓汉鎵鏈夎祫鏂'),
+ #(TYPE_ACTIVITY_EDIT_QUESTION, u'缂栬緫闂'),
+ #(TYPE_ACTIVITY_EDIT_ANSWER, u'缂栬緫绛旀'),
+)
+
+TYPE_RESPONSE = {
+ 'QUESTION_ANSWERED' : u'鍥炵瓟闂',
+ 'QUESTION_COMMENTED': u'闂璇勮',
+ 'ANSWER_COMMENTED' : u'鍥炵瓟璇勮',
+ 'ANSWER_ACCEPTED' : u'鏈浣崇瓟妗',
+}
+
+CONST = {
+ 'closed' : u' [宸插叧闂璢',
+ 'deleted' : u' [宸插垹闄',
+ 'default_version' : u'鍒濆鐗堟湰',
+ 'retagged' : u'鏇存柊浜嗘爣绛',
+
+}
diff --git a/forum/diff.py b/forum/diff.py
new file mode 100644
index 00000000..d741d788
--- /dev/null
+++ b/forum/diff.py
@@ -0,0 +1,66 @@
+#!/usr/bin/python2.2
+"""HTML Diff: http://www.aaronsw.com/2002/diff
+Rough code, badly documented. Send me comments and patches."""
+
+__author__ = 'Aaron Swartz '
+__copyright__ = '(C) 2003 Aaron Swartz. GNU GPL 2.'
+__version__ = '0.22'
+
+import difflib, string
+
+def isTag(x): return x[0] == "<" and x[-1] == ">"
+
+def textDiff(a, b):
+ """Takes in strings a and b and returns a human-readable HTML diff."""
+
+ out = []
+ a, b = html2list(a), html2list(b)
+ s = difflib.SequenceMatcher(None, a, b)
+ for e in s.get_opcodes():
+ if e[0] == "replace":
+ # @@ need to do something more complicated here
+ # call textDiff but not for html, but for some html... ugh
+ # gonna cop-out for now
+ out.append(''+''.join(a[e[1]:e[2]]) + ''+''.join(b[e[3]:e[4]])+"")
+ elif e[0] == "delete":
+ out.append(''+ ''.join(a[e[1]:e[2]]) + "")
+ elif e[0] == "insert":
+ out.append(''+''.join(b[e[3]:e[4]]) + "")
+ elif e[0] == "equal":
+ out.append(''.join(b[e[3]:e[4]]))
+ else:
+ raise "Um, something's broken. I didn't expect a '" + `e[0]` + "'."
+ return ''.join(out)
+
+def html2list(x, b=0):
+ mode = 'char'
+ cur = ''
+ out = []
+ for c in x:
+ if mode == 'tag':
+ if c == '>':
+ if b: cur += ']'
+ else: cur += c
+ out.append(cur); cur = ''; mode = 'char'
+ else: cur += c
+ elif mode == 'char':
+ if c == '<':
+ out.append(cur)
+ if b: cur = '['
+ else: cur = c
+ mode = 'tag'
+ elif c in string.whitespace: out.append(cur+c); cur = ''
+ else: cur += c
+ out.append(cur)
+ return filter(lambda x: x is not '', out)
+
+if __name__ == '__main__':
+ import sys
+ try:
+ a, b = sys.argv[1:3]
+ except ValueError:
+ print "htmldiff: highlight the differences between two html files"
+ print "usage: " + sys.argv[0] + " a b"
+ sys.exit(1)
+ print textDiff(open(a).read(), open(b).read())
+
diff --git a/forum/feed.py b/forum/feed.py
new file mode 100644
index 00000000..d75f3be6
--- /dev/null
+++ b/forum/feed.py
@@ -0,0 +1,41 @@
+锘#!/usr/bin/env python
+#encoding:utf-8
+#-------------------------------------------------------------------------------
+# Name: Syndication feed class for subsribtion
+# Purpose:
+#
+# Author: Mike
+#
+# Created: 29/01/2009
+# Copyright: (c) CNPROG.COM 2009
+# Licence: GPL V2
+#-------------------------------------------------------------------------------
+from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
+from models import Question
+class RssLastestQuestionsFeed(Feed):
+ title = u"CNProg绋嬪簭鍛橀棶绛旂ぞ鍖-鏈鏂伴棶棰"
+ link = u"http://www.cnprog.com/questions/"
+ description = u"涓浗绋嬪簭鍛樼殑缂栫▼鎶鏈棶绛旂ぞ鍖恒傛垜浠仛涓撲笟鐨勩佸彲鍗忎綔缂栬緫鐨勬妧鏈棶绛旂ぞ鍖恒"
+ #ttl = 10
+ copyright = u'Copyright(c)2009.CNPROG.COM'
+
+ def item_link(self, item):
+ return '/questions/%s/' % item.id
+
+ def item_author_name(self, item):
+ return item.author.username
+
+ def item_author_link(self, item):
+ return item.author.get_profile_url()
+
+ def item_pubdate(self, item):
+ return item.added_at
+
+ def items(self, item):
+ return Question.objects.filter(deleted=False).order_by('-added_at')[:30]
+
+def main():
+ pass
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/forum/forms.py b/forum/forms.py
new file mode 100644
index 00000000..70a44f28
--- /dev/null
+++ b/forum/forms.py
@@ -0,0 +1,194 @@
+锘縤mport re
+from datetime import date
+from django import forms
+from models import *
+from const import *
+
+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 = u'鏍囬'
+ self.help_text = u'璇疯緭鍏ュ闂鍏锋湁鎻忚堪鎬ц川鐨勬爣棰 - 鈥滃府蹇欙紒绱фユ眰鍔╋紒鈥濅笉鏄缓璁殑鎻愰棶鏂瑰紡銆'
+ self.initial = ''
+
+ def clean(self, value):
+ if len(value) < 10:
+ raise forms.ValidationError(u"鏍囬鐨勯暱搴﹀繀椤诲ぇ浜10")
+
+ 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 = u'鍐呭'
+ self.help_text = u''
+ self.initial = ''
+
+ def clean(self, value):
+ if len(value) < 10:
+ raise forms.ValidationError(u"鍐呭鑷冲皯瑕10涓瓧绗")
+
+ 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 = u'鏍囩'
+ self.help_text = u'澶氫釜鏍囩璇风敤绌烘牸闂撮殧-鏈澶5涓爣绛俱傦紙浼樺厛浣跨敤鑷姩鍖归厤鐨勮嫳鏂囨爣绛俱傦級'
+ self.initial = ''
+
+ def clean(self, value):
+ value = super(TagNamesField, self).clean(value)
+ data = value.strip()
+ if len(data) < 1:
+ raise forms.ValidationError(u'鏍囩涓嶈兘涓虹┖')
+ list = data.split(' ')
+ list_temp = []
+ if len(list) > 5:
+ raise forms.ValidationError(u'鏈澶氬彧鑳芥湁5涓爣绛')
+ for tag in list:
+ if len(tag) > 20:
+ raise forms.ValidationError(u'姣忎釜鏍囩鐨勯暱搴︿笉瓒呰繃20')
+
+ #TODO: regex match not allowed characters here
+
+ if tag.find('/') > -1 or tag.find('\\') > -1 or tag.find('<') > -1 or tag.find('>') > -1 or tag.find('&') > -1 or tag.find('\'') > -1 or tag.find('"') > -1:
+ #if not tagname_re.match(tag):
+ raise forms.ValidationError(u'鏍囩璇蜂娇鐢ㄨ嫳鏂囧瓧姣嶏紝涓枃鎴栬呮暟瀛楀瓧绗︿覆锛. - _ # 涔熷彲浠ワ級')
+ # 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 = u'绀惧尯wiki妯″紡'
+ self.help_text = u'閫夋嫨绀惧尯wiki妯″紡锛岄棶绛斾笉璁$畻绉垎锛岀鍚嶄篃涓嶆樉绀轰綔鑰呬俊鎭'
+
+
+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 = u'鏇存柊姒傝锛'
+ self.help_text = u'杈撳叆鏈淇敼鐨勭畝鍗曟杩帮紙濡傦細淇敼浜嗗埆瀛楋紝淇浜嗚娉曪紝鏀硅繘浜嗘牱寮忕瓑銆傞潪蹇呭~椤广傦級'
+
+class AskForm(forms.Form):
+ title = TitleField()
+ text = EditorField()
+ tags = TagNamesField()
+ wiki = WikiField()
+
+ openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
+ user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+
+
+
+class AnswerForm(forms.Form):
+ text = EditorField()
+ wiki = WikiField()
+ openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
+ user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ def __init__(self, question, *args, **kwargs):
+ super(AnswerForm, self).__init__(*args, **kwargs)
+ if question.wiki:
+ self.fields['wiki'].initial = True
+
+class CloseForm(forms.Form):
+ reason = forms.ChoiceField(choices=CLOSE_REASONS)
+
+class RetagQuestionForm(forms.Form):
+ tags = TagNamesField()
+ # initialize the default values
+ def __init__(self, question, *args, **kwargs):
+ super(RetagQuestionForm, self).__init__(*args, **kwargs)
+ self.fields['tags'].initial = question.tagnames
+
+class RevisionForm(forms.Form):
+ """
+ Lists revisions of a Question or Answer
+ """
+ revision = forms.ChoiceField(widget=forms.Select(attrs={'style' : 'width:520px'}))
+
+ def __init__(self, post, latest_revision, *args, **kwargs):
+ super(RevisionForm, self).__init__(*args, **kwargs)
+ revisions = post.revisions.all().values_list(
+ 'revision', 'author__username', 'revised_at', 'summary')
+ date_format = '%c'
+ self.fields['revision'].choices = [
+ (r[0], u'%s - %s (%s) %s' % (r[0], r[1], r[2].strftime(date_format), r[3]))
+ for r in revisions]
+ self.fields['revision'].initial = latest_revision.revision
+
+class EditQuestionForm(forms.Form):
+ title = TitleField()
+ text = EditorField()
+ tags = TagNamesField()
+ summary = SummaryField()
+
+ def __init__(self, question, revision, *args, **kwargs):
+ super(EditQuestionForm, self).__init__(*args, **kwargs)
+ self.fields['title'].initial = revision.title
+ self.fields['text'].initial = revision.text
+ self.fields['tags'].initial = revision.tagnames
+ # Once wiki mode is enabled, it can't be disabled
+ if not question.wiki:
+ self.fields['wiki'] = WikiField()
+
+class EditAnswerForm(forms.Form):
+ text = EditorField()
+ summary = SummaryField()
+
+ def __init__(self, answer, revision, *args, **kwargs):
+ super(EditAnswerForm, self).__init__(*args, **kwargs)
+ self.fields['text'].initial = revision.text
+
+class EditUserForm(forms.Form):
+ email = forms.EmailField(label=u'Email', help_text=u'涓嶄細鍏紑锛岀敤浜庡ご鍍忔樉绀烘湇鍔', required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ realname = forms.CharField(label=u'鐪熷疄濮撳悕', required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ website = forms.URLField(label=u'涓汉缃戠珯', required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ city = forms.CharField(label=u'鍩庡競', required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ birthday = forms.DateField(label=u'鐢熸棩', help_text=u'涓嶄細鍏紑锛屽彧浼氭樉绀烘偍鐨勫勾榫勶紝鏍煎紡涓猴細YYYY-MM-DD', required=True, widget=forms.TextInput(attrs={'size' : 35}))
+ about = forms.CharField(label=u'涓汉绠浠', required=False, widget=forms.Textarea(attrs={'cols' : 60}))
+
+ def __init__(self, user, *args, **kwargs):
+ super(EditUserForm, self).__init__(*args, **kwargs)
+ self.fields['email'].initial = user.email
+ self.fields['realname'].initial = user.real_name
+ self.fields['website'].initial = user.website
+ self.fields['city'].initial = user.location
+
+ if user.date_of_birth is not None:
+ self.fields['birthday'].initial = user.date_of_birth.date()
+ else:
+ self.fields['birthday'].initial = '1990-01-01'
+ self.fields['about'].initial = user.about
+ self.user = user
+
+ def clean_email(self):
+ """For security reason one unique email in database"""
+ if self.user.email != self.cleaned_data['email']:
+ if 'email' in self.cleaned_data:
+ try:
+ user = User.objects.get(email = self.cleaned_data['email'])
+ except User.DoesNotExist:
+ return self.cleaned_data['email']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'璇ョ數瀛愰偖浠跺凡琚敞鍐岋紝璇烽夋嫨鍙︿竴涓啀璇曘')
+ raise forms.ValidationError("璇ョ數瀛愰偖浠跺笎鍙峰凡琚敞鍐岋紝璇烽夋嫨鍙︿竴涓啀璇曘")
+ else:
+ return self.cleaned_data['email']
\ No newline at end of file
diff --git a/forum/management/__init__.py b/forum/management/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/forum/management/commands/__init__.py b/forum/management/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/forum/management/commands/base_command.py b/forum/management/commands/base_command.py
new file mode 100644
index 00000000..c073bf7a
--- /dev/null
+++ b/forum/management/commands/base_command.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+#encoding:utf-8
+#-------------------------------------------------------------------------------
+# Name: Award badges command
+# Purpose: This is a command file croning in background process regularly to
+# query database and award badges for user's special acitivities.
+#
+# Author: Mike, Sailing
+#
+# Created: 22/01/2009
+# Copyright: (c) Mike 2009
+# Licence: GPL V2
+#-------------------------------------------------------------------------------
+
+from datetime import datetime, date
+from django.core.management.base import NoArgsCommand
+from django.db import connection
+from django.shortcuts import get_object_or_404
+from django.contrib.contenttypes.models import ContentType
+
+from forum.models import *
+from forum.const import *
+
+class BaseCommand(NoArgsCommand):
+ def update_activities_auditted(self, cursor, activity_ids):
+ # update processed rows to auditted
+ if len(activity_ids):
+ query = "UPDATE activity SET is_auditted = 1 WHERE id in (%s)"\
+ % ','.join('%s' % item for item in activity_ids)
+ cursor.execute(query)
+
+
+
+
+
diff --git a/forum/management/commands/clean_award_badges.py b/forum/management/commands/clean_award_badges.py
new file mode 100644
index 00000000..df3d2917
--- /dev/null
+++ b/forum/management/commands/clean_award_badges.py
@@ -0,0 +1,58 @@
+#-------------------------------------------------------------------------------
+# Name: Award badges command
+# Purpose: This is a command file croning in background process regularly to
+# query database and award badges for user's special acitivities.
+#
+# Author: Mike
+#
+# Created: 18/01/2009
+# Copyright: (c) Mike 2009
+# Licence: GPL V2
+#-------------------------------------------------------------------------------
+#!/usr/bin/env python
+#encoding:utf-8
+from django.core.management.base import NoArgsCommand
+from django.db import connection
+from django.shortcuts import get_object_or_404
+from django.contrib.contenttypes.models import ContentType
+
+from forum.models import *
+
+class Command(NoArgsCommand):
+ def handle_noargs(self, **options):
+ try:
+ self.clean_awards()
+ except Exception, e:
+ print e
+ finally:
+ connection.close()
+
+ def clean_awards(self):
+ Award.objects.all().delete()
+
+ award_type =ContentType.objects.get_for_model(Award)
+ Activity.objects.filter(content_type=award_type).delete()
+
+ for user in User.objects.all():
+ user.gold = 0
+ user.silver = 0
+ user.bronze = 0
+ user.save()
+
+ for badge in Badge.objects.all():
+ badge.awarded_count = 0
+ badge.save()
+
+ query = "UPDATE activity SET is_auditted = 0"
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ finally:
+ cursor.close()
+ connection.close()
+
+def main():
+ pass
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/forum/management/commands/multi_award_badges.py b/forum/management/commands/multi_award_badges.py
new file mode 100644
index 00000000..723a8cec
--- /dev/null
+++ b/forum/management/commands/multi_award_badges.py
@@ -0,0 +1,347 @@
+#!/usr/bin/env python
+#encoding:utf-8
+#-------------------------------------------------------------------------------
+# Name: Award badges command
+# Purpose: This is a command file croning in background process regularly to
+# query database and award badges for user's special acitivities.
+#
+# Author: Mike, Sailing
+#
+# Created: 22/01/2009
+# Copyright: (c) Mike 2009
+# Licence: GPL V2
+#-------------------------------------------------------------------------------
+
+from datetime import datetime, date
+from django.core.management.base import NoArgsCommand
+from django.db import connection
+from django.shortcuts import get_object_or_404
+from django.contrib.contenttypes.models import ContentType
+
+from forum.models import *
+from forum.const import *
+from base_command import BaseCommand
+"""
+(1, '鐐肩嫳娉曞笀', 3, '鐐肩嫳娉曞笀', '鍒犻櫎鑷繁鏈3涓互涓婅禐鎴愮エ鐨勫笘瀛', 1, 0),
+(2, '鍘嬪姏鐧介', 3, '鍘嬪姏鐧介', '鍒犻櫎鑷繁鏈3涓互涓婂弽瀵圭エ鐨勫笘瀛', 1, 0),
+(3, '浼樼鍥炵瓟', 3, '浼樼鍥炵瓟', '鍥炵瓟濂借瘎10娆′互涓', 1, 0),
+(4, '浼樼闂', 3, '浼樼闂', '闂濂借瘎10娆′互涓', 1, 0),
+(5, '璇勮瀹', 3, '璇勮瀹', '璇勮10娆′互涓', 0, 0),
+(6, '娴佽闂', 3, '娴佽闂', '闂鐨勬祻瑙堥噺瓒呰繃1000浜烘', 1, 0),
+(7, '宸¢诲叺', 3, '宸¢诲叺', '绗竴娆℃爣璁板瀮鍦惧笘瀛', 0, 0),
+(8, '娓呮磥宸', 3, '娓呮磥宸', '绗竴娆℃挙閿鎶曠エ', 0, 0),
+(9, '鎵硅瘎瀹', 3, '鎵硅瘎瀹', '绗竴娆″弽瀵圭エ', 0, 0),
+(10, '灏忕紪', 3, '灏忕紪', '绗竴娆$紪杈戞洿鏂', 0, 0),
+(11, '鏉戦暱', 3, '鏉戦暱', '绗竴娆¢噸鏂版爣绛', 0, 0),
+(12, '瀛﹁', 3, '瀛﹁', '绗竴娆℃爣璁扮瓟妗', 0, 0),
+(13, '瀛︾敓', 3, '瀛︾敓', '绗竴娆℃彁闂苟涓旀湁涓娆′互涓婅禐鎴愮エ', 0, 0),
+(14, '鏀寔鑰', 3, '鏀寔鑰', '绗竴娆¤禐鎴愮エ', 0, 0),
+(15, '鏁欏笀', 3, '鏁欏笀', '绗竴娆″洖绛旈棶棰樺苟涓斿緱鍒颁竴涓互涓婅禐鎴愮エ', 0, 0),
+(16, '鑷紶浣滆', 3, '鑷紶浣滆', '瀹屾暣濉啓鐢ㄦ埛璧勬枡鎵鏈夐夐」', 0, 0),
+(17, '鑷鎴愭墠', 3, '鑷鎴愭墠', '鍥炵瓟鑷繁鐨勯棶棰樺苟涓旀湁3涓互涓婅禐鎴愮エ', 1, 0),
+(18, '鏈鏈変环鍊煎洖绛', 1, '鏈鏈変环鍊煎洖绛', '鍥炵瓟瓒呰繃100娆¤禐鎴愮エ', 1, 0),
+(19, '鏈鏈変环鍊奸棶棰', 1, '鏈鏈変环鍊奸棶棰', '闂瓒呰繃100娆¤禐鎴愮エ', 1, 0),
+(20, '涓囦汉杩', 1, '涓囦汉杩', '闂琚100浜轰互涓婃敹钘', 1, 0),
+(21, '钁楀悕闂', 1, '钁楀悕闂', '闂鐨勬祻瑙堥噺瓒呰繃10000浜烘', 1, 0),
+(22, 'alpha鐢ㄦ埛', 2, 'alpha鐢ㄦ埛', '鍐呮祴鏈熼棿鐨勬椿璺冪敤鎴', 0, 0),
+(23, '鏋佸ソ鍥炵瓟', 2, '鏋佸ソ鍥炵瓟', '鍥炵瓟瓒呰繃25娆¤禐鎴愮エ', 1, 0),
+(24, '鏋佸ソ闂', 2, '鏋佸ソ闂', '闂瓒呰繃25娆¤禐鎴愮エ', 1, 0),
+(25, '鍙楁杩庨棶棰', 2, '鍙楁杩庨棶棰', '闂琚25浜轰互涓婃敹钘', 1, 0),
+(26, '浼樼甯傛皯', 2, '浼樼甯傛皯', '鎶曠エ300娆′互涓', 0, 0),
+(27, '缂栬緫涓讳换', 2, '缂栬緫涓讳换', '缂栬緫浜100涓笘瀛', 0, 0),
+(28, '閫氭墠', 2, '閫氭墠', '鍦ㄥ涓爣绛鹃鍩熸椿璺', 0, 0),
+(29, '涓撳', 2, '涓撳', '鍦ㄤ竴涓爣绛鹃鍩熸椿璺冨嚭浼', 0, 0),
+(30, '鑰侀笩', 2, '鑰侀笩', '娲昏穬瓒呰繃涓骞寸殑鐢ㄦ埛', 0, 0),
+(31, '鏈鍙楀叧娉ㄩ棶棰', 2, '鏈鍙楀叧娉ㄩ棶棰', '闂鐨勬祻瑙堥噺瓒呰繃2500浜烘', 1, 0),
+(32, '瀛﹂棶瀹', 2, '瀛﹂棶瀹', '绗竴娆″洖绛旇鎶曡禐鎴愮エ10娆′互涓', 0, 0),
+(33, 'beta鐢ㄦ埛', 2, 'beta鐢ㄦ埛', 'beta鏈熼棿娲昏穬鍙備笌', 0, 0),
+(34, '瀵煎笀', 2, '瀵煎笀', '琚寚瀹氫负鏈浣崇瓟妗堝苟涓旇禐鎴愮エ40浠ヤ笂', 1, 0),
+(35, '宸笀', 2, '宸笀', '鍦ㄦ彁闂60澶╀箣鍚庡洖绛斿苟涓旇禐鎴愮エ5娆′互涓', 1, 0),
+(36, '鍒嗙被涓撳', 2, '鍒嗙被涓撳', '鍒涘缓鐨勬爣绛捐50涓互涓婇棶棰樹娇鐢', 1, 0);
+
+
+TYPE_ACTIVITY_ASK_QUESTION=1
+TYPE_ACTIVITY_ANSWER=2
+TYPE_ACTIVITY_COMMENT_QUESTION=3
+TYPE_ACTIVITY_COMMENT_ANSWER=4
+TYPE_ACTIVITY_UPDATE_QUESTION=5
+TYPE_ACTIVITY_UPDATE_ANSWER=6
+TYPE_ACTIVITY_PRIZE=7
+TYPE_ACTIVITY_MARK_ANSWER=8
+TYPE_ACTIVITY_VOTE_UP=9
+TYPE_ACTIVITY_VOTE_DOWN=10
+TYPE_ACTIVITY_CANCEL_VOTE=11
+TYPE_ACTIVITY_DELETE_QUESTION=12
+TYPE_ACTIVITY_DELETE_ANSWER=13
+TYPE_ACTIVITY_MARK_OFFENSIVE=14
+TYPE_ACTIVITY_UPDATE_TAGS=15
+TYPE_ACTIVITY_FAVORITE=16
+TYPE_ACTIVITY_USER_FULL_UPDATED = 17
+"""
+
+class Command(BaseCommand):
+ def handle_noargs(self, **options):
+ try:
+ self.delete_question_be_voted_up_3()
+ self.delete_answer_be_voted_up_3()
+ self.delete_question_be_vote_down_3()
+ self.delete_answer_be_voted_down_3()
+ self.answer_be_voted_up_10()
+ self.question_be_voted_up_10()
+ self.question_view_1000()
+ self.answer_self_question_be_voted_up_3()
+ self.answer_be_voted_up_100()
+ self.question_be_voted_up_100()
+ self.question_be_favorited_100()
+ self.question_view_10000()
+ self.answer_be_voted_up_25()
+ self.question_be_voted_up_25()
+ self.question_be_favorited_25()
+ self.question_view_2500()
+ self.answer_be_accepted_and_voted_up_40()
+ self.question_be_answered_after_60_days_and_be_voted_up_5()
+ self.created_tag_be_used_in_question_50()
+ except Exception, e:
+ print e
+ finally:
+ connection.close()
+
+ def delete_question_be_voted_up_3(self):
+ """
+ (1, '鐐肩嫳娉曞笀', 3, '鐐肩嫳娉曞笀', '鍒犻櫎鑷繁鏈3涓互涓婅禐鎴愮エ鐨勫笘瀛', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM activity act, question q WHERE act.object_id = q.id AND\
+ act.activity_type = %s AND\
+ q.vote_up_count >=3 AND \
+ act.is_auditted = 0" % (TYPE_ACTIVITY_DELETE_QUESTION)
+ self.__process_activities_badge(query, 1, Question)
+
+ def delete_answer_be_voted_up_3(self):
+ """
+ (1, '鐐肩嫳娉曞笀', 3, '鐐肩嫳娉曞笀', '鍒犻櫎鑷繁鏈3涓互涓婅禐鎴愮エ鐨勫笘瀛', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM activity act, answer an WHERE act.object_id = an.id AND\
+ act.activity_type = %s AND\
+ an.vote_up_count >=3 AND \
+ act.is_auditted = 0" % (TYPE_ACTIVITY_DELETE_ANSWER)
+ self.__process_activities_badge(query, 1, Answer)
+
+ def delete_question_be_vote_down_3(self):
+ """
+ (2, '鍘嬪姏鐧介', 3, '鍘嬪姏鐧介', '鍒犻櫎鑷繁鏈3涓互涓婂弽瀵圭エ鐨勫笘瀛', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM activity act, question q WHERE act.object_id = q.id AND\
+ act.activity_type = %s AND\
+ q.vote_down_count >=3 AND \
+ act.is_auditted = 0" % (TYPE_ACTIVITY_DELETE_QUESTION)
+ content_type = ContentType.objects.get_for_model(Question)
+ self.__process_activities_badge(query, 2, Question)
+
+ def delete_answer_be_voted_down_3(self):
+ """
+ (2, '鍘嬪姏鐧介', 3, '鍘嬪姏鐧介', '鍒犻櫎鑷繁鏈3涓互涓婂弽瀵圭エ鐨勫笘瀛', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM activity act, answer an WHERE act.object_id = an.id AND\
+ act.activity_type = %s AND\
+ an.vote_down_count >=3 AND \
+ act.is_auditted = 0" % (TYPE_ACTIVITY_DELETE_ANSWER)
+ self.__process_activities_badge(query, 2, Answer)
+
+ def answer_be_voted_up_10(self):
+ """
+ (3, '浼樼鍥炵瓟', 3, '浼樼鍥炵瓟', '鍥炵瓟濂借瘎10娆′互涓', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM \
+ activity act, answer a WHERE act.object_id = a.id AND\
+ act.activity_type = %s AND \
+ a.vote_up_count >= 10 AND\
+ act.is_auditted = 0" % (TYPE_ACTIVITY_ANSWER)
+ self.__process_activities_badge(query, 3, Answer)
+
+ def question_be_voted_up_10(self):
+ """
+ (4, '浼樼闂', 3, '浼樼闂', '闂濂借瘎10娆′互涓', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM \
+ activity act, question q WHERE act.object_id = q.id AND\
+ act.activity_type = %s AND \
+ q.vote_up_count >= 10 AND\
+ act.is_auditted = 0" % (TYPE_ACTIVITY_ASK_QUESTION)
+ self.__process_activities_badge(query, 4, Question)
+
+ def question_view_1000(self):
+ """
+ (6, '娴佽闂', 3, '娴佽闂', '闂鐨勬祻瑙堥噺瓒呰繃1000浜烘', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM \
+ activity act, question q WHERE act.activity_type = %s AND\
+ act.object_id = q.id AND \
+ q.view_count >= 1000 AND\
+ act.object_id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 6)
+ self.__process_activities_badge(query, 6, Question, False)
+
+ def answer_self_question_be_voted_up_3(self):
+ """
+ (17, '鑷鎴愭墠', 3, '鑷鎴愭墠', '鍥炵瓟鑷繁鐨勯棶棰樺苟涓旀湁3涓互涓婅禐鎴愮エ', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM \
+ activity act, answer an WHERE act.activity_type = %s AND\
+ act.object_id = an.id AND\
+ an.vote_up_count >= 3 AND\
+ act.user_id = (SELECT user_id FROM question q WHERE q.id = an.question_id) AND\
+ act.object_id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 17)
+ self.__process_activities_badge(query, 17, Question, False)
+
+ def answer_be_voted_up_100(self):
+ """
+ (18, '鏈鏈変环鍊煎洖绛', 1, '鏈鏈変环鍊煎洖绛', '鍥炵瓟瓒呰繃100娆¤禐鎴愮エ', 1, 0),
+ """
+ query = "SELECT an.id, an.author_id FROM answer an WHERE an.vote_up_count >= 100 AND an.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (18)
+
+ self.__process_badge(query, 18, Answer)
+
+ def question_be_voted_up_100(self):
+ """
+ (19, '鏈鏈変环鍊奸棶棰', 1, '鏈鏈変环鍊奸棶棰', '闂瓒呰繃100娆¤禐鎴愮エ', 1, 0),
+ """
+ query = "SELECT q.id, q.author_id FROM question q WHERE q.vote_up_count >= 100 AND q.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (19)
+
+ self.__process_badge(query, 19, Question)
+
+ def question_be_favorited_100(self):
+ """
+ (20, '涓囦汉杩', 1, '涓囦汉杩', '闂琚100浜轰互涓婃敹钘', 1, 0),
+ """
+ query = "SELECT q.id, q.author_id FROM question q WHERE q.favourite_count >= 100 AND q.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (20)
+
+ self.__process_badge(query, 20, Question)
+
+ def question_view_10000(self):
+ """
+ (21, '钁楀悕闂', 1, '钁楀悕闂', '闂鐨勬祻瑙堥噺瓒呰繃10000浜烘', 1, 0),
+ """
+ query = "SELECT q.id, q.author_id FROM question q WHERE q.view_count >= 10000 AND q.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (21)
+
+ self.__process_badge(query, 21, Question)
+
+ def answer_be_voted_up_25(self):
+ """
+ (23, '鏋佸ソ鍥炵瓟', 2, '鏋佸ソ鍥炵瓟', '鍥炵瓟瓒呰繃25娆¤禐鎴愮エ', 1, 0),
+ """
+ query = "SELECT a.id, a.author_id FROM answer a WHERE a.vote_up_count >= 25 AND a.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (23)
+
+ self.__process_badge(query, 23, Answer)
+
+ def question_be_voted_up_25(self):
+ """
+ (24, '鏋佸ソ闂', 2, '鏋佸ソ闂', '闂瓒呰繃25娆¤禐鎴愮エ', 1, 0),
+ """
+ query = "SELECT q.id, q.author_id FROM question q WHERE q.vote_up_count >= 25 AND q.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (24)
+
+ self.__process_badge(query, 24, Question)
+
+ def question_be_favorited_25(self):
+ """
+ (25, '鍙楁杩庨棶棰', 2, '鍙楁杩庨棶棰', '闂琚25浜轰互涓婃敹钘', 1, 0),
+ """
+ query = "SELECT q.id, q.author_id FROM question q WHERE q.favourite_count >= 25 AND q.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (25)
+
+ self.__process_badge(query, 25, Question)
+
+ def question_view_2500(self):
+ """
+ (31, '鏈鍙楀叧娉ㄩ棶棰', 2, '鏈鍙楀叧娉ㄩ棶棰', '闂鐨勬祻瑙堥噺瓒呰繃2500浜烘', 1, 0),
+ """
+ query = "SELECT q.id, q.author_id FROM question q WHERE q.view_count >= 2500 AND q.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (31)
+
+ self.__process_badge(query, 31, Question)
+
+ def answer_be_accepted_and_voted_up_40(self):
+ """
+ (34, '瀵煎笀', 2, '瀵煎笀', '琚寚瀹氫负鏈浣崇瓟妗堝苟涓旇禐鎴愮エ40浠ヤ笂', 1, 0),
+ """
+ query = "SELECT a.id, a.author_id FROM answer a WHERE a.vote_up_count >= 40 AND\
+ a.accepted = 1 AND\
+ a.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (34)
+
+ self.__process_badge(query, 34, Answer)
+
+ def question_be_answered_after_60_days_and_be_voted_up_5(self):
+ """
+ (35, '宸笀', 2, '宸笀', '鍦ㄦ彁闂60澶╀箣鍚庡洖绛斿苟涓旇禐鎴愮エ5娆′互涓', 1, 0),
+ """
+ query = "SELECT a.id, a.author_id FROM question q, answer a WHERE q.id = a.question_id AND\
+ DATEDIFF(a.added_at, q.added_at) >= 60 AND\
+ a.vote_up_count >= 5 AND \
+ a.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (35)
+
+ self.__process_badge(query, 35, Answer)
+
+ def created_tag_be_used_in_question_50(self):
+ """
+ (36, '鍒嗙被涓撳', 2, '鍒嗙被涓撳', '鍒涘缓鐨勬爣绛捐50涓互涓婇棶棰樹娇鐢', 1, 0);
+ """
+ query = "SELECT t.id, t.created_by_id FROM tag t, auth_user u WHERE t.created_by_id = u.id AND \
+ t. used_count >= 50 AND \
+ t.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (36)
+
+ self.__process_badge(query, 36, Tag)
+
+ def __process_activities_badge(self, query, badge, content_object, update_auditted=True):
+ content_type = ContentType.objects.get_for_model(content_object)
+
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ if update_auditted:
+ activity_ids = []
+ badge = get_object_or_404(Badge, id=badge)
+ for row in rows:
+ activity_id = row[0]
+ user_id = row[1]
+ object_id = row[2]
+
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=objet_id)
+ award.save()
+
+ if update_auditted:
+ activity_ids.append(activity_id)
+
+ if update_auditted:
+ self.update_activities_auditted(cursor, activity_ids)
+ finally:
+ cursor.close()
+
+ def __process_badge(self, query, badge, content_object):
+ content_type = ContentType.objects.get_for_model(Answer)
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ badge = get_object_or_404(Badge, id=badge)
+ for row in rows:
+ object_id = row[0]
+ user_id = row[1]
+
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+ finally:
+ cursor.close()
\ No newline at end of file
diff --git a/forum/management/commands/once_award_badges.py b/forum/management/commands/once_award_badges.py
new file mode 100644
index 00000000..c1a147d7
--- /dev/null
+++ b/forum/management/commands/once_award_badges.py
@@ -0,0 +1,350 @@
+锘#!/usr/bin/env python
+#encoding:utf-8
+#-------------------------------------------------------------------------------
+# Name: Award badges command
+# Purpose: This is a command file croning in background process regularly to
+# query database and award badges for user's special acitivities.
+#
+# Author: Mike, Sailing
+#
+# Created: 18/01/2009
+# Copyright: (c) Mike 2009
+# Licence: GPL V2
+#-------------------------------------------------------------------------------
+
+from datetime import datetime, date
+from django.db import connection
+from django.shortcuts import get_object_or_404
+from django.contrib.contenttypes.models import ContentType
+
+from forum.models import *
+from forum.const import *
+from base_command import BaseCommand
+"""
+(1, '鐐肩嫳娉曞笀', 3, '鐐肩嫳娉曞笀', '鍒犻櫎鑷繁鏈3涓互涓婅禐鎴愮エ鐨勫笘瀛', 1, 0),
+(2, '鍘嬪姏鐧介', 3, '鍘嬪姏鐧介', '鍒犻櫎鑷繁鏈3涓互涓婂弽瀵圭エ鐨勫笘瀛', 1, 0),
+(3, '浼樼鍥炵瓟', 3, '浼樼鍥炵瓟', '鍥炵瓟濂借瘎10娆′互涓', 1, 0),
+(4, '浼樼闂', 3, '浼樼闂', '闂濂借瘎10娆′互涓', 1, 0),
+(5, '璇勮瀹', 3, '璇勮瀹', '璇勮10娆′互涓', 0, 0),
+(6, '娴佽闂', 3, '娴佽闂', '闂鐨勬祻瑙堥噺瓒呰繃1000浜烘', 1, 0),
+(7, '宸¢诲叺', 3, '宸¢诲叺', '绗竴娆℃爣璁板瀮鍦惧笘瀛', 0, 0),
+(8, '娓呮磥宸', 3, '娓呮磥宸', '绗竴娆℃挙閿鎶曠エ', 0, 0),
+(9, '鎵硅瘎瀹', 3, '鎵硅瘎瀹', '绗竴娆″弽瀵圭エ', 0, 0),
+(10, '灏忕紪', 3, '灏忕紪', '绗竴娆$紪杈戞洿鏂', 0, 0),
+(11, '鏉戦暱', 3, '鏉戦暱', '绗竴娆¢噸鏂版爣绛', 0, 0),
+(12, '瀛﹁', 3, '瀛﹁', '绗竴娆℃爣璁扮瓟妗', 0, 0),
+(13, '瀛︾敓', 3, '瀛︾敓', '绗竴娆℃彁闂苟涓旀湁涓娆′互涓婅禐鎴愮エ', 0, 0),
+(14, '鏀寔鑰', 3, '鏀寔鑰', '绗竴娆¤禐鎴愮エ', 0, 0),
+(15, '鏁欏笀', 3, '鏁欏笀', '绗竴娆″洖绛旈棶棰樺苟涓斿緱鍒颁竴涓互涓婅禐鎴愮エ', 0, 0),
+(16, '鑷紶浣滆', 3, '鑷紶浣滆', '瀹屾暣濉啓鐢ㄦ埛璧勬枡鎵鏈夐夐」', 0, 0),
+(17, '鑷鎴愭墠', 3, '鑷鎴愭墠', '鍥炵瓟鑷繁鐨勯棶棰樺苟涓旀湁3涓互涓婅禐鎴愮エ', 1, 0),
+(18, '鏈鏈変环鍊煎洖绛', 1, '鏈鏈変环鍊煎洖绛', '鍥炵瓟瓒呰繃100娆¤禐鎴愮エ', 1, 0),
+(19, '鏈鏈変环鍊奸棶棰', 1, '鏈鏈変环鍊奸棶棰', '闂瓒呰繃100娆¤禐鎴愮エ', 1, 0),
+(20, '涓囦汉杩', 1, '涓囦汉杩', '闂琚100浜轰互涓婃敹钘', 1, 0),
+(21, '钁楀悕闂', 1, '钁楀悕闂', '闂鐨勬祻瑙堥噺瓒呰繃10000浜烘', 1, 0),
+(22, 'alpha鐢ㄦ埛', 2, 'alpha鐢ㄦ埛', '鍐呮祴鏈熼棿鐨勬椿璺冪敤鎴', 0, 0),
+(23, '鏋佸ソ鍥炵瓟', 2, '鏋佸ソ鍥炵瓟', '鍥炵瓟瓒呰繃25娆¤禐鎴愮エ', 1, 0),
+(24, '鏋佸ソ闂', 2, '鏋佸ソ闂', '闂瓒呰繃25娆¤禐鎴愮エ', 1, 0),
+(25, '鍙楁杩庨棶棰', 2, '鍙楁杩庨棶棰', '闂琚25浜轰互涓婃敹钘', 1, 0),
+(26, '浼樼甯傛皯', 2, '浼樼甯傛皯', '鎶曠エ300娆′互涓', 0, 0),
+(27, '缂栬緫涓讳换', 2, '缂栬緫涓讳换', '缂栬緫浜100涓笘瀛', 0, 0),
+(28, '閫氭墠', 2, '閫氭墠', '鍦ㄥ涓爣绛鹃鍩熸椿璺', 0, 0),
+(29, '涓撳', 2, '涓撳', '鍦ㄤ竴涓爣绛鹃鍩熸椿璺冨嚭浼', 0, 0),
+(30, '鑰侀笩', 2, '鑰侀笩', '娲昏穬瓒呰繃涓骞寸殑鐢ㄦ埛', 0, 0),
+(31, '鏈鍙楀叧娉ㄩ棶棰', 2, '鏈鍙楀叧娉ㄩ棶棰', '闂鐨勬祻瑙堥噺瓒呰繃2500浜烘', 1, 0),
+(32, '瀛﹂棶瀹', 2, '瀛﹂棶瀹', '绗竴娆″洖绛旇鎶曡禐鎴愮エ10娆′互涓', 0, 0),
+(33, 'beta鐢ㄦ埛', 2, 'beta鐢ㄦ埛', 'beta鏈熼棿娲昏穬鍙備笌', 0, 0),
+(34, '瀵煎笀', 2, '瀵煎笀', '琚寚瀹氫负鏈浣崇瓟妗堝苟涓旇禐鎴愮エ40浠ヤ笂', 1, 0),
+(35, '宸笀', 2, '宸笀', '鍦ㄦ彁闂60澶╀箣鍚庡洖绛斿苟涓旇禐鎴愮エ5娆′互涓', 1, 0),
+(36, '鍒嗙被涓撳', 2, '鍒嗙被涓撳', '鍒涘缓鐨勬爣绛捐50涓互涓婇棶棰樹娇鐢', 1, 0);
+
+
+TYPE_ACTIVITY_ASK_QUESTION=1
+TYPE_ACTIVITY_ANSWER=2
+TYPE_ACTIVITY_COMMENT_QUESTION=3
+TYPE_ACTIVITY_COMMENT_ANSWER=4
+TYPE_ACTIVITY_UPDATE_QUESTION=5
+TYPE_ACTIVITY_UPDATE_ANSWER=6
+TYPE_ACTIVITY_PRIZE=7
+TYPE_ACTIVITY_MARK_ANSWER=8
+TYPE_ACTIVITY_VOTE_UP=9
+TYPE_ACTIVITY_VOTE_DOWN=10
+TYPE_ACTIVITY_CANCEL_VOTE=11
+TYPE_ACTIVITY_DELETE_QUESTION=12
+TYPE_ACTIVITY_DELETE_ANSWER=13
+TYPE_ACTIVITY_MARK_OFFENSIVE=14
+TYPE_ACTIVITY_UPDATE_TAGS=15
+TYPE_ACTIVITY_FAVORITE=16
+TYPE_ACTIVITY_USER_FULL_UPDATED = 17
+"""
+
+BADGE_AWARD_TYPE_FIRST = {
+ TYPE_ACTIVITY_MARK_OFFENSIVE : 7,
+ TYPE_ACTIVITY_CANCEL_VOTE: 8,
+ TYPE_ACTIVITY_VOTE_DOWN : 9,
+ TYPE_ACTIVITY_UPDATE_QUESTION : 10,
+ TYPE_ACTIVITY_UPDATE_ANSWER : 10,
+ TYPE_ACTIVITY_UPDATE_TAGS : 11,
+ TYPE_ACTIVITY_MARK_ANSWER : 12,
+ TYPE_ACTIVITY_VOTE_UP : 14,
+ TYPE_ACTIVITY_USER_FULL_UPDATED: 16
+
+}
+
+class Command(BaseCommand):
+ def handle_noargs(self, **options):
+ try:
+ self.alpha_user()
+ self.beta_user()
+ self.first_type_award()
+ self.first_ask_be_voted()
+ self.first_answer_be_voted()
+ self.first_answer_be_voted_10()
+ self.vote_count_300()
+ self.edit_count_100()
+ self.comment_count_10()
+ except Exception, e:
+ print e
+ finally:
+ connection.close()
+
+ def alpha_user(self):
+ """
+ Before Jan 25, 2009(Chinese New Year Eve and enter into Beta for CNProg), every registered user
+ will be awarded the "Alpha" badge if he has any activities.
+ """
+ alpha_end_date = date(2009, 1, 25)
+ if date.today() < alpha_end_date:
+ badge = get_object_or_404(Badge, id=22)
+ for user in User.objects.all():
+ award = Award.objects.filter(user=user, badge=badge)
+ if award and not badge.multiple:
+ continue
+ activities = Activity.objects.filter(user=user)
+ if len(activities) > 0:
+ new_award = Award(user=user, badge=badge)
+ new_award.save()
+
+ def beta_user(self):
+ """
+ Before Feb 25, 2009, every registered user
+ will be awarded the "Beta" badge if he has any activities.
+ """
+ beta_end_date = date(2009, 2, 25)
+ if date.today() < beta_end_date:
+ badge = get_object_or_404(Badge, id=33)
+ for user in User.objects.all():
+ award = Award.objects.filter(user=user, badge=badge)
+ if award and not badge.multiple:
+ continue
+ activities = Activity.objects.filter(user=user)
+ if len(activities) > 0:
+ new_award = Award(user=user, badge=badge)
+ new_award.save()
+
+ def first_type_award(self):
+ """
+ This will award below badges for users first behaviors:
+
+ (7, '宸¢诲叺', 3, '宸¢诲叺', '绗竴娆℃爣璁板瀮鍦惧笘瀛', 0, 0),
+ (8, '娓呮磥宸', 3, '娓呮磥宸', '绗竴娆℃挙閿鎶曠エ', 0, 0),
+ (9, '鎵硅瘎瀹', 3, '鎵硅瘎瀹', '绗竴娆″弽瀵圭エ', 0, 0),
+ (10, '灏忕紪', 3, '灏忕紪', '绗竴娆$紪杈戞洿鏂', 0, 0),
+ (11, '鏉戦暱', 3, '鏉戦暱', '绗竴娆¢噸鏂版爣绛', 0, 0),
+ (12, '瀛﹁', 3, '瀛﹁', '绗竴娆℃爣璁扮瓟妗', 0, 0),
+ (14, '鏀寔鑰', 3, '鏀寔鑰', '绗竴娆¤禐鎴愮エ', 0, 0),
+ (16, '鑷紶浣滆', 3, '鑷紶浣滆', '瀹屾暣濉啓鐢ㄦ埛璧勬枡鎵鏈夐夐」', 0, 0),
+ """
+ activity_types = ','.join('%s' % item for item in BADGE_AWARD_TYPE_FIRST.keys())
+ # ORDER BY user_id, activity_type
+ query = "SELECT id, user_id, activity_type, content_type_id, object_id\
+ FROM activity WHERE is_auditted = 0 AND activity_type IN (%s) ORDER BY user_id, activity_type" % activity_types
+
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+ # collect activity_id in current process
+ activity_ids = []
+ last_user_id = 0
+ last_activity_type = 0
+ for row in rows:
+ activity_ids.append(row[0])
+ user_id = row[1]
+ activity_type = row[2]
+ content_type_id = row[3]
+ object_id = row[4]
+
+ # if the user and activity are same as the last, continue
+ if user_id == last_user_id and activity_type == last_activity_type:
+ continue;
+
+ user = get_object_or_404(User, id=user_id)
+ badge = get_object_or_404(Badge, id=BADGE_AWARD_TYPE_FIRST[activity_type])
+ content_type = get_object_or_404(ContentType, id=content_type_id)
+
+ count = Award.objects.filter(user=user, badge=badge).count()
+ if count and not badge.multiple:
+ continue
+ else:
+ # new award
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+
+ # set the current user_id and activity_type to last
+ last_user_id = user_id
+ last_activity_type = activity_type
+
+ # update processed rows to auditted
+ self.update_activities_auditted(cursor, activity_ids)
+ finally:
+ cursor.close()
+
+ def first_ask_be_voted(self):
+ """
+ For user asked question and got first upvote, we award him following badge:
+
+ (13, '瀛︾敓', 3, '瀛︾敓', '绗竴娆℃彁闂苟涓旀湁涓娆′互涓婅禐鎴愮エ', 0, 0),
+ """
+ query = "SELECT act.user_id, q.vote_up_count, act.object_id FROM \
+ activity act, question q WHERE act.activity_type = %s AND \
+ act.object_id = q.id AND\
+ act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 13)
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ badge = get_object_or_404(Badge, id=13)
+ content_type = ContentType.objects.get_for_model(Question)
+ awarded_users = []
+ for row in rows:
+ user_id = row[0]
+ vote_up_count = row[1]
+ object_id = row[2]
+ if vote_up_count > 0 and user_id not in awarded_users:
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+ awarded_users.append(user_id)
+ finally:
+ cursor.close()
+
+ def first_answer_be_voted(self):
+ """
+ When user answerd questions and got first upvote, we award him following badge:
+
+ (15, '鏁欏笀', 3, '鏁欏笀', '绗竴娆″洖绛旈棶棰樺苟涓斿緱鍒颁竴涓互涓婅禐鎴愮エ', 0, 0),
+ """
+ query = "SELECT act.user_id, a.vote_up_count, act.object_id FROM \
+ activity act, answer a WHERE act.activity_type = %s AND \
+ act.object_id = a.id AND\
+ act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 15)
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ awarded_users = []
+ badge = get_object_or_404(Badge, id=15)
+ content_type = ContentType.objects.get_for_model(Answer)
+ for row in rows:
+ user_id = row[0]
+ vote_up_count = row[1]
+ object_id = row[2]
+ if vote_up_count > 0 and user_id not in awarded_users:
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+ awarded_users.append(user_id)
+ finally:
+ cursor.close()
+
+ def first_answer_be_voted_10(self):
+ """
+ (32, '瀛﹂棶瀹', 2, '瀛﹂棶瀹', '绗竴娆″洖绛旇鎶曡禐鎴愮エ10娆′互涓', 0, 0)
+ """
+ query = "SELECT act.user_id, act.object_id FROM \
+ activity act, answer a WHERE act.object_id = a.id AND\
+ act.activity_type = %s AND \
+ a.vote_up_count >= 10 AND\
+ act.user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 32)
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ awarded_users = []
+ badge = get_object_or_404(Badge, id=32)
+ content_type = ContentType.objects.get_for_model(Answer)
+ for row in rows:
+ user_id = row[0]
+ if user_id not in awarded_users:
+ user = get_object_or_404(User, id=user_id)
+ object_id = row[1]
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+ awarded_users.append(user_id)
+ finally:
+ cursor.close()
+
+ def vote_count_300(self):
+ """
+ (26, '浼樼甯傛皯', 2, '浼樼甯傛皯', '鎶曠エ300娆′互涓', 0, 0)
+ """
+ query = "SELECT count(*) vote_count, user_id FROM activity WHERE \
+ activity_type = %s OR \
+ activity_type = %s AND \
+ user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) \
+ GROUP BY user_id HAVING vote_count >= 300" % (TYPE_ACTIVITY_VOTE_UP, TYPE_ACTIVITY_VOTE_DOWN, 26)
+
+ self.__award_for_count_num(query, 26)
+
+ def edit_count_100(self):
+ """
+ (27, '缂栬緫涓讳换', 2, '缂栬緫涓讳换', '缂栬緫浜100涓笘瀛', 0, 0)
+ """
+ query = "SELECT count(*) vote_count, user_id FROM activity WHERE \
+ activity_type = %s OR \
+ activity_type = %s AND \
+ user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) \
+ GROUP BY user_id HAVING vote_count >= 100" % (TYPE_ACTIVITY_UPDATE_QUESTION, TYPE_ACTIVITY_UPDATE_ANSWER, 27)
+
+ self.__award_for_count_num(query, 27)
+
+ def comment_count_10(self):
+ """
+ (5, '璇勮瀹', 3, '璇勮瀹', '璇勮10娆′互涓', 0, 0),
+ """
+ query = "SELECT count(*) vote_count, user_id FROM activity WHERE \
+ activity_type = %s OR \
+ activity_type = %s AND \
+ user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) \
+ GROUP BY user_id HAVING vote_count >= 10" % (TYPE_ACTIVITY_COMMENT_QUESTION, TYPE_ACTIVITY_COMMENT_ANSWER, 5)
+ self.__award_for_count_num(query, 5)
+
+ def __award_for_count_num(self, query, badge):
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ awarded_users = []
+ badge = get_object_or_404(Badge, id=badge)
+ for row in rows:
+ vote_count = row[0]
+ user_id = row[1]
+
+ if user_id not in awarded_users:
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge)
+ award.save()
+ awarded_users.append(user_id)
+ finally:
+ cursor.close()
+
+def main():
+ pass
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/forum/management/commands/sample_command.py b/forum/management/commands/sample_command.py
new file mode 100644
index 00000000..55e67235
--- /dev/null
+++ b/forum/management/commands/sample_command.py
@@ -0,0 +1,7 @@
+from django.core.management.base import NoArgsCommand
+from forum.models import Comment
+
+class Command(NoArgsCommand):
+ def handle_noargs(self, **options):
+ objs = Comment.objects.all()
+ print objs
\ No newline at end of file
diff --git a/forum/managers.py b/forum/managers.py
new file mode 100644
index 00000000..0f22c59c
--- /dev/null
+++ b/forum/managers.py
@@ -0,0 +1,259 @@
+锘縤mport datetime
+import logging
+from django.contrib.auth.models import User, UserManager
+from django.db import connection, models, transaction
+from django.db.models import Q
+from forum.models import *
+from urllib import quote, unquote
+
+class QuestionManager(models.Manager):
+ def get_translation_questions(self, orderby, page_size):
+ questions = self.filter(deleted=False, author__id__in=[28,29]).order_by(orderby)[:page_size]
+ return questions
+
+ def get_questions_by_pagesize(self, orderby, page_size):
+ questions = self.filter(deleted=False).order_by(orderby)[:page_size]
+ return questions
+
+ def get_questions_by_tag(self, tagname, orderby):
+ questions = self.filter(deleted=False, tags__name = unquote(tagname)).order_by(orderby)
+ return questions
+
+ def get_unanswered_questions(self, orderby):
+ questions = self.filter(deleted=False, answer_count=0).order_by(orderby)
+ return questions
+
+ def get_questions(self, orderby):
+ questions = self.filter(deleted=False).order_by(orderby)
+ return questions
+
+ def update_tags(self, question, tagnames, user):
+ """
+ Updates Tag associations for a question to match the given
+ tagname string.
+
+ Returns ``True`` if tag usage counts were updated as a result,
+ ``False`` otherwise.
+ """
+ from forum.models import Tag
+ current_tags = list(question.tags.all())
+ current_tagnames = set(t.name for t in current_tags)
+ updated_tagnames = set(t for t in tagnames.split(' ') if t)
+ modified_tags = []
+
+ removed_tags = [t for t in current_tags
+ if t.name not in updated_tagnames]
+ if removed_tags:
+ modified_tags.extend(removed_tags)
+ question.tags.remove(*removed_tags)
+
+ added_tagnames = updated_tagnames - current_tagnames
+ if added_tagnames:
+ added_tags = Tag.objects.get_or_create_multiple(added_tagnames,
+ user)
+ modified_tags.extend(added_tags)
+ question.tags.add(*added_tags)
+
+ if modified_tags:
+ Tag.objects.update_use_counts(modified_tags)
+ return True
+
+ return False
+
+ def update_answer_count(self, question):
+ """
+ Executes an UPDATE query to update denormalised data with the
+ number of answers the given question has.
+ """
+
+ # for some reasons, this Answer class failed to be imported,
+ # although we have imported all classes from models on top.
+ from forum.models import Answer
+ self.filter(id=question.id).update(
+ answer_count=Answer.objects.get_answers_from_question(question).count())
+
+ def update_view_count(self, question):
+ """
+ update counter+1 when user browse question page
+ """
+ self.filter(id=question.id).update(view_count = question.view_count + 1)
+
+ def update_favorite_count(self, question):
+ """
+ update favourite_count for given question
+ """
+ from forum.models import FavoriteQuestion
+ self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count())
+
+ def get_similar_questions(self, question):
+ """
+ Get 10 similar questions for given one.
+ This will search the same tag list for give question(by exactly same string) first.
+ Questions with the individual tags will be added to list if above questions are not full.
+ """
+ #print datetime.datetime.now()
+ from forum.models import Question
+ questions = list(Question.objects.filter(tagnames = question.tagnames).all())
+
+ tags_list = question.tags.all()
+ for tag in tags_list:
+ extend_questions = Question.objects.filter(tags__id = tag.id)[:50]
+ for item in extend_questions:
+ if item not in questions and len(questions) < 10:
+ questions.append(item)
+
+ #print datetime.datetime.now()
+ return questions
+
+class TagManager(models.Manager):
+ UPDATE_USED_COUNTS_QUERY = (
+ 'UPDATE tag '
+ 'SET used_count = ('
+ 'SELECT COUNT(*) FROM question_tags '
+ 'WHERE tag_id = tag.id'
+ ') '
+ 'WHERE id IN (%s)')
+
+ def get_valid_tags(self, page_size):
+ from forum.models import Tag
+ tags = Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-id")[:page_size]
+ return tags
+
+ def get_or_create_multiple(self, names, user):
+ """
+ Fetches a list of Tags with the given names, creating any Tags
+ which don't exist when necesssary.
+ """
+ tags = list(self.filter(name__in=names))
+ #Set all these tag visible
+ for tag in tags:
+ if tag.deleted:
+ tag.deleted = False
+ tag.deleted_by = None
+ tag.deleted_at = None
+ tag.save()
+
+ if len(tags) < len(names):
+ existing_names = set(tag.name for tag in tags)
+ new_names = [name for name in names if name not in existing_names]
+ tags.extend([self.create(name=name, created_by=user)
+ for name in new_names if self.filter(name=name).count() == 0 and len(name.strip()) > 0])
+
+ return tags
+
+ def update_use_counts(self, tags):
+ """Updates the given Tags with their current use counts."""
+ if not tags:
+ return
+ cursor = connection.cursor()
+ query = self.UPDATE_USED_COUNTS_QUERY % ','.join(['%s'] * len(tags))
+ cursor.execute(query, [tag.id for tag in tags])
+ transaction.commit_unless_managed()
+
+ def get_tags_by_questions(self, questions):
+ question_ids = []
+ for question in questions:
+ question_ids.append(question.id)
+
+ question_ids_str = ','.join([str(id) for id in question_ids])
+ related_tags = self.extra(
+ tables=['tag', 'question_tags'],
+ where=["tag.id = question_tags.tag_id AND question_tags.question_id IN (" + question_ids_str + ")"]
+ ).distinct()
+
+ return related_tags
+
+class AnswerManager(models.Manager):
+ GET_ANSWERS_FROM_USER_QUESTIONS = u'SELECT answer.* FROM answer INNER JOIN question ON answer.question_id = question.id WHERE question.author_id =%s AND answer.author_id <> %s'
+ def get_answers_from_question(self, question, user=None):
+ """
+ Retrieves visibile answers for the given question. Delete answers
+ are only visibile to the person who deleted them.
+ """
+
+ if user is None or not user.is_authenticated():
+ return self.filter(question=question, deleted=False)
+ else:
+ return self.filter(Q(question=question),
+ Q(deleted=False) | Q(deleted_by=user))
+
+ def get_answers_from_questions(self, user_id):
+ """
+ Retrieves visibile answers for the given question. Which are not included own answers
+ """
+ cursor = connection.cursor()
+ cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id])
+ return cursor.fetchall()
+
+class VoteManager(models.Manager):
+ COUNT_UP_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = 1"
+ COUNT_DOWN_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = -1"
+ COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = DATE(NOW())"
+ def get_up_vote_count_from_user(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_UP_VOTE_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+ else:
+ return 0
+
+ def get_down_vote_count_from_user(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_DOWN_VOTE_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+ else:
+ return 0
+
+ def get_votes_count_today_from_user(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+
+ else:
+ return 0
+
+class FlaggedItemManager(models.Manager):
+ COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = DATE(NOW())"
+ def get_flagged_items_count_today(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+
+ else:
+ return 0
+
+class ReputeManager(models.Manager):
+ COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = DATE(NOW())"
+ def get_reputation_by_upvoted_today(self, user):
+ """
+ For one user in one day, he can only earn rep till certain score (ep. +200)
+ by upvoted(also substracted from upvoted canceled). This is because we need
+ to prohibit gaming system by upvoting/cancel again and again.
+ """
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+
+ else:
+ return 0
+class AwardManager(models.Manager):
+ def get_recent_awards(self):
+ awards = super(AwardManager, self).extra(
+ select={'badge_id': 'badge.id', 'badge_name':'badge.name',
+ 'badge_description': 'badge.description', 'badge_type': 'badge.type',
+ 'user_id': 'auth_user.id', 'user_name': 'auth_user.username'
+ },
+ tables=['award', 'badge', 'auth_user'],
+ order_by=['-awarded_at'],
+ where=['auth_user.id=award.user_id AND badge_id=badge.id'],
+ ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name')
+ return awards
diff --git a/forum/models.py b/forum/models.py
new file mode 100644
index 00000000..290c9d56
--- /dev/null
+++ b/forum/models.py
@@ -0,0 +1,653 @@
+锘# encoding:utf-8
+import datetime
+import hashlib
+from urllib import quote_plus, urlencode
+from django.db import models
+from django.utils.html import strip_tags
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+from django.template.defaultfilters import slugify
+from django.db.models.signals import post_delete, post_save, pre_save
+import django.dispatch
+
+from forum.managers import *
+from const import *
+
+class Tag(models.Model):
+ name = models.CharField(max_length=255, unique=True)
+ created_by = models.ForeignKey(User, related_name='created_tags')
+ deleted = models.BooleanField(default=False)
+ deleted_at = models.DateTimeField(null=True, blank=True)
+ deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_tags')
+ # Denormalised data
+ used_count = models.PositiveIntegerField(default=0)
+
+ objects = TagManager()
+
+ class Meta:
+ db_table = u'tag'
+ ordering = ('-used_count', 'name')
+
+ def __unicode__(self):
+ return self.name
+
+class Comment(models.Model):
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ user = models.ForeignKey(User, related_name='comments')
+ comment = models.CharField(max_length=300)
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+
+ class Meta:
+ ordering = ('-added_at',)
+ db_table = u'comment'
+ def __unicode__(self):
+ return self.comment
+
+class Vote(models.Model):
+ VOTE_UP = +1
+ VOTE_DOWN = -1
+ VOTE_CHOICES = (
+ (VOTE_UP, u'Up'),
+ (VOTE_DOWN, u'Down'),
+ )
+
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ user = models.ForeignKey(User, related_name='votes')
+ vote = models.SmallIntegerField(choices=VOTE_CHOICES)
+ voted_at = models.DateTimeField(default=datetime.datetime.now)
+
+ objects = VoteManager()
+
+ class Meta:
+ unique_together = ('content_type', 'object_id', 'user')
+ db_table = u'vote'
+ def __unicode__(self):
+ return '[%s] voted at %s: %s' %(self.user, self.voted_at, self.vote)
+
+ def is_upvote(self):
+ return self.vote == self.VOTE_UP
+
+ def is_downvote(self):
+ return self.vote == self.VOTE_DOWN
+
+class FlaggedItem(models.Model):
+ """A flag on a Question or Answer indicating offensive content."""
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ user = models.ForeignKey(User, related_name='flagged_items')
+ flagged_at = models.DateTimeField(default=datetime.datetime.now)
+
+ objects = FlaggedItemManager()
+
+ class Meta:
+ unique_together = ('content_type', 'object_id', 'user')
+ db_table = u'flagged_item'
+ def __unicode__(self):
+ return '[%s] flagged at %s' %(self.user, self.flagged_at)
+
+class Question(models.Model):
+ title = models.CharField(max_length=300)
+ author = models.ForeignKey(User, related_name='questions')
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ tags = models.ManyToManyField(Tag, related_name='questions')
+ # Status
+ wiki = models.BooleanField(default=False)
+ wikified_at = models.DateTimeField(null=True, blank=True)
+ answer_accepted = models.BooleanField(default=False)
+ closed = models.BooleanField(default=False)
+ closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions')
+ closed_at = models.DateTimeField(null=True, blank=True)
+ close_reason = models.SmallIntegerField(choices=CLOSE_REASONS, null=True, blank=True)
+ deleted = models.BooleanField(default=False)
+ deleted_at = models.DateTimeField(null=True, blank=True)
+ deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_questions')
+ locked = models.BooleanField(default=False)
+ locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_questions')
+ locked_at = models.DateTimeField(null=True, blank=True)
+ # Denormalised data
+ score = models.IntegerField(default=0)
+ vote_up_count = models.IntegerField(default=0)
+ vote_down_count = models.IntegerField(default=0)
+ answer_count = models.PositiveIntegerField(default=0)
+ comment_count = models.PositiveIntegerField(default=0)
+ view_count = models.PositiveIntegerField(default=0)
+ offensive_flag_count = models.SmallIntegerField(default=0)
+ favourite_count = models.PositiveIntegerField(default=0)
+ last_edited_at = models.DateTimeField(null=True, blank=True)
+ last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_questions')
+ last_activity_at = models.DateTimeField(default=datetime.datetime.now)
+ last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions')
+ tagnames = models.CharField(max_length=125)
+ summary = models.CharField(max_length=180)
+ html = models.TextField()
+ comments = generic.GenericRelation(Comment)
+ votes = generic.GenericRelation(Vote)
+ flagged_items = generic.GenericRelation(FlaggedItem)
+
+ objects = QuestionManager()
+
+ def save(self, **kwargs):
+ """
+ Overridden to manually manage addition of tags when the object
+ is first saved.
+
+ This is required as we're using ``tagnames`` as the sole means of
+ adding and editing tags.
+ """
+ initial_addition = (self.id is None)
+ super(Question, self).save(**kwargs)
+ if initial_addition:
+ tags = Tag.objects.get_or_create_multiple(self.tagname_list(),
+ self.author)
+ self.tags.add(*tags)
+ Tag.objects.update_use_counts(tags)
+
+ def tagname_list(self):
+ """Creates a list of Tag names from the ``tagnames`` attribute."""
+ return [name for name in self.tagnames.split(u' ')]
+
+ def get_absolute_url(self):
+ return '%s%s' % (reverse('question', args=[self.id]), self.title)
+
+ def has_favorite_by_user(self, user):
+ if not user.is_authenticated():
+ return False
+ return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0
+
+ def get_answer_count_by_user(self, user_id):
+ query_set = Answer.objects.filter(author__id=user_id)
+ return query_set.filter(question=self).count()
+
+ def get_question_title(self):
+ if self.closed:
+ attr = CONST['closed']
+ elif self.deleted:
+ attr = CONST['deleted']
+ else:
+ attr = None
+ return u'%s %s' % (self.title, attr) if attr is not None else self.title
+
+ def get_revision_url(self):
+ return reverse('question_revisions', args=[self.id])
+
+ def get_latest_revision(self):
+ return self.revisions.all()[0]
+
+ def __unicode__(self):
+ return self.title
+
+ class Meta:
+ db_table = u'question'
+
+class QuestionRevision(models.Model):
+ """A revision of a Question."""
+ question = models.ForeignKey(Question, related_name='revisions')
+ revision = models.PositiveIntegerField(blank=True)
+ title = models.CharField(max_length=300)
+ author = models.ForeignKey(User, related_name='question_revisions')
+ revised_at = models.DateTimeField()
+ tagnames = models.CharField(max_length=125)
+ summary = models.CharField(max_length=300, blank=True)
+ text = models.TextField()
+
+ class Meta:
+ db_table = u'question_revision'
+ ordering = ('-revision',)
+
+ def get_question_title(self):
+ return self.question.title
+
+ def get_absolute_url(self):
+ return '/questions/%s/revisions' % (self.question.id)
+
+ def save(self, **kwargs):
+ """Looks up the next available revision number."""
+ if not self.revision:
+ self.revision = QuestionRevision.objects.filter(
+ question=self.question).values_list('revision',
+ flat=True)[0] + 1
+ super(QuestionRevision, self).save(**kwargs)
+
+ def __unicode__(self):
+ return u'revision %s of %s' % (self.revision, self.title)
+
+class Answer(models.Model):
+ question = models.ForeignKey(Question, related_name='answers')
+ author = models.ForeignKey(User, related_name='answers')
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ # Status
+ wiki = models.BooleanField(default=False)
+ wikified_at = models.DateTimeField(null=True, blank=True)
+ accepted = models.BooleanField(default=False)
+ accepted_at = models.DateTimeField(null=True, blank=True)
+ deleted = models.BooleanField(default=False)
+ deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_answers')
+ locked = models.BooleanField(default=False)
+ locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_answers')
+ locked_at = models.DateTimeField(null=True, blank=True)
+ # Denormalised data
+ score = models.IntegerField(default=0)
+ vote_up_count = models.IntegerField(default=0)
+ vote_down_count = models.IntegerField(default=0)
+ comment_count = models.PositiveIntegerField(default=0)
+ offensive_flag_count = models.SmallIntegerField(default=0)
+ last_edited_at = models.DateTimeField(null=True, blank=True)
+ last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_answers')
+ html = models.TextField()
+ comments = generic.GenericRelation(Comment)
+ votes = generic.GenericRelation(Vote)
+ flagged_items = generic.GenericRelation(FlaggedItem)
+
+ objects = AnswerManager()
+
+ def get_user_vote(self, user):
+ votes = self.votes.filter(user=user)
+ if votes.count() > 0:
+ return votes[0]
+ else:
+ return None
+
+ def get_latest_revision(self):
+ return self.revisions.all()[0]
+
+ def get_question_title(self):
+ return self.question.title
+
+ def get_absolute_url(self):
+ return '%s%s#%s' % (reverse('question', args=[self.question.id]), self.question.title, self.id)
+
+ class Meta:
+ db_table = u'answer'
+
+ def __unicode__(self):
+ return self.html
+
+class AnswerRevision(models.Model):
+ """A revision of an Answer."""
+ answer = models.ForeignKey(Answer, related_name='revisions')
+ revision = models.PositiveIntegerField()
+ author = models.ForeignKey(User, related_name='answer_revisions')
+ revised_at = models.DateTimeField()
+ summary = models.CharField(max_length=300, blank=True)
+ text = models.TextField()
+
+ def get_absolute_url(self):
+ return '/answers/%s/revisions' % (self.answer.id)
+
+ def get_question_title(self):
+ return self.answer.question.title
+
+ class Meta:
+ db_table = u'answer_revision'
+ ordering = ('-revision',)
+
+ def save(self, **kwargs):
+ """Looks up the next available revision number if not set."""
+ if not self.revision:
+ self.revision = AnswerRevision.objects.filter(
+ answer=self.answer).values_list('revision',
+ flat=True)[0] + 1
+ super(AnswerRevision, self).save(**kwargs)
+
+class FavoriteQuestion(models.Model):
+ """A favorite Question of a User."""
+ question = models.ForeignKey(Question)
+ user = models.ForeignKey(User, related_name='user_favorite_questions')
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ class Meta:
+ db_table = u'favorite_question'
+ def __unicode__(self):
+ return '[%s] favorited at %s' %(self.user, self.added_at)
+
+class Badge(models.Model):
+ """Awarded for notable actions performed on the site by Users."""
+ GOLD = 1
+ SILVER = 2
+ BRONZE = 3
+ TYPE_CHOICES = (
+ (GOLD, u'閲戠墝'),
+ (SILVER, u'閾剁墝'),
+ (BRONZE, u'閾滅墝'),
+ )
+
+ name = models.CharField(max_length=50)
+ type = models.SmallIntegerField(choices=TYPE_CHOICES)
+ slug = models.SlugField(max_length=50, blank=True)
+ description = models.CharField(max_length=300)
+ multiple = models.BooleanField(default=False)
+ # Denormalised data
+ awarded_count = models.PositiveIntegerField(default=0)
+
+ class Meta:
+ db_table = u'badge'
+ ordering = ('name',)
+ unique_together = ('name', 'type')
+
+ def __unicode__(self):
+ return u'%s: %s' % (self.get_type_display(), self.name)
+
+ def save(self, **kwargs):
+ if not self.slug:
+ self.slug = self.name#slugify(self.name)
+ super(Badge, self).save(**kwargs)
+
+ def get_absolute_url(self):
+ return '%s%s/' % (reverse('badge', args=[self.id]), self.slug)
+
+class Award(models.Model):
+ """The awarding of a Badge to a User."""
+ user = models.ForeignKey(User, related_name='award_user')
+ badge = models.ForeignKey(Badge, related_name='award_badge')
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ awarded_at = models.DateTimeField(default=datetime.datetime.now)
+ notified = models.BooleanField(default=False)
+ objects = AwardManager()
+
+ def __unicode__(self):
+ return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.name, self.awarded_at)
+
+ class Meta:
+ db_table = u'award'
+
+class Repute(models.Model):
+ """The reputation histories for user"""
+ user = models.ForeignKey(User)
+ positive = models.SmallIntegerField(default=0)
+ negative = models.SmallIntegerField(default=0)
+ question = models.ForeignKey(Question)
+ reputed_at = models.DateTimeField(default=datetime.datetime.now)
+ reputation_type = models.SmallIntegerField(choices=TYPE_REPUTATION)
+ reputation = models.IntegerField(default=1)
+ objects = ReputeManager()
+
+ def __unicode__(self):
+ return u'[%s]\' reputation changed at %s' % (self.user.username, self.reputed_at)
+
+ class Meta:
+ db_table = u'repute'
+
+class Activity(models.Model):
+ """
+ We keep some history data for user activities
+ """
+ user = models.ForeignKey(User)
+ activity_type = models.SmallIntegerField(choices=TYPE_ACTIVITY)
+ active_at = models.DateTimeField(default=datetime.datetime.now)
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ is_auditted = models.BooleanField(default=False)
+
+ def __unicode__(self):
+ return u'[%s] was active at %s' % (self.user.username, self.active_at)
+
+ class Meta:
+ db_table = u'activity'
+
+class Book(models.Model):
+ """
+ Model for book info
+ """
+ user = models.ForeignKey(User)
+ title = models.CharField(max_length=255)
+ short_name = models.CharField(max_length=255)
+ author = models.CharField(max_length=255)
+ price = models.DecimalField(max_digits=6, decimal_places=2)
+ pages = models.SmallIntegerField()
+ published_at = models.DateTimeField()
+ publication = models.CharField(max_length=255)
+ cover_img = models.CharField(max_length=255)
+ tagnames = models.CharField(max_length=125)
+ added_at = models.DateTimeField()
+ last_edited_at = models.DateTimeField()
+ questions = models.ManyToManyField(Question, related_name='book', db_table='book_question')
+
+ def get_absolute_url(self):
+ return '%s' % reverse('book', args=[self.short_name])
+
+ def __unicode__(self):
+ return self.title
+ class Meta:
+ db_table = u'book'
+
+class BookAuthorInfo(models.Model):
+ """
+ Model for book author info
+ """
+ user = models.ForeignKey(User)
+ book = models.ForeignKey(Book)
+ blog_url = models.CharField(max_length=255)
+ added_at = models.DateTimeField()
+ last_edited_at = models.DateTimeField()
+
+ class Meta:
+ db_table = u'book_author_info'
+
+class BookAuthorRss(models.Model):
+ """
+ Model for book author blog rss
+ """
+ user = models.ForeignKey(User)
+ book = models.ForeignKey(Book)
+ title = models.CharField(max_length=255)
+ url = models.CharField(max_length=255)
+ rss_created_at = models.DateTimeField()
+ added_at = models.DateTimeField()
+
+ class Meta:
+ db_table = u'book_author_rss'
+
+# User extend properties
+QUESTIONS_PER_PAGE_CHOICES = (
+ (10, u'10'),
+ (30, u'30'),
+ (50, u'50'),
+)
+
+User.add_to_class('reputation', models.PositiveIntegerField(default=1))
+User.add_to_class('gravatar', models.CharField(max_length=32))
+User.add_to_class('favorite_questions',
+ models.ManyToManyField(Question, through=FavoriteQuestion,
+ related_name='favorited_by'))
+User.add_to_class('badges', models.ManyToManyField(Badge, through=Award,
+ related_name='awarded_to'))
+User.add_to_class('gold', models.SmallIntegerField(default=0))
+User.add_to_class('silver', models.SmallIntegerField(default=0))
+User.add_to_class('bronze', models.SmallIntegerField(default=0))
+User.add_to_class('questions_per_page',
+ models.SmallIntegerField(choices=QUESTIONS_PER_PAGE_CHOICES, default=10))
+User.add_to_class('last_seen',
+ models.DateTimeField(default=datetime.datetime.now))
+User.add_to_class('real_name', models.CharField(max_length=100, blank=True))
+User.add_to_class('website', models.URLField(max_length=200, blank=True))
+User.add_to_class('location', models.CharField(max_length=100, blank=True))
+User.add_to_class('date_of_birth', models.DateField(null=True, blank=True))
+User.add_to_class('about', models.TextField(blank=True))
+
+# custom signal
+tags_updated = django.dispatch.Signal(providing_args=["question"])
+edit_question_or_answer = django.dispatch.Signal(providing_args=["instance", "modified_by"])
+delete_post_or_answer = django.dispatch.Signal(providing_args=["instance", "deleted_by"])
+mark_offensive = django.dispatch.Signal(providing_args=["instance", "mark_by"])
+user_updated = django.dispatch.Signal(providing_args=["instance", "updated_by"])
+def get_messages(self):
+ messages = []
+ for m in self.message_set.all():
+ messages.append(m.message)
+ return messages
+
+def delete_messages(self):
+ self.message_set.all().delete()
+
+def get_profile_url(self):
+ """Returns the URL for this User's profile."""
+ return '%s%s/' % (reverse('user', args=[self.id]), self.username)
+User.add_to_class('get_profile_url', get_profile_url)
+User.add_to_class('get_messages', get_messages)
+User.add_to_class('delete_messages', delete_messages)
+
+def calculate_gravatar_hash(instance, **kwargs):
+ """Calculates a User's gravatar hash from their email address."""
+ if kwargs.get('raw', False):
+ return
+ instance.gravatar = hashlib.md5(instance.email).hexdigest()
+
+def record_ask_event(instance, created, **kwargs):
+ if created:
+ activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ASK_QUESTION)
+ activity.save()
+
+def record_answer_event(instance, created, **kwargs):
+ if created:
+ activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ANSWER)
+ activity.save()
+
+def record_comment_event(instance, created, **kwargs):
+ if created:
+ from django.contrib.contenttypes.models import ContentType
+ question_type = ContentType.objects.get_for_model(Question)
+ question_type_id = question_type.id
+ type = TYPE_ACTIVITY_COMMENT_QUESTION if instance.content_type_id == question_type_id else TYPE_ACTIVITY_COMMENT_ANSWER
+ activity = Activity(user=instance.user, active_at=instance.added_at, content_object=instance, activity_type=type)
+ activity.save()
+
+def record_revision_question_event(instance, created, **kwargs):
+ if created and instance.revision <> 1:
+ activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_QUESTION)
+ activity.save()
+
+def record_revision_answer_event(instance, created, **kwargs):
+ if created and instance.revision <> 1:
+ activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_ANSWER)
+ activity.save()
+
+def record_award_event(instance, created, **kwargs):
+ """
+ After we awarded a badge to user, we need to record this activity and notify user.
+ We also recaculate awarded_count of this badge and user information.
+ """
+ if created:
+ activity = Activity(user=instance.user, active_at=instance.awarded_at, content_object=instance,
+ activity_type=TYPE_ACTIVITY_PRIZE)
+ activity.save()
+
+ instance.badge.awarded_count += 1
+ instance.badge.save()
+
+ if instance.badge.type == Badge.GOLD:
+ instance.user.gold += 1
+ if instance.badge.type == Badge.SILVER:
+ instance.user.silver += 1
+ if instance.badge.type == Badge.BRONZE:
+ instance.user.bronze += 1
+ instance.user.save()
+
+def notify_award_message(instance, created, **kwargs):
+ """
+ Notify users when they have been awarded badges by using Django message.
+ """
+ if created:
+ user = instance.user
+ user.message_set.create(message=u"%s" % instance.badge.name)
+
+def record_answer_accepted(instance, created, **kwargs):
+ """
+ when answer is accepted, we record this for question author - who accepted it.
+ """
+ if not created and instance.accepted:
+ activity = Activity(user=instance.question.author, active_at=datetime.datetime.now(), \
+ content_object=instance, activity_type=TYPE_ACTIVITY_MARK_ANSWER)
+ activity.save()
+
+def update_last_seen(instance, created, **kwargs):
+ """
+ when user has activities, we update 'last_seen' time stamp for him
+ """
+ user = instance.user
+ user.last_seen = datetime.datetime.now()
+ user.save()
+
+def record_vote(instance, created, **kwargs):
+ """
+ when user have voted
+ """
+ if created:
+ if instance.vote == 1:
+ vote_type = TYPE_ACTIVITY_VOTE_UP
+ else:
+ vote_type = TYPE_ACTIVITY_VOTE_DOWN
+
+ activity = Activity(user=instance.user, active_at=instance.voted_at, content_object=instance, activity_type=vote_type)
+ activity.save()
+
+def record_cancel_vote(instance, **kwargs):
+ """
+ when user canceled vote, the vote will be deleted.
+ """
+ activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_CANCEL_VOTE)
+ activity.save()
+
+def record_delete_question(instance, delete_by, **kwargs):
+ """
+ when user deleted the question
+ """
+ if instance.__class__ == "Question":
+ activity_type = TYPE_ACTIVITY_DELETE_QUESTION
+ else:
+ activity_type = TYPE_ACTIVITY_DELETE_ANSWER
+
+ activity = Activity(user=delete_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=activity_type)
+ activity.save()
+
+def record_mark_offensive(instance, mark_by, **kwargs):
+ activity = Activity(user=mark_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_MARK_OFFENSIVE)
+ activity.save()
+
+def record_update_tags(question, **kwargs):
+ """
+ when user updated tags of the question
+ """
+ activity = Activity(user=question.author, active_at=datetime.datetime.now(), content_object=question, activity_type=TYPE_ACTIVITY_UPDATE_TAGS)
+ activity.save()
+
+def record_favorite_question(instance, created, **kwargs):
+ """
+ when user add the question in him favorite questions list.
+ """
+ if created:
+ activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_FAVORITE)
+ activity.save()
+
+def record_user_full_updated(instance, **kwargs):
+ activity = Activity(user=instance, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_USER_FULL_UPDATED)
+ activity.save()
+
+#signal for User modle save changes
+pre_save.connect(calculate_gravatar_hash, sender=User)
+post_save.connect(record_ask_event, sender=Question)
+post_save.connect(record_answer_event, sender=Answer)
+post_save.connect(record_comment_event, sender=Comment)
+post_save.connect(record_revision_question_event, sender=QuestionRevision)
+post_save.connect(record_revision_answer_event, sender=AnswerRevision)
+post_save.connect(record_award_event, sender=Award)
+post_save.connect(notify_award_message, sender=Award)
+post_save.connect(record_answer_accepted, sender=Answer)
+post_save.connect(update_last_seen, sender=Activity)
+post_save.connect(record_vote, sender=Vote)
+post_delete.connect(record_cancel_vote, sender=Vote)
+delete_post_or_answer.connect(record_delete_question, sender=Question)
+delete_post_or_answer.connect(record_delete_question, sender=Answer)
+mark_offensive.connect(record_mark_offensive, sender=Question)
+mark_offensive.connect(record_mark_offensive, sender=Answer)
+tags_updated.connect(record_update_tags, sender=Question)
+post_save.connect(record_favorite_question, sender=FavoriteQuestion)
+user_updated.connect(record_user_full_updated, sender=User)
\ No newline at end of file
diff --git a/forum/templatetags/__init__.py b/forum/templatetags/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/forum/templatetags/extra_filters.py b/forum/templatetags/extra_filters.py
new file mode 100644
index 00000000..744fa762
--- /dev/null
+++ b/forum/templatetags/extra_filters.py
@@ -0,0 +1,83 @@
+锘縡rom django import template
+from forum import auth
+
+register = template.Library()
+
+@register.filter
+def can_vote_up(user):
+ return auth.can_vote_up(user)
+
+@register.filter
+def can_flag_offensive(user):
+ return auth.can_flag_offensive(user)
+
+@register.filter
+def can_add_comments(user):
+ return auth.can_add_comments(user)
+
+@register.filter
+def can_vote_down(user):
+ return auth.can_vote_down(user)
+
+@register.filter
+def can_retag_questions(user):
+ return auth.can_retag_questions(user)
+
+@register.filter
+def can_edit_post(user, post):
+ return auth.can_edit_post(user, post)
+
+@register.filter
+def can_delete_comment(user, comment):
+ return auth.can_delete_comment(user, comment)
+
+@register.filter
+def can_view_offensive_flags(user):
+ return auth.can_view_offensive_flags(user)
+
+@register.filter
+def can_close_question(user, question):
+ return auth.can_close_question(user, question)
+
+@register.filter
+def can_lock_posts(user):
+ return auth.can_lock_posts(user)
+
+@register.filter
+def can_accept_answer(user, question, answer):
+ return auth.can_accept_answer(user, question, answer)
+
+@register.filter
+def can_reopen_question(user, question):
+ return auth.can_reopen_question(user, question)
+
+@register.filter
+def can_delete_post(user, post):
+ return auth.can_delete_post(user, post)
+
+@register.filter
+def can_view_user_edit(request_user, target_user):
+ return auth.can_view_user_edit(request_user, target_user)
+
+@register.filter
+def can_view_user_votes(request_user, target_user):
+ return auth.can_view_user_votes(request_user, target_user)
+
+@register.filter
+def can_view_user_preferences(request_user, target_user):
+ return auth.can_view_user_preferences(request_user, target_user)
+
+@register.filter
+def is_user_self(request_user, target_user):
+ return auth.is_user_self(request_user, target_user)
+
+@register.filter
+def cnprog_intword(number):
+ try:
+ if 1000 <= number < 10000:
+ string = str(number)[0:1]
+ return "%sk" % string
+ else:
+ return number
+ except:
+ return number
\ No newline at end of file
diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py
new file mode 100644
index 00000000..7c53c2cb
--- /dev/null
+++ b/forum/templatetags/extra_tags.py
@@ -0,0 +1,232 @@
+锘縤mport time
+import datetime
+import math
+import re
+import logging
+from django import template
+from django.utils.encoding import smart_unicode
+from django.utils.safestring import mark_safe
+from django.utils.timesince import timesince
+from forum.const import *
+
+register = template.Library()
+
+GRAVATAR_TEMPLATE = ('')
+
+@register.simple_tag
+def gravatar(user, size):
+ """
+ Creates an ```` for a user's Gravatar with a given size.
+
+ This tag can accept a User object, or a dict containing the
+ appropriate values.
+ """
+ try:
+ gravatar = user['gravatar']
+ except (TypeError, AttributeError, KeyError):
+ gravatar = user.gravatar
+ return mark_safe(GRAVATAR_TEMPLATE % {
+ 'size': size,
+ 'gravatar_hash': gravatar,
+ })
+
+MAX_FONTSIZE = 18
+MIN_FONTSIZE = 12
+@register.simple_tag
+def tag_font_size(max_size, min_size, current_size):
+ """
+ do a logarithmic mapping calcuation for a proper size for tagging cloud
+ Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/
+ """
+ #avoid invalid calculation
+ if current_size == 0:
+ current_size = 1
+ try:
+ weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size))
+ except:
+ weight = 0
+ return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight)
+
+
+LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5
+LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 4
+NUM_PAGES_OUTSIDE_RANGE = 1
+ADJACENT_PAGES = 2
+@register.inclusion_tag("paginator.html")
+def cnprog_paginator(context):
+ """
+ custom paginator tag
+ Inspired from http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/
+ """
+ if (context["is_paginated"]):
+ " Initialize variables "
+ in_leading_range = in_trailing_range = False
+ pages_outside_leading_range = pages_outside_trailing_range = range(0)
+
+ if (context["pages"] <= LEADING_PAGE_RANGE_DISPLAYED):
+ in_leading_range = in_trailing_range = True
+ page_numbers = [n for n in range(1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
+ elif (context["page"] <= LEADING_PAGE_RANGE):
+ in_leading_range = True
+ page_numbers = [n for n in range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) if n > 0 and n <= context["pages"]]
+ pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
+ elif (context["page"] > context["pages"] - TRAILING_PAGE_RANGE):
+ in_trailing_range = True
+ page_numbers = [n for n in range(context["pages"] - TRAILING_PAGE_RANGE_DISPLAYED + 1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
+ pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
+ else:
+ page_numbers = [n for n in range(context["page"] - ADJACENT_PAGES, context["page"] + ADJACENT_PAGES + 1) if n > 0 and n <= context["pages"]]
+ pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
+ pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
+
+ extend_url = context.get('extend_url', '')
+ return {
+ "base_url": context["base_url"],
+ "is_paginated": context["is_paginated"],
+ "previous": context["previous"],
+ "has_previous": context["has_previous"],
+ "next": context["next"],
+ "has_next": context["has_next"],
+ "page": context["page"],
+ "pages": context["pages"],
+ "page_numbers": page_numbers,
+ "in_leading_range" : in_leading_range,
+ "in_trailing_range" : in_trailing_range,
+ "pages_outside_leading_range": pages_outside_leading_range,
+ "pages_outside_trailing_range": pages_outside_trailing_range,
+ "extend_url" : extend_url
+ }
+
+@register.inclusion_tag("pagesize.html")
+def cnprog_pagesize(context):
+ """
+ display the pagesize selection boxes for paginator
+ """
+ if (context["is_paginated"]):
+ return {
+ "base_url": context["base_url"],
+ "pagesize" : context["pagesize"],
+ "is_paginated": context["is_paginated"]
+ }
+
+@register.simple_tag
+def get_score_badge(user):
+ BADGE_TEMPLATE = '%(reputation)s'
+ if user.gold > 0 :
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, ' '
+ '鈼'
+ '%(gold)s'
+ '')
+ if user.silver > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, ' '
+ '鈼'
+ '%(silver)s'
+ '')
+ if user.bronze > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, ' '
+ '鈼'
+ '%(bronze)s'
+ '')
+ 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,
+ })
+
+@register.simple_tag
+def get_score_badge_by_details(rep, gold, silver, bronze):
+ BADGE_TEMPLATE = '%(reputation)s'
+ if gold > 0 :
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, ''
+ '鈼'
+ '%(gold)s'
+ '')
+ if silver > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, ''
+ '鈼'
+ '%(silver)s'
+ '')
+ if bronze > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, ''
+ '鈼'
+ '%(bronze)s'
+ '')
+ 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,
+ })
+
+@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])
+ diff = current_time - birthday
+ return diff.days / 365
+
+@register.simple_tag
+def get_total_count(up_count, down_count):
+ return up_count + down_count
+
+@register.simple_tag
+def format_number(value):
+ strValue = str(value)
+ if len(strValue) <= 3:
+ return strValue
+ result = ''
+ first = ''
+ pattern = re.compile('(-?\d+)(\d{3})')
+ m = re.match(pattern, strValue)
+ while m != None:
+ first = m.group(1)
+ second = m.group(2)
+ result = ',' + second + result
+ strValue = first + ',' + second
+ m = re.match(pattern, strValue)
+ return first + result
+
+@register.simple_tag
+def convert2tagname_list(question):
+ question['tagnames'] = [name for name in question['tagnames'].split(u' ')]
+ return ''
+
+@register.simple_tag
+def diff_date(date, limen=2):
+ current_time = datetime.datetime(*time.localtime()[0:6])
+ diff = current_time - date
+ diff_days = diff.days
+ if diff_days > limen:
+ return date
+ else:
+ return timesince(date) + u'鍓'
+
+@register.simple_tag
+def get_latest_changed_timestamp():
+ try:
+ from time import localtime, strftime
+ from os import path
+ from django.conf import settings
+ root = settings.SITE_SRC_ROOT
+ dir = (
+ root,
+ '%s/forum' % root,
+ '%s/templates' % root,
+ )
+ stamp = (path.getmtime(d) for d in dir)
+ latest = max(stamp)
+ timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest))
+ except:
+ timestr = ''
+ return timestr
\ No newline at end of file
diff --git a/forum/user.py b/forum/user.py
new file mode 100644
index 00000000..7afe1c36
--- /dev/null
+++ b/forum/user.py
@@ -0,0 +1,74 @@
+锘# coding=utf-8
+class UserView:
+ def __init__(self, id, tab_title, tab_description, page_title, view_name, template_file, data_size=0):
+ self.id = id
+ self.tab_title = tab_title
+ self.tab_description = tab_description
+ self.page_title = page_title
+ self.view_name = view_name
+ self.template_file = template_file
+ self.data_size = data_size
+
+
+USER_TEMPLATE_VIEWS = (
+ UserView(
+ id = 'stats',
+ tab_title = u'姒傝',
+ tab_description = u'鐢ㄦ埛姒傝',
+ page_title = u'姒傝-鐢ㄦ埛璧勬枡',
+ view_name = 'user_stats',
+ template_file = 'user_stats.html'
+ ),
+ UserView(
+ id = 'recent',
+ tab_title = u'鏈杩戞椿鍔',
+ tab_description = u'鐢ㄦ埛鏈杩戠殑娲诲姩鐘跺喌',
+ page_title = u'鏈杩戞椿鍔 - 鐢ㄦ埛璧勬枡',
+ view_name = 'user_recent',
+ template_file = 'user_recent.html',
+ data_size = 50
+ ),
+ UserView(
+ id = 'responses',
+ tab_title = u'鍥炲簲',
+ tab_description = u'鍏朵粬鐢ㄦ埛鐨勫洖澶嶅拰璇勮',
+ page_title = u'鍥炲簲 - 鐢ㄦ埛璧勬枡',
+ view_name = 'user_responses',
+ template_file = 'user_responses.html',
+ data_size = 50
+ ),
+ UserView(
+ id = 'reputation',
+ tab_title = u'绉垎',
+ tab_description = u'鐢ㄦ埛绀惧尯绉垎',
+ page_title = u'绉垎 - 鐢ㄦ埛璧勬枡',
+ view_name = 'user_reputation',
+ template_file = 'user_reputation.html'
+ ),
+ UserView(
+ id = 'favorites',
+ tab_title = u'鏀惰棌',
+ tab_description = u'鐢ㄦ埛鏀惰棌鐨勯棶棰',
+ page_title = u'鏀惰棌 - 鐢ㄦ埛璧勬枡',
+ view_name = 'user_favorites',
+ template_file = 'user_favorites.html',
+ data_size = 50
+ ),
+ UserView(
+ id = 'votes',
+ tab_title = u'鎶曠エ',
+ tab_description = u'鐢ㄦ埛鎵鏈夋姇绁',
+ page_title = u'鎶曠エ - 鐢ㄦ埛璧勬枡',
+ view_name = 'user_votes',
+ template_file = 'user_votes.html',
+ data_size = 50
+ ),
+ UserView(
+ id = 'preferences',
+ tab_title = u'璁剧疆',
+ tab_description = u'鐢ㄦ埛鍙傛暟璁剧疆',
+ page_title = u'璁剧疆 - 鐢ㄦ埛璧勬枡',
+ view_name = 'user_preferences',
+ template_file = 'user_preferences.html'
+ )
+)
\ No newline at end of file
diff --git a/forum/views.py b/forum/views.py
new file mode 100644
index 00000000..49ad6c19
--- /dev/null
+++ b/forum/views.py
@@ -0,0 +1,1945 @@
+# 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,Http404
+from django.core.paginator import Paginator, EmptyPage, InvalidPage
+from django.template import RequestContext
+from django.utils.html import *
+from django.utils import simplejson
+from django.core import serializers
+from django.db import transaction
+from django.contrib.contenttypes.models import ContentType
+
+from utils.html import sanitize_html
+from markdown2 import Markdown
+#from lxml.html.diff import htmldiff
+from forum.diff import textDiff as htmldiff
+from forum.forms import *
+from forum.models import *
+from forum.auth import *
+from forum.const import *
+from forum.user import *
+from forum import auth
+
+# 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 index(request):
+ view_id = request.GET.get('sort', None)
+ view_dic = {
+ "latest":"-last_activity_at",
+ "hottest":"-answer_count",
+ "mostvoted":"-score",
+ "trans": "-last_activity_at"
+ }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "latest"
+ orderby = "-last_activity_at"
+ # group questions by author_id of 28,29
+ if view_id == 'trans':
+ questions = Question.objects.get_translation_questions(orderby, INDEX_PAGE_SIZE)
+ else:
+ questions = Question.objects.get_questions_by_pagesize(orderby, INDEX_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()
+
+ return render_to_response('index.html', {
+ "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):
+ return render_to_response('faq.html', context_instance=RequestContext(request))
+
+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 "unanswered.html"
+ 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")
+ 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 tagname is not None:
+ objects = Question.objects.get_questions_by_tag(tagname, orderby)
+ elif unanswered:
+ #check if request is from unanswered questions
+ template_file = "unanswered.html"
+ objects = Question.objects.get_unanswered_questions(orderby)
+ else:
+ objects = Question.objects.get_questions(orderby)
+
+ # RISK - inner join queries
+ objects = objects.select_related(depth=1);
+ objects_list = Paginator(objects, pagesize)
+ questions = objects_list.page(page)
+
+ # Get related tags from this page objects
+ related_tags = Tag.objects.get_tags_by_questions(questions.object_list)
+
+ return render_to_response(template_file, {
+ "questions" : questions,
+ "tab_id" : view_id,
+ "questions_count" : objects_list.count,
+ "tags" : related_tags,
+ "searchtag" : tagname,
+ "is_unanswered" : unanswered,
+ "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))
+
+#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()
+ 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']
+ )
+
+ return HttpResponseRedirect(question.get_absolute_url())
+
+ else:
+ form = AskForm()
+
+ tags = _get_tags_cache_json()
+ return render_to_response('ask.html', {
+ 'form' : form,
+ 'tags' : tags,
+ }, 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', 'votes')
+ view_dic = {"latest":"-added_at", "oldest":"added_at", "votes":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "votes"
+ orderby = "-score"
+
+ 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)
+ answers = Answer.objects.get_answers_from_question(question, request.user)
+ answers = answers.select_related(depth=1)
+
+ favorited = question.has_favorite_by_user(request.user)
+ question_vote = question.votes.select_related().filter(user=request.user)
+ 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)
+ objects_list = Paginator(answers, ANSWERS_PAGE_SIZE)
+ page_objects = objects_list.page(page)
+ # update view count
+ Question.objects.update_view_count(question)
+ 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 = ('
%(title)s
\n'
+ '
%(html)s
\n'
+ '
%(tags)s
')
+def question_revisions(request, id):
+ post = get_object_or_404(Question, id=id)
+ revisions = list(post.revisions.all())
+ for i, revision in enumerate(revisions):
+ revision.html = QUESTION_REVISION_TEMPLATE % {
+ 'title': revision.title,
+ 'html': sanitize_html(markdowner.convert(revision.text)),
+ 'tags': ' '.join(['%s' % tag
+ for tag in revision.tagnames.split(' ')]),
+ }
+ if i > 0:
+ revisions[i - 1].diff = htmldiff(revision.html,
+ revisions[i - 1].html)
+ else:
+ revisions[i - 1].diff = QUESTION_REVISION_TEMPLATE % {
+ 'title': revisions[0].title,
+ 'html': sanitize_html(markdowner.convert(revisions[0].text)),
+ 'tags': ' '.join(['%s' % tag
+ for tag in revisions[0].tagnames.split(' ')]),
+ }
+ revisions[i - 1].summary = None
+ return render_to_response('revisions_question.html', {
+ 'post': post,
+ 'revisions': revisions,
+ }, context_instance=RequestContext(request))
+
+ANSWER_REVISION_TEMPLATE = ('
%(html)s
')
+def answer_revisions(request, id):
+ post = get_object_or_404(Answer, id=id)
+ revisions = list(post.revisions.all())
+ for i, revision in enumerate(revisions):
+ revision.html = ANSWER_REVISION_TEMPLATE % {
+ 'html': sanitize_html(markdowner.convert(revision.text))
+ }
+ if i > 0:
+ revisions[i - 1].diff = htmldiff(revision.html,
+ revisions[i - 1].html)
+ else:
+ revisions[i - 1].diff = revisions[i-1].text
+ revisions[i - 1].summary = None
+ return render_to_response('revisions_answer.html', {
+ 'post': post,
+ 'revisions': revisions,
+ }, context_instance=RequestContext(request))
+
+#TODO: allow anynomus
+@login_required
+def answer(request, id):
+ question = get_object_or_404(Question, id=id)
+ if request.method == "POST":
+ form = AnswerForm(question, request.POST)
+ if form.is_valid():
+ update_time = datetime.datetime.now()
+ answer = Answer(
+ question = question,
+ author = request.user,
+ added_at = update_time,
+ wiki = form.cleaned_data['wiki'],
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text'])),
+ )
+ if answer.wiki:
+ answer.last_edited_by = answer.author
+ answer.last_edited_at = update_time
+ answer.wikified_at = update_time
+
+ answer.save()
+ Question.objects.update_answer_count(question)
+
+ question = get_object_or_404(Question, id=id)
+ question.last_activity_at = update_time
+ question.last_activity_by = request.user
+ question.save()
+
+ AnswerRevision.objects.create(
+ answer = answer,
+ revision = 1,
+ author = request.user,
+ revised_at = update_time,
+ summary = CONST['default_version'],
+ text = form.cleaned_data['text']
+ )
+
+ 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 is not None:
+ 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' : '/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
+
+ 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:
+ 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)
+ 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")
+
+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 = '/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 = '/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 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():
+ user.email = sanitize_html(form.cleaned_data['email'])
+ 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,
+ }, 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 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']
+ tags = user.created_tags.all().order_by('-used_count')[:50]
+ 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']
+
+ 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,
+ "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,
+ "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
+ self.title_link = u'/questions/%s/%s#%s' %(question_id, title, answer_id)\
+ if int(answer_id) > 0 else u'/questions/%s/%s' %(question_id, title)
+ 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 \
+ 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 activity.user_id=%s AND activity.activity_type=%s'],
+ 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'],
+ 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'],
+ 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'],
+ where=['activity.content_type_id = %s AND activity.object_id = question_revision.id 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 \
+ 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.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 = u'/questions/%s/%s#%s' % (question_id, title, answer_id)
+ self.time = time
+ self.userlink = u'/users/%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)
+ 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_preferences(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ 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,
+ }, 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', user)
+
+def answer_comments(request, id):
+ answer = get_object_or_404(Answer, id=id)
+ user = request.user
+ return __comments(request, answer, 'answer', user)
+
+def __comments(request, obj, type, user):
+ # only support get comments by ajax now
+ if request.is_ajax():
+ if request.method == "GET":
+ return __generate_comments_json(obj, type, user)
+ elif request.method == "POST":
+ 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()
+ return __generate_comments_json(obj, type, user)
+
+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 = []
+ 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
+ delete_url = "/" + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id)
+ json_comments.append({"id" : comment.id,
+ "object_id" : obj.id,
+ "add_date" : comment.added_at.strftime('%Y-%m-%d'),
+ "text" : comment.comment,
+ "user_display_name" : comment_user.username,
+ "user_url" : "/users/%s/%s" % (comment_user.id, comment_user.username),
+ "delete_url" : delete_url
+ })
+
+ data = simplejson.dumps(json_comments)
+ return HttpResponse(data, mimetype="application/json")
+
+def delete_question_comment(request, question_id, comment_id):
+ if request.is_ajax():
+ question = get_object_or_404(Question, id=question_id)
+ comment = get_object_or_404(Comment, id=comment_id)
+
+ question.comments.remove(comment)
+ question.comment_count = question.comment_count - 1
+ question.save()
+ user = request.user
+ return __generate_comments_json(question, 'question', user)
+
+def delete_answer_comment(request, answer_id, comment_id):
+ if request.is_ajax():
+ answer = get_object_or_404(Answer, id=answer_id)
+ comment = get_object_or_404(Comment, id=comment_id)
+
+ answer.comments.remove(comment)
+ answer.comment_count = answer.comment_count - 1
+ answer.save()
+ user = request.user
+ return __generate_comments_json(answer, 'answer', user)
+
+def logout(request):
+ url = request.GET.get('next')
+ return render_to_response('logout.html', {
+ 'next' : url,
+ }, 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,
+ }, 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]
+ ).values('id').distinct()
+
+ 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
+
+ #%s
+ xml_template = "%s"
+
+ 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
+
+ # genetate 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 % ('', u"涓婁紶鍥剧墖鍙檺浜庣Н鍒+60浠ヤ笂娉ㄥ唽鐢ㄦ埛!", '')
+ except FileTypeNotAllow:
+ result = xml_template % ('', u"鍙厑璁镐笂浼'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'绫诲瀷鐨勬枃浠讹紒", '')
+ except FileSizeNotAllow:
+ result = xml_template % ('', u"鍙厑璁镐笂浼%sK澶у皬鐨勬枃浠讹紒" % settings.ALLOW_MAX_FILE_SIZE / 1024, '')
+ except Exception:
+ result = xml_template % ('', u"鍦ㄦ枃浠朵笂浼犺繃绋嬩腑浜х敓浜嗛敊璇紝璇疯仈绯荤鐞嗗憳锛岃阿璋_^", '')
+
+ return HttpResponse(result, mimetype="application/xml")
+
+def books(request):
+ return HttpResponseRedirect("/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,
+ }, 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('/')
+ if search_type == 'tag':
+ return HttpResponseRedirect('/tags/?q=%s&page=%s' % (keywords.strip(), page))
+ elif search_type == "user":
+ return HttpResponseRedirect('/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"
+
+ 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)
+
+ 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,
+ "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/locale/zh_cn/LC_MESSAGES/django.mo b/locale/zh_cn/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..f8f70df4
Binary files /dev/null and b/locale/zh_cn/LC_MESSAGES/django.mo differ
diff --git a/locale/zh_cn/LC_MESSAGES/django.po b/locale/zh_cn/LC_MESSAGES/django.po
new file mode 100644
index 00000000..07eaa93d
--- /dev/null
+++ b/locale/zh_cn/LC_MESSAGES/django.po
@@ -0,0 +1,416 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-12-31 16:00+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: .\settings.py:32
+msgid "account/"
+msgstr ""
+
+#: .\settings.py:32 .\django_authopenid\urls.py:9
+#: .\django_authopenid\urls.py:11
+msgid "signin/"
+msgstr ""
+
+#: .\django_authopenid\forms.py:67 .\django_authopenid\views.py:93
+msgid "i-names are not supported"
+msgstr "i-names涓嶆敮鎸併"
+
+#: .\django_authopenid\forms.py:100 .\django_authopenid\forms.py:156
+#: .\django_authopenid\forms.py:205
+msgid ""
+"Usernames can only contain letters, numbers and "
+"underscores"
+msgstr "鐢ㄦ埛鍚嶆牸寮忔湁璇傚彧鏈夊瓧姣嶏紝鏁板瓧鍜屼笅鍒掔嚎鏄厑璁哥殑銆"
+
+#: .\django_authopenid\forms.py:107
+msgid ""
+"This username does not exist in our database. Please "
+"choose another."
+msgstr "鐢ㄦ埛鍚嶄笉瀛樺湪銆傝閲嶆柊杈撳叆銆"
+
+#: .\django_authopenid\forms.py:124 .\django_authopenid\forms.py:229
+msgid ""
+"Please enter a valid username and password. Note that "
+"both fields are case-sensitive."
+msgstr "璇疯緭鍏ョ敤鎴峰悕鍜屽瘑鐮併傛敞鎰忓尯鍒嗗ぇ灏忓啓銆"
+
+#: .\django_authopenid\forms.py:128 .\django_authopenid\forms.py:233
+msgid "This account is inactive."
+msgstr "鐢ㄦ埛宸插喕缁撱"
+
+#: .\django_authopenid\forms.py:168
+msgid "This username is already taken. Please choose another."
+msgstr "鐢ㄦ埛鍚嶅凡缁忚娉ㄥ唽锛岃閫夌敤涓涓柊鐨勫笎鍙枫"
+
+#: .\django_authopenid\forms.py:182
+msgid ""
+"This email is already registered in our database. Please "
+"choose another."
+msgstr "鐢靛瓙閭欢宸茶娉ㄥ唽銆傝浣跨敤涓涓柊鐨勯偖浠跺湴鍧銆"
+
+#: .\django_authopenid\forms.py:212
+msgid ""
+"This username don't exist. Please choose another."
+msgstr "鐢ㄦ埛鍚嶄笉瀛樺湪"
+
+#: .\django_authopenid\forms.py:328
+msgid ""
+"Old password is incorrect. Please enter the correct "
+"password."
+msgstr "鏃у瘑鐮侀敊璇"
+
+#: .\django_authopenid\forms.py:340
+msgid "new passwords do not match"
+msgstr "鏂板瘑鐮佷笉鍖归厤"
+
+#: .\django_authopenid\forms.py:432
+msgid "Incorrect username."
+msgstr "鐢ㄦ埛鍚嶄笉姝g‘"
+
+#: .\django_authopenid\urls.py:10
+msgid "signout/"
+msgstr ""
+
+#: .\django_authopenid\urls.py:11
+msgid "complete/"
+msgstr ""
+
+#: .\django_authopenid\urls.py:13
+msgid "register/"
+msgstr ""
+
+#: .\django_authopenid\urls.py:14
+msgid "signup/"
+msgstr ""
+
+#: .\django_authopenid\urls.py:15
+msgid "sendpw/"
+msgstr ""
+
+#: .\django_authopenid\urls.py:16 .\django_authopenid\urls.py:21
+msgid "password/"
+msgstr ""
+
+#: .\django_authopenid\urls.py:16
+msgid "confirm/"
+msgstr ""
+
+#: .\django_authopenid\urls.py:22
+msgid "email/"
+msgstr ""
+
+#: .\django_authopenid\urls.py:23
+msgid "openid/"
+msgstr ""
+
+#: .\django_authopenid\urls.py:24
+msgid "delete/"
+msgstr ""
+
+#: .\django_authopenid\views.py:99
+#, python-format
+msgid "闈炴硶OpenID鍦板潃锛 %s"
+msgstr ""
+
+#: .\django_authopenid\views.py:366
+msgid "Welcome"
+msgstr "娆㈣繋"
+
+#: .\django_authopenid\views.py:456
+msgid "Password changed."
+msgstr "瀵嗙爜宸叉洿鏂般"
+
+#: .\django_authopenid\views.py:488
+msgid "Email changed."
+msgstr "閭欢鍦板潃宸叉洿鏂般"
+
+#: .\django_authopenid\views.py:519 .\django_authopenid\views.py:671
+#, python-format
+msgid "No OpenID %s found associated in our database"
+msgstr "璇penID %s 涓嶅湪绯荤粺涓"
+
+#: .\django_authopenid\views.py:523 .\django_authopenid\views.py:678
+#, python-format
+msgid "The OpenID %s isn't associated to current user logged in"
+msgstr "OpenID %s 娌℃湁鍜屽綋鍓嶇櫥褰曠敤鎴风粦瀹氥"
+
+#: .\django_authopenid\views.py:531
+msgid "Email Changed."
+msgstr "閭欢鍦板潃宸叉洿鏂般"
+
+#: .\django_authopenid\views.py:606
+msgid "This OpenID is already associated with another account."
+msgstr "杩欎釜OpenID宸茬粡缁戝畾鍒板彟澶栦竴涓笎鍙枫"
+
+#: .\django_authopenid\views.py:611
+#, python-format
+msgid "OpenID %s is now associated with your account."
+msgstr "OpenID %s 宸茬粡缁戝畾鍒版偍鐨勫笎鍙枫"
+
+#: .\django_authopenid\views.py:681
+msgid "Account deleted."
+msgstr "甯愬彿宸插垹闄ゃ"
+
+#: .\django_authopenid\views.py:721
+msgid "Request for new password"
+msgstr "鎵惧洖瀵嗙爜"
+
+#: .\django_authopenid\views.py:734
+msgid "A new password has been sent to your email address."
+msgstr "鏂扮殑瀵嗙爜宸茬粡鍙戦佸埌鎮ㄧ殑閭欢甯愬彿銆"
+
+#: .\django_authopenid\views.py:764
+#, python-format
+msgid ""
+"Could not change password. Confirmation key '%s' is not "
+"registered."
+msgstr "涓嶈兘淇敼瀵嗙爜銆傜‘璁や俊鎭 '%s' 鏈夎銆"
+
+#: .\django_authopenid\views.py:773
+msgid ""
+"Can not change password. User don't exist anymore in our "
+"database."
+msgstr "涓嶈兘淇敼瀵嗙爜銆傜敤鎴峰笎鍙蜂笉瀛樺湪銆"
+
+#: .\django_authopenid\views.py:782
+#, python-format
+msgid "Password changed for %s. You may now sign in."
+msgstr "甯愬彿 %s 鐨勫瘑鐮佸凡缁忎慨鏀广傛偍鐜板湪鍙互鐢ㄥ畠鏉ョ櫥褰曘"
+
+#: .\templates\authopenid\changeemail.html.py:8
+msgid "Account: change email"
+msgstr "淇敼鐢靛瓙閭欢"
+
+#: .\templates\authopenid\changeemail.html.py:10
+msgid ""
+"This is where you can change the email address associated with your account. "
+"Please keep this email address up to date so we can send you a password-"
+"reset email if you request one."
+msgstr ""
+"鎮ㄥ彲浠ュ湪杩欓噷淇敼鎮ㄧ殑鐢靛瓙閭欢锛岃纭繚杩欎釜閭欢鍦板潃鏈夋晥-鎵惧洖瀵嗙爜灏嗗彂閫佹柊瀵嗙爜鍒版偍"
+"鐨勯偖浠跺湴鍧銆"
+
+#: .\templates\authopenid\changeemail.html.py:12
+#: .\templates\authopenid\changeopenid.html.py:9
+#: .\templates\authopenid\changepw.html.py:15
+#: .\templates\authopenid\complete.html.py:26
+#: .\templates\authopenid\complete.html.py:36
+#: .\templates\authopenid\delete.html.py:10
+#: .\templates\authopenid\delete.html.py:20
+#: .\templates\authopenid\sendpw.html.py:11
+#: .\templates\authopenid\signup.html.py:15
+msgid "Please correct errors below:"
+msgstr "璇锋敼姝d互涓嬮敊璇細"
+
+#: .\templates\authopenid\changeemail.html.py:29
+#: .\templates\authopenid\complete.html.py:52
+msgid "Email"
+msgstr "鐢靛瓙閭欢"
+
+#: .\templates\authopenid\changeemail.html.py:30
+#: .\templates\authopenid\complete.html.py:68
+msgid "Password"
+msgstr "瀵嗙爜"
+
+#: .\templates\authopenid\changeemail.html.py:32
+msgid "Change email"
+msgstr "淇敼鐢靛瓙閭欢"
+
+#: .\templates\authopenid\changeopenid.html.py:5
+msgid "Account: change OpenID URL"
+msgstr "淇敼OpenID鍦板潃"
+
+#: .\templates\authopenid\changeopenid.html.py:7
+msgid ""
+"This is where you can change your OpenID URL. Make sure you remember it!"
+msgstr "璇蜂慨鏀规偍鐨凮penID鍦板潃锛岃涓嶈蹇樿杩欎釜鍦板潃锛"
+
+#: .\templates\authopenid\changeopenid.html.py:24
+msgid "OpenID URL:"
+msgstr "OpenID鍦板潃锛"
+
+#: .\templates\authopenid\changeopenid.html.py:25
+msgid "Change OpenID"
+msgstr "淇敼OpenID"
+
+#: .\templates\authopenid\changepw.html.py:11
+msgid "Account: change password"
+msgstr "淇敼瀵嗙爜"
+
+#: .\templates\authopenid\changepw.html.py:13
+msgid "This is where you can change your password. Make sure you remember it!"
+msgstr "璇蜂慨鏀规偍鐨勫瘑鐮侊紝鍒囪涓嶈蹇樿锛"
+
+#: .\templates\authopenid\changepw.html.py:23
+msgid "Current password"
+msgstr "鏃у瘑鐮"
+
+#: .\templates\authopenid\changepw.html.py:24
+msgid "New password"
+msgstr "鏂板瘑鐮"
+
+#: .\templates\authopenid\changepw.html.py:25
+msgid "New password again"
+msgstr "閲嶅瀵嗙爜"
+
+#: .\templates\authopenid\changepw.html.py:26
+msgid "Change password"
+msgstr "淇敼瀵嗙爜"
+
+#: .\templates\authopenid\complete.html.py:12
+msgid "Your OpenID is verified! "
+msgstr "鎮ㄧ殑OpenID甯愬彿宸茬粡楠岃瘉閫氳繃"
+
+
+#: .\templates\authopenid\complete.html.py:17
+msgid "Associate your OpenID"
+msgstr "缁戝畾鎮ㄧ殑OpenID"
+
+#: .\templates\authopenid\complete.html.py:18
+msgid ""
+"\n"
+"\t
If you're joining Sitename, associate your OpenID with "
+"a new account. If you're already a member, associate with your existing "
+"account.
\n"
+"\t"
+msgstr ""
+"\n"
+"\t
杈撳叆鎮ㄧ殑鏂板笎鍙锋垨鑰呮寚瀹氬凡缁忓瓨鍦ㄧ殑甯愬彿銆
\n"
+"\t"
+
+#: .\templates\authopenid\complete.html.py:50
+msgid "A new account"
+msgstr "鏂板笎鍙"
+
+#: .\templates\authopenid\complete.html.py:51
+#: .\templates\authopenid\complete.html.py:67
+#: .\templates\authopenid\sendpw.html.py:24
+msgid "Username"
+msgstr "鐢ㄦ埛鍚"
+
+#: .\templates\authopenid\complete.html.py:66
+msgid "An exisiting account"
+msgstr "宸茬粡瀛樺湪鐨勫笎鍙"
+
+#: .\templates\authopenid\delete.html.py:6
+msgid "Account: delete account"
+msgstr "鍒犻櫎甯愬彿"
+
+#: .\templates\authopenid\delete.html.py:8
+msgid ""
+"Note: After deleting your account, anyone will be able to register this "
+"username."
+msgstr "娉ㄦ剰锛氬垹闄ゆ偍鐨勫笎鍙峰悗锛屼换浣曞叾浠栦汉鍙互鍐嶆敞鍐岃繖涓笎鍙枫"
+
+#: .\templates\authopenid\delete.html.py:12
+msgid "Check confirm box, if you want delete your account."
+msgstr "濡傛灉纭畾鍒犻櫎锛岃閫変腑澶氶夋銆"
+
+#: .\templates\authopenid\delete.html.py:15
+msgid "Password:"
+msgstr "瀵嗙爜锛"
+
+#: .\templates\authopenid\delete.html.py:27
+msgid "I am sure I want to delete my account."
+msgstr "鎴戠‘璁よ鍒犻櫎杩欎釜甯愬彿銆"
+
+#: .\templates\authopenid\delete.html.py:28
+msgid "Password/OpenID URL"
+msgstr "瀵嗙爜/OpenID鍦板潃"
+
+#: .\templates\authopenid\delete.html.py:28
+msgid "(required for your security)"
+msgstr "锛堝繀闇锛"
+
+#: .\templates\authopenid\delete.html.py:30
+msgid "Delete account permanently"
+msgstr "姘镐箙鍒犻櫎甯愬彿"
+
+#: .\templates\authopenid\sendpw.html.py:7
+msgid "Account: Send a new password"
+msgstr "鍙戦佷竴涓柊鐨勫瘑鐮"
+
+#: .\templates\authopenid\sendpw.html.py:9
+msgid ""
+"Lost your password ? Here you can ask to reset your password. Enter the "
+"username you use and you will get a confirmation email with your new "
+"password. This new password will be activated only after you have clicked on "
+"the link in the email."
+msgstr ""
+"涓㈠け浜嗘偍鐨勫瘑鐮侊紵浣犲彲浠ュ湪杩欓噷閲嶈瀵嗙爜銆傝緭鍏ョ敤鎴峰悕浣犱細鏀跺埌鏂扮殑瀵嗙爜鐨勯偖浠躲傚瘑鐮"
+"鍙湁鎮ㄥ湪婵娲婚偖浠朵腑鐨勯摼鎺ユ墠浼氳婵娲汇"
+
+#: .\templates\authopenid\sendpw.html.py:26
+msgid "Send new password"
+msgstr "鍙戦佹柊瀵嗙爜"
+
+#: .\templates\authopenid\settings.html.py:29
+msgid "Give your account a new password."
+msgstr "淇敼瀵嗙爜"
+
+#: .\templates\authopenid\settings.html.py:31
+msgid "Add or update the email address associated with your account."
+msgstr "娣诲姞鎴栬呮洿鏂版偍鐨勯偖浠跺湴鍧銆"
+
+#: .\templates\authopenid\settings.html.py:34
+msgid "Change openid associated to your account"
+msgstr "淇敼鍜屼綘甯愬彿缁戝畾鐨凮penID鍦板潃"
+
+#: .\templates\authopenid\settings.html.py:38
+msgid "Erase your username and all your data from website"
+msgstr "鍒犻櫎鎮ㄧ殑甯愬彿鍜屾墍鏈夊唴瀹"
+
+#: .\templates\authopenid\signup.html.py:7
+msgid ""
+"\n"
+"
Join
\n"
+"
There are two ways to join: with an email + username, or with OpenID."
+" Enter information only for the type of sign up you want to do.
\n"
+" "
+msgstr ""
+
+#: .\templates\authopenid\signup.html.py:13
+msgid "Regular Signup"
+msgstr ""
+
+#: .\templates\authopenid\signup.html.py:29
+msgid "Choose a Username:"
+msgstr ""
+
+#: .\templates\authopenid\signup.html.py:31
+msgid "Enter Your Email Address:"
+msgstr ""
+
+#: .\templates\authopenid\signup.html.py:32
+msgid "Choose a Password:"
+msgstr ""
+
+#: .\templates\authopenid\signup.html.py:33
+msgid "Confirm Your Password:"
+msgstr ""
+
+#: .\templates\authopenid\signup.html.py:35
+msgid "Sign up"
+msgstr ""
+
+#: .\templates\authopenid\signup.html.py:39
+msgid "OpenID Signup"
+msgstr ""
+
+#: .\templates\authopenid\signup.html.py:42
+msgid "Sign in with OpenID"
+msgstr ""
diff --git a/manage.py b/manage.py
new file mode 100644
index 00000000..b8c4be8e
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/middleware/__init__.py b/middleware/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/middleware/pagesize.py b/middleware/pagesize.py
new file mode 100644
index 00000000..bb6c7aa3
--- /dev/null
+++ b/middleware/pagesize.py
@@ -0,0 +1,29 @@
+# used in questions
+QUESTIONS_PAGE_SIZE = 10
+class QuestionsPageSizeMiddleware(object):
+ def process_request(self, request):
+ # Set flag to False by default. If it is equal to True, then need to be saved.
+ pagesize_changed = False
+ # get pagesize from session, if failed then get default value
+ user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
+ # set pagesize equal to logon user specified value in database
+ if request.user.is_authenticated() and request.user.questions_per_page > 0:
+ user_page_size = request.user.questions_per_page
+
+ try:
+ # get new pagesize from UI selection
+ pagesize = int(request.GET.get('pagesize', user_page_size))
+ if pagesize <> user_page_size:
+ pagesize_changed = True
+
+ except ValueError:
+ pagesize = user_page_size
+
+ # save this pagesize to user database
+ if pagesize_changed:
+ if request.user.is_authenticated():
+ user = request.user
+ user.questions_per_page = pagesize
+ user.save()
+ # put pagesize into session
+ request.session["pagesize"] = pagesize
\ No newline at end of file
diff --git a/settings.py b/settings.py
new file mode 100644
index 00000000..4aa70254
--- /dev/null
+++ b/settings.py
@@ -0,0 +1,125 @@
+# Django settings for lanai project.
+import os.path
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+#David Cramer debug toolbar
+INTERNAL_IPS = ('127.0.0.1',)
+DEBUG_TOOLBAR_PANELS = (
+ 'debug_toolbar.panels.sql.SQLDebugPanel',
+ 'debug_toolbar.panels.headers.HeaderDebugPanel',
+ 'debug_toolbar.panels.cache.CacheDebugPanel',
+ 'debug_toolbar.panels.profiler.ProfilerDebugPanel',
+ 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
+ 'debug_toolbar.panels.templates.TemplatesDebugPanel',
+)
+
+DEBUG_TOOLBAR_CONFIG = {
+ "INTERCEPT_REDIRECTS":False
+}
+
+#for OpenID auth
+ugettext = lambda s: s
+LOGIN_URL = '/%s%s' % (ugettext('account/'), ugettext('signin/'))
+
+#system will send admins email about error stacktrace if DEBUG=False
+ADMINS = (
+ ('Mike Chen', 'chagel@gmail.com'),
+)
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = 'mysql' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
+
+SERVER_EMAIL = 'webmaster@cnprog.com'
+DEFAULT_FROM_EMAIL = 'webmaster@cnprog.com'
+EMAIL_HOST_USER = ''
+EMAIL_HOST_PASSWORD = ''
+EMAIL_SUBJECT_PREFIX = '[cnprog.com]'
+EMAIL_HOST='smtp.gmail.com'
+EMAIL_PORT='587'
+EMAIL_USE_TLS=True
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'Asia/Chongqing Asia/Chungking'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+#LANGUAGE_CODE = 'en-us'
+LANGUAGE_CODE = 'zh-cn'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/admin/media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '$oo^&_m&qwbib=(_4m_n*zn-d=g#s0he5fx9xonnym#8p6yigm'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.gzip.GZipMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.middleware.transaction.TransactionMiddleware',
+ #'django.middleware.sqlprint.SqlPrintingMiddleware',
+ 'middleware.pagesize.QuestionsPageSizeMiddleware',
+ #'debug_toolbar.middleware.DebugToolbarMiddleware',
+)
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+ 'django.core.context_processors.request',
+ 'django.core.context_processors.auth',
+)
+
+ROOT_URLCONF = 'urls'
+
+TEMPLATE_DIRS = (
+ os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
+)
+
+FILE_UPLOAD_TEMP_DIR = os.path.join(os.path.dirname(__file__), 'tmp').replace('\\','/')
+FILE_UPLOAD_HANDLERS = ("django.core.files.uploadhandler.MemoryFileUploadHandler",
+ "django.core.files.uploadhandler.TemporaryFileUploadHandler",)
+DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
+# for user upload
+ALLOW_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
+# unit byte
+ALLOW_MAX_FILE_SIZE = 1024 * 1024
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.admin',
+ 'django.contrib.humanize',
+ 'forum',
+ 'django_authopenid',
+ 'debug_toolbar' ,
+)
+
+# User settings
+from settings_local import *
+
diff --git a/settings_local.py.dist b/settings_local.py.dist
new file mode 100644
index 00000000..96f28b4e
--- /dev/null
+++ b/settings_local.py.dist
@@ -0,0 +1,21 @@
+SITE_SRC_ROOT = '/Users/sailing/Development/cnprog_beta2'
+
+#for logging
+import logging
+LOG_FILENAME = '/Users/sailing/Development/cnprog_beta2/django.lanai.log'
+logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)
+
+
+DATABASE_NAME = 'cnprog' # Or path to database file if using sqlite3.
+DATABASE_USER = 'root' # Not used with sqlite3.
+DATABASE_PASSWORD = '' # Not used with sqlite3.
+
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = '/Users/sailing/Development/cnprog_beta2/templates/upfiles/'
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = 'http://127.0.0.1:8000/upfiles/'
diff --git a/sql_scripts/cnprog.xml b/sql_scripts/cnprog.xml
new file mode 100644
index 00000000..95f9b362
--- /dev/null
+++ b/sql_scripts/cnprog.xml
@@ -0,0 +1,1498 @@
+
+
+
+
+
+/Users/sailing/Development/cnprog_beta2/sql_scripts
+
+
+ENGINE=MyISAM AUTO_INCREMENT=103 DEFAULT CHARSET=latin1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+content_type_id
+
+
+user_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+author_id
+
+
+deleted_by_id
+
+
+last_edited_by_id
+
+
+locked_by_id
+
+
+question_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+answer_id
+
+
+author_id
+
+
+
+
+ENGINE=InnoDB DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+name
+
+
+
+
+ENGINE=InnoDB DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+group_id, permission_id
+
+
+permission_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+user_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+content_type_id
+
+
+content_type_id, codename
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+username
+
+
+
+
+ENGINE=InnoDB DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+group_id
+
+
+user_id, group_id
+
+
+
+
+ENGINE=InnoDB DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+permission_id
+
+
+user_id, permission_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+badge_id
+
+
+user_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+slug
+
+
+name, type
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+short_name
+
+
+user_id
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+user_id
+
+
+book_id
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+user_id
+
+
+book_id
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+book_id
+
+
+question_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+content_type_id
+
+
+user_id
+
+
+content_type_id, object_id, user_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+content_type_id
+
+
+user_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+user_id
+
+
+
+
+ENGINE=InnoDB DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+user_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+app_label, model
+
+
+
+
+ENGINE=InnoDB DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+question_id
+
+
+user_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+content_type_id, object_id, user_id
+
+
+content_type_id
+
+
+user_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+author_id
+
+
+closed_by_id
+
+
+deleted_by_id
+
+
+last_activity_by_id
+
+
+last_edited_by_id
+
+
+locked_by_id
+
+
+
+
+ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+author_id
+
+
+question_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+question_id, tag_id
+
+
+tag_id
+
+
+
+
+ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=latin1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+question_id
+
+
+user_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+name
+
+
+created_by_id
+
+
+
+
+ENGINE=InnoDB DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+user_id
+
+
+badge_id
+
+
+
+
+ENGINE=InnoDB DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+user_id
+
+
+question_id
+
+
+
+
+ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+content_type_id, object_id, user_id
+
+
+content_type_id
+
+
+user_id
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+db.doc.option.mgr
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sql_scripts/cnprog_new_install.sql b/sql_scripts/cnprog_new_install.sql
new file mode 100644
index 00000000..ac33a6ba
--- /dev/null
+++ b/sql_scripts/cnprog_new_install.sql
@@ -0,0 +1,811 @@
+-- MySQL Administrator dump 1.4
+--
+-- ------------------------------------------------------
+-- Server version 5.0.67
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+
+
+--
+-- Create schema cnprog
+--
+
+CREATE DATABASE IF NOT EXISTS cnprog;
+USE cnprog;
+
+--
+-- Definition of table `cnprog`.`answer`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`answer`;
+CREATE TABLE `cnprog`.`answer` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `added_at` datetime NOT NULL,
+ `wiki` tinyint(1) NOT NULL,
+ `wikified_at` datetime default NULL,
+ `accepted` tinyint(1) NOT NULL,
+ `deleted` tinyint(1) NOT NULL,
+ `deleted_by_id` int(11) default NULL,
+ `locked` tinyint(1) NOT NULL,
+ `locked_by_id` int(11) default NULL,
+ `locked_at` datetime default NULL,
+ `score` int(11) NOT NULL,
+ `vote_up_count` int(11) NOT NULL,
+ `vote_down_count` int(11) NOT NULL,
+ `comment_count` int(10) unsigned NOT NULL,
+ `offensive_flag_count` smallint(6) NOT NULL,
+ `last_edited_at` datetime default NULL,
+ `last_edited_by_id` int(11) default NULL,
+ `html` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `answer_question_id` (`question_id`),
+ KEY `answer_author_id` (`author_id`),
+ KEY `answer_deleted_by_id` (`deleted_by_id`),
+ KEY `answer_locked_by_id` (`locked_by_id`),
+ KEY `answer_last_edited_by_id` (`last_edited_by_id`),
+ CONSTRAINT `author_id_refs_id_192b0170` FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `deleted_by_id_refs_id_192b0170` FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `last_edited_by_id_refs_id_192b0170` FOREIGN KEY (`last_edited_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `locked_by_id_refs_id_192b0170` FOREIGN KEY (`locked_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `question_id_refs_id_7d6550c9` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`auth_group`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_group`;
+CREATE TABLE `cnprog`.`auth_group` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(80) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_group`
+--
+
+--
+-- Definition of table `cnprog`.`auth_group_permissions`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_group_permissions`;
+CREATE TABLE `cnprog`.`auth_group_permissions` (
+ `id` int(11) NOT NULL auto_increment,
+ `group_id` int(11) NOT NULL,
+ `permission_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `group_id` (`group_id`,`permission_id`),
+ KEY `permission_id_refs_id_5886d21f` (`permission_id`),
+ CONSTRAINT `group_id_refs_id_3cea63fe` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`),
+ CONSTRAINT `permission_id_refs_id_5886d21f` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_group_permissions`
+--
+
+--
+-- Definition of table `cnprog`.`auth_message`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_message`;
+CREATE TABLE `cnprog`.`auth_message` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `message` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `auth_message_user_id` (`user_id`),
+ CONSTRAINT `user_id_refs_id_650f49a6` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_message`
+--
+
+--
+-- Definition of table `cnprog`.`auth_permission`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_permission`;
+CREATE TABLE `cnprog`.`auth_permission` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(50) NOT NULL,
+ `content_type_id` int(11) NOT NULL,
+ `codename` varchar(100) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `content_type_id` (`content_type_id`,`codename`),
+ KEY `auth_permission_content_type_id` (`content_type_id`),
+ CONSTRAINT `content_type_id_refs_id_728de91f` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=76 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_permission`
+--
+INSERT INTO `cnprog`.`auth_permission` VALUES (1,'Can add permission',1,'add_permission'),
+ (2,'Can change permission',1,'change_permission'),
+ (3,'Can delete permission',1,'delete_permission'),
+ (4,'Can add group',2,'add_group'),
+ (5,'Can change group',2,'change_group'),
+ (6,'Can delete group',2,'delete_group'),
+ (7,'Can add user',3,'add_user'),
+ (8,'Can change user',3,'change_user'),
+ (9,'Can delete user',3,'delete_user'),
+ (10,'Can add message',4,'add_message'),
+ (11,'Can change message',4,'change_message'),
+ (12,'Can delete message',4,'delete_message'),
+ (13,'Can add content type',5,'add_contenttype'),
+ (14,'Can change content type',5,'change_contenttype'),
+ (15,'Can delete content type',5,'delete_contenttype'),
+ (16,'Can add session',6,'add_session'),
+ (17,'Can change session',6,'change_session'),
+ (18,'Can delete session',6,'delete_session'),
+ (19,'Can add site',7,'add_site'),
+ (20,'Can change site',7,'change_site'),
+ (21,'Can delete site',7,'delete_site'),
+ (25,'Can add answer',9,'add_answer'),
+ (26,'Can change answer',9,'change_answer'),
+ (27,'Can delete answer',9,'delete_answer'),
+ (28,'Can add comment',10,'add_comment'),
+ (29,'Can change comment',10,'change_comment'),
+ (30,'Can delete comment',10,'delete_comment'),
+ (31,'Can add tag',11,'add_tag'),
+ (32,'Can change tag',11,'change_tag'),
+ (33,'Can delete tag',11,'delete_tag'),
+ (37,'Can add nonce',13,'add_nonce'),
+ (38,'Can change nonce',13,'change_nonce'),
+ (39,'Can delete nonce',13,'delete_nonce'),
+ (40,'Can add association',14,'add_association'),
+ (41,'Can change association',14,'change_association'),
+ (42,'Can delete association',14,'delete_association'),
+ (43,'Can add nonce',15,'add_nonce'),
+ (44,'Can change nonce',15,'change_nonce'),
+ (45,'Can delete nonce',15,'delete_nonce'),
+ (46,'Can add association',16,'add_association'),
+ (47,'Can change association',16,'change_association'),
+ (48,'Can delete association',16,'delete_association'),
+ (49,'Can add user association',17,'add_userassociation'),
+ (50,'Can change user association',17,'change_userassociation'),
+ (51,'Can delete user association',17,'delete_userassociation'),
+ (52,'Can add user password queue',18,'add_userpasswordqueue'),
+ (53,'Can change user password queue',18,'change_userpasswordqueue'),
+ (54,'Can delete user password queue',18,'delete_userpasswordqueue'),
+ (55,'Can add log entry',19,'add_logentry'),
+ (56,'Can change log entry',19,'change_logentry'),
+ (57,'Can delete log entry',19,'delete_logentry'),
+ (58,'Can add question',20,'add_question'),
+ (59,'Can change question',20,'change_question'),
+ (60,'Can delete question',20,'delete_question'),
+ (61,'Can add vote',21,'add_vote'),
+ (62,'Can change vote',21,'change_vote'),
+ (63,'Can delete vote',21,'delete_vote'),
+ (64,'Can add flagged item',22,'add_flaggeditem'),
+ (65,'Can change flagged item',22,'change_flaggeditem'),
+ (66,'Can delete flagged item',22,'delete_flaggeditem'),
+ (67,'Can add favorite question',23,'add_favoritequestion'),
+ (68,'Can change favorite question',23,'change_favoritequestion'),
+ (69,'Can delete favorite question',23,'delete_favoritequestion'),
+ (70,'Can add badge',24,'add_badge'),
+ (71,'Can change badge',24,'change_badge'),
+ (72,'Can delete badge',24,'delete_badge'),
+ (73,'Can add award',25,'add_award'),
+ (74,'Can change award',25,'change_award'),
+ (75,'Can delete award',25,'delete_award');
+
+--
+-- Definition of table `cnprog`.`auth_user`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_user`;
+CREATE TABLE `cnprog`.`auth_user` (
+ `id` int(11) NOT NULL auto_increment,
+ `username` varchar(30) NOT NULL,
+ `first_name` varchar(30) NOT NULL,
+ `last_name` varchar(30) NOT NULL,
+ `email` varchar(75) NOT NULL,
+ `password` varchar(128) NOT NULL,
+ `is_staff` tinyint(1) NOT NULL,
+ `is_active` tinyint(1) NOT NULL,
+ `is_superuser` tinyint(1) NOT NULL,
+ `last_login` datetime NOT NULL,
+ `date_joined` datetime NOT NULL,
+ `gold` smallint(6) NOT NULL default '0',
+ `silver` smallint(5) unsigned NOT NULL default '0',
+ `bronze` smallint(5) unsigned NOT NULL default '0',
+ `reputation` int(10) unsigned default '1',
+ `gravatar` varchar(128) default NULL,
+ `questions_per_page` smallint(5) unsigned default '10',
+ `last_seen` datetime default NULL,
+ `real_name` varchar(100) default NULL,
+ `website` varchar(200) default NULL,
+ `location` varchar(100) default NULL,
+ `date_of_birth` datetime default NULL,
+ `about` text,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `username` (`username`)
+) ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_user`
+--
+INSERT INTO `cnprog`.`auth_user` VALUES (2,'chagel','','','chagel@gmail.com','sha1$6a2fb$0d2ffe90bcba542fc962f57967a88e507799cc74',1,1,1,'2008-12-16 15:35:17','2008-12-11 20:12:53',0,0,0,1,'8c1efc4f4618aa68b18c88f2bcaa5564',10,NULL,NULL,NULL,NULL,NULL,NULL),
+ (3,'mike','','','ichagel@yahoo.com','sha1$f7ef5$1015ae6b2c8a2774a028419d3c57e13145b83284',0,1,0,'2008-12-15 12:56:23','2008-12-15 12:56:23',0,0,0,1,NULL,10,NULL,NULL,NULL,NULL,NULL,NULL),
+ (4,'sailingcai','','','sailingcai@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-23 06:14:45','2008-12-20 15:19:21',1,2,3,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','',NULL,''),
+ (5,'sailingcai1','','','1@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21',NULL,NULL,NULL,NULL,NULL),
+ (6,'sailing2','','','2@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (7,'sailing3','','','3@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (8,'sailing4','','','4@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (9,'sailing5','','','5@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (10,'sailing6','','','6@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (11,'sailing7','','','7@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (12,'sailing8','','','8@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (13,'sailing9','','','9@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (14,'sailing10','','','10@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (15,'sailing11','','','11@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (16,'sailing12','','','12@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (17,'sailing13','','','13@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (18,'sailing14','','','14@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (19,'sailing15','','','15@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (20,'sailing16','','','16@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (21,'sailing17','','','17@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (22,'sailing18','','','18@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (23,'sailing19','','','19@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (24,'sailing20','','','20@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (25,'sailing21','','','21@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (26,'sailing22','','','22@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (27,'sailing23','','','23@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (28,'sailing24','','','24@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (29,'sailing25','','','25@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (30,'sailing26','','','26@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (31,'sailing27','','','27@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (32,'sailing28','','','28@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (33,'sailing29','','','29@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (34,'sailing30','','','30@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (35,'sailing31','','','31@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (36,'sailing32','','','32@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (37,'sailing33','','','33@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (38,'sailing34','','','34@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (39,'sailing35','','','35@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (40,'sailing36','','','36@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (41,'sailing37','','','37@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (42,'sailing38','','','38@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (43,'sailing39','','','39@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (44,'sailing40','','','40@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (45,'sailing41','','','41@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (46,'sailing42','','','42@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (47,'sailing43','','','43@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (48,'sailing44','','','44@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (49,'sailing45','','','45@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (50,'sailing46','','','46@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (51,'sailing47','','','47@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (52,'sailing48','','','48@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (53,'sailing49','','','49@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (54,'sailing50','','','50@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (55,'sailing51','','','51@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (56,'sailing52','','','52@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (57,'sailing53','','','53@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (58,'sailing54','','','54@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (59,'sailing55','','','55@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (60,'sailing56','','','56@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (61,'sailing57','','','57@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (62,'sailing58','','','58@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (63,'sailing59','','','59@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (64,'sailing60','','','60@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (65,'sailing61','','','61@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (66,'sailing62','','','62@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (67,'sailing63','','','63@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (68,'sailing64','','','64@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (69,'sailing65','','','65@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (70,'sailing66','','','66@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (71,'sailing67','','','67@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (72,'sailing68','','','68@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (73,'sailing69','','','69@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (74,'sailing70','','','70@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (75,'sailing71','','','71@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (76,'sailing72','','','72@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (77,'sailing73','','','73@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (78,'sailing74','','','74@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (79,'sailing75','','','75@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (80,'sailing76','','','76@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (81,'sailing77','','','77@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (82,'sailing78','','','78@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (83,'sailing79','','','79@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (84,'sailing80','','','80@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (85,'sailing81','','','81@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (86,'sailing82','','','82@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (87,'sailing83','','','83@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (88,'sailing84','','','84@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (89,'sailing85','','','85@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (90,'sailing86','','','86@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (91,'sailing87','','','87@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (92,'sailing88','','','88@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (93,'sailing89','','','89@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (94,'sailing90','','','90@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (95,'sailing91','','','91@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (96,'sailing92','','','92@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (97,'sailing93','','','93@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (98,'sailing94','','','94@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (99,'sailing95','','','95@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (100,'sailing96','','','96@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (101,'sailing97','','','97@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (102,'sailing98','','','98@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (103,'sailing99','','','99@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00','');
+
+--
+-- Definition of table `cnprog`.`auth_user_groups`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_user_groups`;
+CREATE TABLE `cnprog`.`auth_user_groups` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `group_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`,`group_id`),
+ KEY `group_id_refs_id_f116770` (`group_id`),
+ CONSTRAINT `group_id_refs_id_f116770` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`),
+ CONSTRAINT `user_id_refs_id_7ceef80f` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_user_groups`
+--
+
+--
+-- Definition of table `cnprog`.`auth_user_user_permissions`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_user_user_permissions`;
+CREATE TABLE `cnprog`.`auth_user_user_permissions` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `permission_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`,`permission_id`),
+ KEY `permission_id_refs_id_67e79cb` (`permission_id`),
+ CONSTRAINT `permission_id_refs_id_67e79cb` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`),
+ CONSTRAINT `user_id_refs_id_dfbab7d` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_user_user_permissions`
+--
+
+--
+-- Definition of table `cnprog`.`award`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`award`;
+CREATE TABLE `cnprog`.`award` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `badge_id` int(11) NOT NULL,
+ `awarded_at` datetime NOT NULL,
+ `notified` tinyint(1) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `award_user_id` (`user_id`),
+ KEY `award_badge_id` (`badge_id`),
+ CONSTRAINT `badge_id_refs_id_651af0e1` FOREIGN KEY (`badge_id`) REFERENCES `badge` (`id`),
+ CONSTRAINT `user_id_refs_id_2d83e9b6` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`award`
+--
+
+--
+-- Definition of table `cnprog`.`badge`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`badge`;
+CREATE TABLE `cnprog`.`badge` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(50) NOT NULL,
+ `type` smallint(6) NOT NULL,
+ `slug` varchar(50) NOT NULL,
+ `description` varchar(300) NOT NULL,
+ `multiple` tinyint(1) NOT NULL,
+ `awarded_count` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`,`type`),
+ KEY `badge_slug` (`slug`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`badge`
+--
+
+--
+-- Definition of table `cnprog`.`comment`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`comment`;
+CREATE TABLE `cnprog`.`comment` (
+ `id` int(11) NOT NULL auto_increment,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `comment` varchar(300) NOT NULL,
+ `added_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `content_type_id` (`content_type_id`,`object_id`,`user_id`),
+ KEY `comment_content_type_id` (`content_type_id`),
+ KEY `comment_user_id` (`user_id`),
+ CONSTRAINT `content_type_id_refs_id_13a5866c` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_6be725e8` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`comment`
+--
+
+--
+-- Definition of table `cnprog`.`django_admin_log`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_admin_log`;
+CREATE TABLE `cnprog`.`django_admin_log` (
+ `id` int(11) NOT NULL auto_increment,
+ `action_time` datetime NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `content_type_id` int(11) default NULL,
+ `object_id` longtext,
+ `object_repr` varchar(200) NOT NULL,
+ `action_flag` smallint(5) unsigned NOT NULL,
+ `change_message` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `django_admin_log_user_id` (`user_id`),
+ KEY `django_admin_log_content_type_id` (`content_type_id`),
+ CONSTRAINT `content_type_id_refs_id_288599e6` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_c8665aa` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`django_admin_log`
+--
+INSERT INTO `cnprog`.`django_admin_log` VALUES (1,'2008-12-18 23:41:41',2,7,'1','cnprog.com',2,'宸蹭慨鏀 domain 鍜 name 銆');
+
+--
+-- Definition of table `cnprog`.`django_authopenid_association`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_authopenid_association`;
+CREATE TABLE `cnprog`.`django_authopenid_association` (
+ `id` int(11) NOT NULL auto_increment,
+ `server_url` longtext NOT NULL,
+ `handle` varchar(255) NOT NULL,
+ `secret` longtext NOT NULL,
+ `issued` int(11) NOT NULL,
+ `lifetime` int(11) NOT NULL,
+ `assoc_type` longtext NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`django_authopenid_association`
+--
+INSERT INTO `cnprog`.`django_authopenid_association` VALUES (2,'https://www.google.com/accounts/o8/ud','AOQobUfcCH4sgjsBGGscrzxIa5UM4clofAB6nixx8Qq_NWco4ynn_Kc4','u5cva43abzdwF8CJOFZfkzfk7x8=\n',1229022261,1229022261,'HMAC-SHA1'),
+ (3,'https://api.screenname.aol.com/auth/openidServer','diAyLjAgayAwIGJhT2VvYkdDZ21RSHJ4QldzQnhTdjIxV3BVbz0%3D-j5HRXRB1VbPyg48jGKE1Q70dfv76lGHEPwd9071%2FJ7f6SSw5YhakrwWpsVXtr34T6iHwPDdo6RU%3D','EmQL3+5oR6mFKIaeBNy6hXyUJ/w=\n',1229282202,1229282202,'HMAC-SHA1'),
+ (4,'https://open.login.yahooapis.com/openid/op/auth','JcBeY.uWXu2YjzbuCQiqFzAb0MIc7ATeKiPO4eAp3vluPMqZp_NCxepvMLGrJjxxDKTaNnr06wepMos8ap6SQYZiTi51tZ05lMWnpZAiOA1hsq_WMlEL7G9YE66GEA9A','QXiuN6B7E8nP5QhyHI3IB26t4SA=\n',1229282256,1229282256,'HMAC-SHA1'),
+ (5,'http://openid.claimid.com/server','{HMAC-SHA1}{494575fd}{uLEbxQ==}','GvPbkgMHh0QVPH7mStCGuWb2AKY=\n',1229288957,1229288957,'HMAC-SHA1'),
+ (6,'http://www.blogger.com/openid-server.g','oida-1229424484019-158830626','8gaU4aKnIFCLKIkHdxZQp7ZGNck=\n',1229424478,1229424478,'HMAC-SHA1');
+
+--
+-- Definition of table `cnprog`.`django_authopenid_nonce`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_authopenid_nonce`;
+CREATE TABLE `cnprog`.`django_authopenid_nonce` (
+ `id` int(11) NOT NULL auto_increment,
+ `server_url` varchar(255) NOT NULL,
+ `timestamp` int(11) NOT NULL,
+ `salt` varchar(40) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`django_authopenid_userassociation`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_authopenid_userassociation`;
+CREATE TABLE `cnprog`.`django_authopenid_userassociation` (
+ `id` int(11) NOT NULL auto_increment,
+ `openid_url` varchar(255) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`),
+ CONSTRAINT `user_id_refs_id_163d208d` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`django_authopenid_userassociation`
+--
+INSERT INTO `cnprog`.`django_authopenid_userassociation` VALUES (2,'https://www.google.com/accounts/o8/id?id=AItOawl7CVVHl4DWtteqj4dd_A23zKRwPZgOOjw',2),
+ (3,'https://me.yahoo.com/a/f8f2zXF91okYL4iN2Zh4P542a5s-#f4af2',3),
+ (4,'https://me.yahoo.com/sailingcai#6fa4e',4);
+
+--
+-- Definition of table `cnprog`.`django_authopenid_userpasswordqueue`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_authopenid_userpasswordqueue`;
+CREATE TABLE `cnprog`.`django_authopenid_userpasswordqueue` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `new_password` varchar(30) NOT NULL,
+ `confirm_key` varchar(40) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`),
+ CONSTRAINT `user_id_refs_id_76bcaaa4` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`django_authopenid_userpasswordqueue`
+--
+
+--
+-- Definition of table `cnprog`.`django_content_type`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_content_type`;
+CREATE TABLE `cnprog`.`django_content_type` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(100) NOT NULL,
+ `app_label` varchar(100) NOT NULL,
+ `model` varchar(100) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `app_label` (`app_label`,`model`)
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`django_content_type`
+--
+INSERT INTO `cnprog`.`django_content_type` VALUES (1,'permission','auth','permission'),
+ (2,'group','auth','group'),
+ (3,'user','auth','user'),
+ (4,'message','auth','message'),
+ (5,'content type','contenttypes','contenttype'),
+ (6,'session','sessions','session'),
+ (7,'site','sites','site'),
+ (9,'answer','forum','answer'),
+ (10,'comment','forum','comment'),
+ (11,'tag','forum','tag'),
+ (13,'nonce','django_openidconsumer','nonce'),
+ (14,'association','django_openidconsumer','association'),
+ (15,'nonce','django_authopenid','nonce'),
+ (16,'association','django_authopenid','association'),
+ (17,'user association','django_authopenid','userassociation'),
+ (18,'user password queue','django_authopenid','userpasswordqueue'),
+ (19,'log entry','admin','logentry'),
+ (20,'question','forum','question'),
+ (21,'vote','forum','vote'),
+ (22,'flagged item','forum','flaggeditem'),
+ (23,'favorite question','forum','favoritequestion'),
+ (24,'badge','forum','badge'),
+ (25,'award','forum','award');
+
+--
+-- Definition of table `cnprog`.`django_session`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_session`;
+CREATE TABLE `cnprog`.`django_session` (
+ `session_key` varchar(40) NOT NULL,
+ `session_data` longtext NOT NULL,
+ `expire_date` datetime NOT NULL,
+ PRIMARY KEY (`session_key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`django_site`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_site`;
+CREATE TABLE `cnprog`.`django_site` (
+ `id` int(11) NOT NULL auto_increment,
+ `domain` varchar(100) NOT NULL,
+ `name` varchar(50) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`django_site`
+--
+INSERT INTO `cnprog`.`django_site` VALUES (1,'cnprog.com','CNProg.com');
+
+--
+-- Definition of table `cnprog`.`favorite_question`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`favorite_question`;
+CREATE TABLE `cnprog`.`favorite_question` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `added_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `favorite_question_question_id` (`question_id`),
+ KEY `favorite_question_user_id` (`user_id`),
+ CONSTRAINT `question_id_refs_id_1ebe1cc3` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`),
+ CONSTRAINT `user_id_refs_id_52853822` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`favorite_question`
+--
+
+--
+-- Definition of table `cnprog`.`flagged_item`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`flagged_item`;
+CREATE TABLE `cnprog`.`flagged_item` (
+ `id` int(11) NOT NULL auto_increment,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `flagged_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `content_type_id` (`content_type_id`,`object_id`,`user_id`),
+ KEY `flagged_item_content_type_id` (`content_type_id`),
+ KEY `flagged_item_user_id` (`user_id`),
+ CONSTRAINT `content_type_id_refs_id_76e44d74` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_35e3c608` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`flagged_item`
+--
+
+--
+-- Definition of table `cnprog`.`question`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`question`;
+CREATE TABLE `cnprog`.`question` (
+ `id` int(11) NOT NULL auto_increment,
+ `title` varchar(300) NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `added_at` datetime NOT NULL,
+ `wiki` tinyint(1) NOT NULL,
+ `wikified_at` datetime default NULL,
+ `answer_accepted` tinyint(1) NOT NULL,
+ `closed` tinyint(1) NOT NULL,
+ `closed_by_id` int(11) default NULL,
+ `closed_at` datetime default NULL,
+ `close_reason` smallint(6) default NULL,
+ `deleted` tinyint(1) NOT NULL,
+ `deleted_at` datetime default NULL,
+ `deleted_by_id` int(11) default NULL,
+ `locked` tinyint(1) NOT NULL,
+ `locked_by_id` int(11) default NULL,
+ `locked_at` datetime default NULL,
+ `vote_up_count` int(11) NOT NULL,
+ `vote_down_count` int(11) NOT NULL,
+ `score` int(11) NOT NULL,
+ `answer_count` int(10) unsigned NOT NULL,
+ `comment_count` int(10) unsigned NOT NULL,
+ `view_count` int(10) unsigned NOT NULL,
+ `offensive_flag_count` smallint(6) NOT NULL,
+ `favourite_count` int(10) unsigned NOT NULL,
+ `last_edited_at` datetime default NULL,
+ `last_edited_by_id` int(11) default NULL,
+ `last_activity_at` datetime NOT NULL,
+ `last_activity_by_id` int(11) NOT NULL,
+ `tagnames` varchar(125) NOT NULL,
+ `summary` varchar(180) NOT NULL,
+ `html` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `question_author_id` (`author_id`),
+ KEY `question_closed_by_id` (`closed_by_id`),
+ KEY `question_deleted_by_id` (`deleted_by_id`),
+ KEY `question_locked_by_id` (`locked_by_id`),
+ KEY `question_last_edited_by_id` (`last_edited_by_id`),
+ KEY `question_last_activity_by_id` (`last_activity_by_id`),
+ CONSTRAINT `author_id_refs_id_56e9d00c` FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `closed_by_id_refs_id_56e9d00c` FOREIGN KEY (`closed_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `deleted_by_id_refs_id_56e9d00c` FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `last_activity_by_id_refs_id_56e9d00c` FOREIGN KEY (`last_activity_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `last_edited_by_id_refs_id_56e9d00c` FOREIGN KEY (`last_edited_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `locked_by_id_refs_id_56e9d00c` FOREIGN KEY (`locked_by_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`question_tags`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`question_tags`;
+CREATE TABLE `cnprog`.`question_tags` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `tag_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `question_id` (`question_id`,`tag_id`),
+ KEY `tag_id_refs_id_43fcb953` (`tag_id`),
+ CONSTRAINT `question_id_refs_id_266147c6` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`),
+ CONSTRAINT `tag_id_refs_id_43fcb953` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`tag`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`tag`;
+CREATE TABLE `cnprog`.`tag` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(255) NOT NULL,
+ `created_by_id` int(11) NOT NULL,
+ `used_count` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`),
+ KEY `tag_created_by_id` (`created_by_id`),
+ CONSTRAINT `created_by_id_refs_id_47205d6d` FOREIGN KEY (`created_by_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`user_badge`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`user_badge`;
+CREATE TABLE `cnprog`.`user_badge` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `user_id` int(10) unsigned NOT NULL,
+ `badge_id` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`user_favorite_questions`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`user_favorite_questions`;
+CREATE TABLE `cnprog`.`user_favorite_questions` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `user_id` int(10) unsigned NOT NULL,
+ `question_id` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`user_favorite_questions`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`vote`;
+CREATE TABLE `cnprog`.`vote` (
+ `id` int(11) NOT NULL auto_increment,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `vote` smallint(6) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `content_type_id` (`content_type_id`,`object_id`,`user_id`),
+ KEY `vote_content_type_id` (`content_type_id`),
+ KEY `vote_user_id` (`user_id`),
+ CONSTRAINT `content_type_id_refs_id_50124414` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_760a4df0` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`vote`
+--
+
+
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
diff --git a/sql_scripts/cnprog_new_install_2009_02_28.sql b/sql_scripts/cnprog_new_install_2009_02_28.sql
new file mode 100644
index 00000000..80b9fced
--- /dev/null
+++ b/sql_scripts/cnprog_new_install_2009_02_28.sql
@@ -0,0 +1,456 @@
+SET FOREIGN_KEY_CHECKS = 0;
+
+CREATE TABLE `activity` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `activity_type` smallint(6) NOT NULL,
+ `active_at` datetime NOT NULL,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `is_auditted` tinyint(1) default '0',
+ PRIMARY KEY (`id`),
+ KEY `activity_user_id` (`user_id`),
+ KEY `activity_content_type_id` (`content_type_id`)
+) ENGINE=MyISAM AUTO_INCREMENT=103 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `answer` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `added_at` datetime NOT NULL,
+ `wiki` tinyint(1) NOT NULL,
+ `wikified_at` datetime default NULL,
+ `accepted` tinyint(1) NOT NULL,
+ `deleted` tinyint(1) NOT NULL,
+ `deleted_by_id` int(11) default NULL,
+ `locked` tinyint(1) NOT NULL,
+ `locked_by_id` int(11) default NULL,
+ `locked_at` datetime default NULL,
+ `score` int(11) NOT NULL,
+ `comment_count` int(10) unsigned NOT NULL,
+ `offensive_flag_count` smallint(6) NOT NULL,
+ `last_edited_at` datetime default NULL,
+ `last_edited_by_id` int(11) default NULL,
+ `html` longtext NOT NULL,
+ `vote_up_count` int(11) NOT NULL,
+ `vote_down_count` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `answer_question_id` (`question_id`),
+ KEY `answer_author_id` (`author_id`),
+ KEY `answer_deleted_by_id` (`deleted_by_id`),
+ KEY `answer_locked_by_id` (`locked_by_id`),
+ KEY `answer_last_edited_by_id` (`last_edited_by_id`),
+ CONSTRAINT `author_id_refs_id_192b0170` FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `deleted_by_id_refs_id_192b0170` FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `last_edited_by_id_refs_id_192b0170` FOREIGN KEY (`last_edited_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `locked_by_id_refs_id_192b0170` FOREIGN KEY (`locked_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `question_id_refs_id_7d6550c9` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `answer_revision` (
+ `id` int(11) NOT NULL auto_increment,
+ `answer_id` int(11) NOT NULL,
+ `revision` int(10) unsigned NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `revised_at` datetime NOT NULL,
+ `summary` varchar(300) collate utf8_unicode_ci NOT NULL,
+ `text` longtext collate utf8_unicode_ci NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `answer_revision_answer_id` (`answer_id`),
+ KEY `answer_revision_author_id` (`author_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_group` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(80) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_group_permissions` (
+ `id` int(11) NOT NULL auto_increment,
+ `group_id` int(11) NOT NULL,
+ `permission_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `group_id` (`group_id`,`permission_id`),
+ KEY `permission_id_refs_id_5886d21f` (`permission_id`),
+ CONSTRAINT `group_id_refs_id_3cea63fe` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`),
+ CONSTRAINT `permission_id_refs_id_5886d21f` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_message` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `message` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `auth_message_user_id` (`user_id`),
+ CONSTRAINT `user_id_refs_id_650f49a6` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_permission` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(50) NOT NULL,
+ `content_type_id` int(11) NOT NULL,
+ `codename` varchar(100) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `content_type_id` (`content_type_id`,`codename`),
+ KEY `auth_permission_content_type_id` (`content_type_id`),
+ CONSTRAINT `content_type_id_refs_id_728de91f` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_user` (
+ `id` int(11) NOT NULL auto_increment,
+ `username` varchar(30) NOT NULL,
+ `first_name` varchar(30) NOT NULL,
+ `last_name` varchar(30) NOT NULL,
+ `email` varchar(75) NOT NULL,
+ `password` varchar(128) NOT NULL,
+ `is_staff` tinyint(1) NOT NULL,
+ `is_active` tinyint(1) NOT NULL,
+ `is_superuser` tinyint(1) NOT NULL,
+ `last_login` datetime NOT NULL,
+ `date_joined` datetime NOT NULL,
+ `gold` smallint(6) NOT NULL default '0',
+ `silver` smallint(5) unsigned NOT NULL default '0',
+ `bronze` smallint(5) unsigned NOT NULL default '0',
+ `reputation` int(10) unsigned default '1',
+ `gravatar` varchar(128) default NULL,
+ `questions_per_page` smallint(5) unsigned default '10',
+ `last_seen` datetime default NULL,
+ `real_name` varchar(100) default NULL,
+ `website` varchar(200) default NULL,
+ `location` varchar(100) default NULL,
+ `date_of_birth` datetime default NULL,
+ `about` text,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `username` (`username`)
+) ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_user_groups` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `group_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`,`group_id`),
+ KEY `group_id_refs_id_f116770` (`group_id`),
+ CONSTRAINT `group_id_refs_id_f116770` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`),
+ CONSTRAINT `user_id_refs_id_7ceef80f` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_user_user_permissions` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `permission_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`,`permission_id`),
+ KEY `permission_id_refs_id_67e79cb` (`permission_id`),
+ CONSTRAINT `permission_id_refs_id_67e79cb` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`),
+ CONSTRAINT `user_id_refs_id_dfbab7d` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `award` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `badge_id` int(11) NOT NULL,
+ `awarded_at` datetime NOT NULL,
+ `notified` tinyint(1) NOT NULL,
+ `content_type_id` int(11) default NULL,
+ `object_id` int(10) default NULL,
+ PRIMARY KEY (`id`),
+ KEY `award_user_id` (`user_id`),
+ KEY `award_badge_id` (`badge_id`),
+ CONSTRAINT `badge_id_refs_id_651af0e1` FOREIGN KEY (`badge_id`) REFERENCES `badge` (`id`),
+ CONSTRAINT `user_id_refs_id_2d83e9b6` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `badge` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(50) NOT NULL,
+ `type` smallint(6) NOT NULL,
+ `slug` varchar(50) NOT NULL,
+ `description` varchar(300) NOT NULL,
+ `multiple` tinyint(1) NOT NULL,
+ `awarded_count` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`,`type`),
+ KEY `badge_slug` (`slug`)
+) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `comment` (
+ `id` int(11) NOT NULL auto_increment,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `comment` varchar(300) NOT NULL,
+ `added_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `comment_content_type_id` (`content_type_id`),
+ KEY `comment_user_id` (`user_id`),
+ KEY `content_type_id` (`content_type_id`,`object_id`,`user_id`),
+ CONSTRAINT `content_type_id_refs_id_13a5866c` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_6be725e8` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_admin_log` (
+ `id` int(11) NOT NULL auto_increment,
+ `action_time` datetime NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `content_type_id` int(11) default NULL,
+ `object_id` longtext,
+ `object_repr` varchar(200) NOT NULL,
+ `action_flag` smallint(5) unsigned NOT NULL,
+ `change_message` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `django_admin_log_user_id` (`user_id`),
+ KEY `django_admin_log_content_type_id` (`content_type_id`),
+ CONSTRAINT `content_type_id_refs_id_288599e6` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_c8665aa` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_authopenid_association` (
+ `id` int(11) NOT NULL auto_increment,
+ `server_url` longtext NOT NULL,
+ `handle` varchar(255) NOT NULL,
+ `secret` longtext NOT NULL,
+ `issued` int(11) NOT NULL,
+ `lifetime` int(11) NOT NULL,
+ `assoc_type` longtext NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_authopenid_nonce` (
+ `id` int(11) NOT NULL auto_increment,
+ `server_url` varchar(255) NOT NULL,
+ `timestamp` int(11) NOT NULL,
+ `salt` varchar(40) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_authopenid_userassociation` (
+ `id` int(11) NOT NULL auto_increment,
+ `openid_url` varchar(255) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`),
+ CONSTRAINT `user_id_refs_id_163d208d` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_authopenid_userpasswordqueue` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `new_password` varchar(30) NOT NULL,
+ `confirm_key` varchar(40) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`),
+ CONSTRAINT `user_id_refs_id_76bcaaa4` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_content_type` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(100) NOT NULL,
+ `app_label` varchar(100) NOT NULL,
+ `model` varchar(100) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `app_label` (`app_label`,`model`)
+) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_session` (
+ `session_key` varchar(40) NOT NULL,
+ `session_data` longtext NOT NULL,
+ `expire_date` datetime NOT NULL,
+ PRIMARY KEY (`session_key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_site` (
+ `id` int(11) NOT NULL auto_increment,
+ `domain` varchar(100) NOT NULL,
+ `name` varchar(50) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `favorite_question` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `added_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `favorite_question_question_id` (`question_id`),
+ KEY `favorite_question_user_id` (`user_id`),
+ CONSTRAINT `question_id_refs_id_1ebe1cc3` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`),
+ CONSTRAINT `user_id_refs_id_52853822` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `flagged_item` (
+ `id` int(11) NOT NULL auto_increment,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `flagged_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `content_type_id` (`content_type_id`,`object_id`,`user_id`),
+ KEY `flagged_item_content_type_id` (`content_type_id`),
+ KEY `flagged_item_user_id` (`user_id`),
+ CONSTRAINT `content_type_id_refs_id_76e44d74` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_35e3c608` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `question` (
+ `id` int(11) NOT NULL auto_increment,
+ `title` varchar(300) NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `added_at` datetime NOT NULL,
+ `wiki` tinyint(1) NOT NULL,
+ `wikified_at` datetime default NULL,
+ `answer_accepted` tinyint(1) NOT NULL,
+ `closed` tinyint(1) NOT NULL,
+ `closed_by_id` int(11) default NULL,
+ `closed_at` datetime default NULL,
+ `close_reason` smallint(6) default NULL,
+ `deleted` tinyint(1) NOT NULL,
+ `deleted_at` datetime default NULL,
+ `deleted_by_id` int(11) default NULL,
+ `locked` tinyint(1) NOT NULL,
+ `locked_by_id` int(11) default NULL,
+ `locked_at` datetime default NULL,
+ `score` int(11) NOT NULL,
+ `answer_count` int(10) unsigned NOT NULL,
+ `comment_count` int(10) unsigned NOT NULL,
+ `view_count` int(10) unsigned NOT NULL,
+ `offensive_flag_count` smallint(6) NOT NULL,
+ `favourite_count` int(10) unsigned NOT NULL,
+ `last_edited_at` datetime default NULL,
+ `last_edited_by_id` int(11) default NULL,
+ `last_activity_at` datetime NOT NULL,
+ `last_activity_by_id` int(11) NOT NULL,
+ `tagnames` varchar(125) NOT NULL,
+ `summary` varchar(180) NOT NULL,
+ `html` longtext NOT NULL,
+ `vote_up_count` int(11) NOT NULL,
+ `vote_down_count` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `question_author_id` (`author_id`),
+ KEY `question_closed_by_id` (`closed_by_id`),
+ KEY `question_deleted_by_id` (`deleted_by_id`),
+ KEY `question_locked_by_id` (`locked_by_id`),
+ KEY `question_last_edited_by_id` (`last_edited_by_id`),
+ KEY `question_last_activity_by_id` (`last_activity_by_id`),
+ CONSTRAINT `author_id_refs_id_56e9d00c` FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `closed_by_id_refs_id_56e9d00c` FOREIGN KEY (`closed_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `deleted_by_id_refs_id_56e9d00c` FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `last_activity_by_id_refs_id_56e9d00c` FOREIGN KEY (`last_activity_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `last_edited_by_id_refs_id_56e9d00c` FOREIGN KEY (`last_edited_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `locked_by_id_refs_id_56e9d00c` FOREIGN KEY (`locked_by_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `question_revision` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `revision` int(10) unsigned NOT NULL,
+ `title` varchar(300) NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `revised_at` datetime NOT NULL,
+ `tagnames` varchar(125) NOT NULL,
+ `summary` varchar(300) NOT NULL,
+ `text` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `question_revision_question_id` (`question_id`),
+ KEY `question_revision_author_id` (`author_id`)
+) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
+
+
+CREATE TABLE `question_tags` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `tag_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `question_id` (`question_id`,`tag_id`),
+ KEY `tag_id_refs_id_43fcb953` (`tag_id`),
+ CONSTRAINT `question_id_refs_id_266147c6` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`),
+ CONSTRAINT `tag_id_refs_id_43fcb953` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `repute` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `positive` smallint(6) NOT NULL,
+ `negative` smallint(6) NOT NULL,
+ `question_id` int(11) NOT NULL,
+ `reputed_at` datetime NOT NULL,
+ `reputation_type` smallint(6) NOT NULL,
+ `reputation` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `repute_user_id` (`user_id`),
+ KEY `repute_question_id` (`question_id`)
+) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `tag` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(255) NOT NULL,
+ `created_by_id` int(11) NOT NULL,
+ `used_count` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`),
+ KEY `tag_created_by_id` (`created_by_id`),
+ CONSTRAINT `created_by_id_refs_id_47205d6d` FOREIGN KEY (`created_by_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `user_badge` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `user_id` int(10) unsigned NOT NULL,
+ `badge_id` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `user_favorite_questions` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `user_id` int(10) unsigned NOT NULL,
+ `question_id` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `vote` (
+ `id` int(11) NOT NULL auto_increment,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `vote` smallint(6) NOT NULL,
+ `voted_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `content_type_id` (`content_type_id`,`object_id`,`user_id`),
+ KEY `vote_content_type_id` (`content_type_id`),
+ KEY `vote_user_id` (`user_id`),
+ CONSTRAINT `content_type_id_refs_id_50124414` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_760a4df0` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+
+
+SET FOREIGN_KEY_CHECKS = 1;
diff --git a/sql_scripts/cnprog_new_install_2009_03_31.sql b/sql_scripts/cnprog_new_install_2009_03_31.sql
new file mode 100644
index 00000000..c2c69f36
--- /dev/null
+++ b/sql_scripts/cnprog_new_install_2009_03_31.sql
@@ -0,0 +1,891 @@
+USE cnprog;
+
+
+/************ Update: Tables ***************/
+
+/******************** Add Table: activity ************************/
+
+/* Build Table Structure */
+CREATE TABLE activity
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ activity_type SMALLINT NOT NULL,
+ active_at DATETIME NOT NULL,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ is_auditted TINYINT NULL DEFAULT 0
+) ENGINE=MyISAM AUTO_INCREMENT=103 DEFAULT CHARSET=latin1;
+
+/* Table Items: activity */
+
+/* Add Indexes for: activity */
+CREATE INDEX activity_content_type_id ON activity (content_type_id);
+CREATE INDEX activity_user_id ON activity (user_id);
+
+/******************** Add Table: answer ************************/
+
+/* Build Table Structure */
+CREATE TABLE answer
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ author_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ wiki TINYINT NOT NULL,
+ wikified_at DATETIME NULL,
+ accepted TINYINT NOT NULL,
+ deleted TINYINT NOT NULL,
+ deleted_by_id INTEGER NULL,
+ locked TINYINT NOT NULL,
+ locked_by_id INTEGER NULL,
+ locked_at DATETIME NULL,
+ score INTEGER NOT NULL,
+ comment_count INTEGER UNSIGNED NOT NULL,
+ offensive_flag_count SMALLINT NOT NULL,
+ last_edited_at DATETIME NULL,
+ last_edited_by_id INTEGER NULL,
+ html LONGTEXT NOT NULL,
+ vote_up_count INTEGER NOT NULL,
+ vote_down_count INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
+
+/* Table Items: answer */
+
+/* Add Indexes for: answer */
+CREATE INDEX answer_author_id ON answer (author_id);
+CREATE INDEX answer_deleted_by_id ON answer (deleted_by_id);
+CREATE INDEX answer_last_edited_by_id ON answer (last_edited_by_id);
+CREATE INDEX answer_locked_by_id ON answer (locked_by_id);
+CREATE INDEX answer_question_id ON answer (question_id);
+
+/******************** Add Table: answer_revision ************************/
+
+/* Build Table Structure */
+CREATE TABLE answer_revision
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ answer_id INTEGER NOT NULL,
+ revision INTEGER UNSIGNED NOT NULL,
+ author_id INTEGER NOT NULL,
+ revised_at DATETIME NOT NULL,
+ summary TEXT NOT NULL,
+ `text` LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+/* Table Items: answer_revision */
+
+/* Add Indexes for: answer_revision */
+CREATE INDEX answer_revision_answer_id ON answer_revision (answer_id);
+CREATE INDEX answer_revision_author_id ON answer_revision (author_id);
+
+/******************** Add Table: auth_group ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_group
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(80) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_group */
+
+/* Add Indexes for: auth_group */
+CREATE UNIQUE INDEX name ON auth_group (name);
+
+/******************** Add Table: auth_group_permissions ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_group_permissions
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ group_id INTEGER NOT NULL,
+ permission_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_group_permissions */
+
+/* Add Indexes for: auth_group_permissions */
+CREATE UNIQUE INDEX group_id ON auth_group_permissions (group_id, permission_id);
+CREATE INDEX permission_id_refs_id_5886d21f ON auth_group_permissions (permission_id);
+
+/******************** Add Table: auth_message ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_message
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ message LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_message */
+
+/* Add Indexes for: auth_message */
+CREATE INDEX auth_message_user_id ON auth_message (user_id);
+
+/******************** Add Table: auth_permission ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_permission
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(50) NOT NULL,
+ content_type_id INTEGER NOT NULL,
+ codename VARCHAR(100) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_permission */
+
+/* Add Indexes for: auth_permission */
+CREATE INDEX auth_permission_content_type_id ON auth_permission (content_type_id);
+CREATE UNIQUE INDEX content_type_id ON auth_permission (content_type_id, codename);
+
+/******************** Add Table: auth_user ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_user
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ username VARCHAR(30) NOT NULL,
+ first_name VARCHAR(30) NOT NULL,
+ last_name VARCHAR(30) NOT NULL,
+ email VARCHAR(75) NOT NULL,
+ password VARCHAR(128) NOT NULL,
+ is_staff TINYINT NOT NULL,
+ is_active TINYINT NOT NULL,
+ is_superuser TINYINT NOT NULL,
+ last_login DATETIME NOT NULL,
+ date_joined DATETIME NOT NULL,
+ gold SMALLINT NOT NULL DEFAULT 0,
+ silver SMALLINT UNSIGNED NOT NULL DEFAULT 0,
+ bronze SMALLINT UNSIGNED NOT NULL DEFAULT 0,
+ reputation INTEGER UNSIGNED NULL DEFAULT 1,
+ gravatar VARCHAR(128) NULL,
+ questions_per_page SMALLINT UNSIGNED NULL DEFAULT 10,
+ last_seen DATETIME NULL,
+ real_name VARCHAR(100) NULL,
+ website VARCHAR(200) NULL,
+ location VARCHAR(100) NULL,
+ date_of_birth DATETIME NULL,
+ about TEXT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_user */
+
+/* Add Indexes for: auth_user */
+CREATE UNIQUE INDEX username ON auth_user (username);
+
+/******************** Add Table: auth_user_groups ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_user_groups
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ group_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_user_groups */
+
+/* Add Indexes for: auth_user_groups */
+CREATE INDEX group_id_refs_id_f116770 ON auth_user_groups (group_id);
+CREATE UNIQUE INDEX user_id ON auth_user_groups (user_id, group_id);
+
+/******************** Add Table: auth_user_user_permissions ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_user_user_permissions
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ permission_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_user_user_permissions */
+
+/* Add Indexes for: auth_user_user_permissions */
+CREATE INDEX permission_id_refs_id_67e79cb ON auth_user_user_permissions (permission_id);
+CREATE UNIQUE INDEX user_id ON auth_user_user_permissions (user_id, permission_id);
+
+/******************** Add Table: award ************************/
+
+/* Build Table Structure */
+CREATE TABLE award
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ badge_id INTEGER NOT NULL,
+ awarded_at DATETIME NOT NULL,
+ notified TINYINT NOT NULL,
+ content_type_id INTEGER NULL,
+ object_id INTEGER NULL
+) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
+
+/* Table Items: award */
+
+/* Add Indexes for: award */
+CREATE INDEX award_badge_id ON award (badge_id);
+CREATE INDEX award_user_id ON award (user_id);
+
+/******************** Add Table: badge ************************/
+
+/* Build Table Structure */
+CREATE TABLE badge
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(50) NOT NULL,
+ `type` SMALLINT NOT NULL,
+ slug VARCHAR(50) NOT NULL,
+ description TEXT NOT NULL,
+ multiple TINYINT NOT NULL,
+ awarded_count INTEGER UNSIGNED NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
+
+/* Table Items: badge */
+
+/* Add Indexes for: badge */
+CREATE INDEX badge_slug ON badge (slug);
+CREATE UNIQUE INDEX name ON badge (name, `type`);
+
+/******************** Add Table: book ************************/
+
+/* Build Table Structure */
+CREATE TABLE book
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ title VARCHAR(255) NOT NULL,
+ short_name VARCHAR(255) NOT NULL,
+ author VARCHAR(255) NOT NULL,
+ user_id INTEGER NULL,
+ price DECIMAL(10, 2) NULL,
+ pages SMALLINT NULL,
+ published_at DATE NOT NULL,
+ publication VARCHAR(255) NOT NULL,
+ cover_img VARCHAR(255) NULL,
+ tagnames VARCHAR(125) NULL,
+ added_at DATETIME NOT NULL,
+ last_edited_at DATETIME NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book */
+
+/* Add Indexes for: book */
+CREATE UNIQUE INDEX book_short_name_Idx ON book (short_name);
+CREATE INDEX fk_books_auth_user ON book (user_id);
+
+/******************** Add Table: book_author_info ************************/
+
+/* Build Table Structure */
+CREATE TABLE book_author_info
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ blog_url VARCHAR(255) NULL,
+ user_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ last_edited_at DATETIME NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book_author_info */
+
+/* Add Indexes for: book_author_info */
+CREATE INDEX fk_book_author_info_auth_user ON book_author_info (user_id);
+
+/******************** Add Table: book_author_rss ************************/
+
+/* Build Table Structure */
+CREATE TABLE book_author_rss
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ title VARCHAR(255) NOT NULL,
+ url VARCHAR(255) NOT NULL,
+ rss_created_at DATETIME NOT NULL,
+ user_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book_author_rss */
+
+/* Add Indexes for: book_author_rss */
+CREATE INDEX fk_book_author_rss_auth_user ON book_author_rss (user_id);
+
+/******************** Add Table: book_question ************************/
+
+/* Build Table Structure */
+CREATE TABLE book_question
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ book_id INTEGER NOT NULL,
+ question_id INTEGER NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book_question */
+
+/* Add Indexes for: book_question */
+CREATE INDEX fk_book_question_book ON book_question (book_id);
+CREATE INDEX fk_book_question_question ON book_question (question_id);
+
+/******************** Add Table: `comment` ************************/
+
+/* Build Table Structure */
+CREATE TABLE `comment`
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ user_id INTEGER NOT NULL,
+ `comment` TEXT NOT NULL,
+ added_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
+
+/* Table Items: `comment` */
+
+/* Add Indexes for: comment */
+CREATE INDEX comment_content_type_id ON `comment` (content_type_id);
+CREATE INDEX comment_user_id ON `comment` (user_id);
+CREATE INDEX content_type_id ON `comment` (content_type_id, object_id, user_id);
+
+/******************** Add Table: django_admin_log ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_admin_log
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ action_time DATETIME NOT NULL,
+ user_id INTEGER NOT NULL,
+ content_type_id INTEGER NULL,
+ object_id LONGTEXT NULL,
+ object_repr VARCHAR(200) NOT NULL,
+ action_flag SMALLINT UNSIGNED NOT NULL,
+ change_message LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+/* Table Items: django_admin_log */
+
+/* Add Indexes for: django_admin_log */
+CREATE INDEX django_admin_log_content_type_id ON django_admin_log (content_type_id);
+CREATE INDEX django_admin_log_user_id ON django_admin_log (user_id);
+
+/******************** Add Table: django_authopenid_association ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_association
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ server_url LONGTEXT NOT NULL,
+ handle VARCHAR(255) NOT NULL,
+ secret LONGTEXT NOT NULL,
+ issued INTEGER NOT NULL,
+ lifetime INTEGER NOT NULL,
+ assoc_type LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+/******************** Add Table: django_authopenid_nonce ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_nonce
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ server_url VARCHAR(255) NOT NULL,
+ `timestamp` INTEGER NOT NULL,
+ salt VARCHAR(40) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;
+
+/******************** Add Table: django_authopenid_userassociation ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_userassociation
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ openid_url VARCHAR(255) NOT NULL,
+ user_id INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+/* Table Items: django_authopenid_userassociation */
+
+/* Add Indexes for: django_authopenid_userassociation */
+CREATE UNIQUE INDEX user_id ON django_authopenid_userassociation (user_id);
+
+/******************** Add Table: django_authopenid_userpasswordqueue ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_userpasswordqueue
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ new_password VARCHAR(30) NOT NULL,
+ confirm_key VARCHAR(40) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: django_authopenid_userpasswordqueue */
+
+/* Add Indexes for: django_authopenid_userpasswordqueue */
+CREATE UNIQUE INDEX user_id ON django_authopenid_userpasswordqueue (user_id);
+
+/******************** Add Table: django_content_type ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_content_type
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(100) NOT NULL,
+ app_label VARCHAR(100) NOT NULL,
+ model VARCHAR(100) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
+
+/* Table Items: django_content_type */
+
+/* Add Indexes for: django_content_type */
+CREATE UNIQUE INDEX app_label ON django_content_type (app_label, model);
+
+/******************** Add Table: django_session ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_session
+(
+ session_key VARCHAR(40) NOT NULL,
+ session_data LONGTEXT NOT NULL,
+ expire_date DATETIME NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: django_session */
+ALTER TABLE django_session ADD CONSTRAINT pkdjango_session
+ PRIMARY KEY (session_key);
+
+/******************** Add Table: django_site ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_site
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ domain VARCHAR(100) NOT NULL,
+ name VARCHAR(50) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+/******************** Add Table: favorite_question ************************/
+
+/* Build Table Structure */
+CREATE TABLE favorite_question
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ user_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+/* Table Items: favorite_question */
+
+/* Add Indexes for: favorite_question */
+CREATE INDEX favorite_question_question_id ON favorite_question (question_id);
+CREATE INDEX favorite_question_user_id ON favorite_question (user_id);
+
+/******************** Add Table: flagged_item ************************/
+
+/* Build Table Structure */
+CREATE TABLE flagged_item
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ user_id INTEGER NOT NULL,
+ flagged_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+/* Table Items: flagged_item */
+
+/* Add Indexes for: flagged_item */
+CREATE UNIQUE INDEX content_type_id ON flagged_item (content_type_id, object_id, user_id);
+CREATE INDEX flagged_item_content_type_id ON flagged_item (content_type_id);
+CREATE INDEX flagged_item_user_id ON flagged_item (user_id);
+
+/******************** Add Table: question ************************/
+
+/* Build Table Structure */
+CREATE TABLE question
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ title TEXT NOT NULL,
+ author_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ wiki TINYINT NOT NULL,
+ wikified_at DATETIME NULL,
+ answer_accepted TINYINT NOT NULL,
+ closed TINYINT NOT NULL,
+ closed_by_id INTEGER NULL,
+ closed_at DATETIME NULL,
+ close_reason SMALLINT NULL,
+ deleted TINYINT NOT NULL,
+ deleted_at DATETIME NULL,
+ deleted_by_id INTEGER NULL,
+ locked TINYINT NOT NULL,
+ locked_by_id INTEGER NULL,
+ locked_at DATETIME NULL,
+ score INTEGER NOT NULL,
+ answer_count INTEGER UNSIGNED NOT NULL,
+ comment_count INTEGER UNSIGNED NOT NULL,
+ view_count INTEGER UNSIGNED NOT NULL,
+ offensive_flag_count SMALLINT NOT NULL,
+ favourite_count INTEGER UNSIGNED NOT NULL,
+ last_edited_at DATETIME NULL,
+ last_edited_by_id INTEGER NULL,
+ last_activity_at DATETIME NOT NULL,
+ last_activity_by_id INTEGER NOT NULL,
+ tagnames VARCHAR(125) NOT NULL,
+ summary VARCHAR(180) NOT NULL,
+ html LONGTEXT NOT NULL,
+ vote_up_count INTEGER NOT NULL,
+ vote_down_count INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
+
+/* Table Items: question */
+
+/* Add Indexes for: question */
+CREATE INDEX question_author_id ON question (author_id);
+CREATE INDEX question_closed_by_id ON question (closed_by_id);
+CREATE INDEX question_deleted_by_id ON question (deleted_by_id);
+CREATE INDEX question_last_activity_by_id ON question (last_activity_by_id);
+CREATE INDEX question_last_edited_by_id ON question (last_edited_by_id);
+CREATE INDEX question_locked_by_id ON question (locked_by_id);
+
+/******************** Add Table: question_revision ************************/
+
+/* Build Table Structure */
+CREATE TABLE question_revision
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ revision INTEGER UNSIGNED NOT NULL,
+ title TEXT NOT NULL,
+ author_id INTEGER NOT NULL,
+ revised_at DATETIME NOT NULL,
+ tagnames VARCHAR(125) NOT NULL,
+ summary TEXT NOT NULL,
+ `text` LONGTEXT NOT NULL
+) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
+
+/* Table Items: question_revision */
+
+/* Add Indexes for: question_revision */
+CREATE INDEX question_revision_author_id ON question_revision (author_id);
+CREATE INDEX question_revision_question_id ON question_revision (question_id);
+
+/******************** Add Table: question_tags ************************/
+
+/* Build Table Structure */
+CREATE TABLE question_tags
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ tag_id INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
+
+/* Table Items: question_tags */
+
+/* Add Indexes for: question_tags */
+CREATE UNIQUE INDEX question_id ON question_tags (question_id, tag_id);
+CREATE INDEX tag_id_refs_id_43fcb953 ON question_tags (tag_id);
+
+/******************** Add Table: repute ************************/
+
+/* Build Table Structure */
+CREATE TABLE repute
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ positive SMALLINT NOT NULL,
+ negative SMALLINT NOT NULL,
+ question_id INTEGER NOT NULL,
+ reputed_at DATETIME NOT NULL,
+ reputation_type SMALLINT NOT NULL,
+ reputation INTEGER NOT NULL
+) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=latin1;
+
+/* Table Items: repute */
+
+/* Add Indexes for: repute */
+CREATE INDEX repute_question_id ON repute (question_id);
+CREATE INDEX repute_user_id ON repute (user_id);
+
+/******************** Add Table: tag ************************/
+
+/* Build Table Structure */
+CREATE TABLE tag
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ created_by_id INTEGER NOT NULL,
+ used_count INTEGER UNSIGNED NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+/* Table Items: tag */
+
+/* Add Indexes for: tag */
+CREATE UNIQUE INDEX name ON tag (name);
+CREATE INDEX tag_created_by_id ON tag (created_by_id);
+
+/******************** Add Table: user_badge ************************/
+
+/* Build Table Structure */
+CREATE TABLE user_badge
+(
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ badge_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: user_badge */
+
+/* Add Indexes for: user_badge */
+CREATE INDEX fk_user_badge_auth_user ON user_badge (user_id);
+CREATE INDEX fk_user_badge_badge ON user_badge (badge_id);
+
+/******************** Add Table: user_favorite_questions ************************/
+
+/* Build Table Structure */
+CREATE TABLE user_favorite_questions
+(
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ question_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: user_favorite_questions */
+
+/* Add Indexes for: user_favorite_questions */
+CREATE INDEX fk_user_favorite_questions_auth_user ON user_favorite_questions (user_id);
+CREATE INDEX fk_user_favorite_questions_question ON user_favorite_questions (question_id);
+
+/******************** Add Table: vote ************************/
+
+/* Build Table Structure */
+CREATE TABLE vote
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ user_id INTEGER NOT NULL,
+ vote SMALLINT NOT NULL,
+ voted_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+
+/* Table Items: vote */
+
+/* Add Indexes for: vote */
+CREATE UNIQUE INDEX content_type_id ON vote (content_type_id, object_id, user_id);
+CREATE INDEX vote_content_type_id ON vote (content_type_id);
+CREATE INDEX vote_user_id ON vote (user_id);
+
+
+/************ Add Foreign Keys to Database ***************/
+/*-----------------------------------------------------------
+Warning: Versions of MySQL prior to 4.1.2 require indexes on all columns involved in a foreign key. The following indexes may be required:
+fk_auth_group_permissions_auth_group may require an index on table: auth_group_permissions, column: group_id
+fk_auth_user_groups_auth_user may require an index on table: auth_user_groups, column: user_id
+fk_auth_user_user_permissions_auth_user may require an index on table: auth_user_user_permissions, column: user_id
+fk_question_tags_question may require an index on table: question_tags, column: question_id
+-----------------------------------------------------------
+*/
+
+/************ Foreign Key: fk_activity_auth_user ***************/
+ALTER TABLE activity ADD CONSTRAINT fk_activity_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: deleted_by_id_refs_id_192b0170 ***************/
+ALTER TABLE answer ADD CONSTRAINT deleted_by_id_refs_id_192b0170
+ FOREIGN KEY (deleted_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_answer_auth_user ***************/
+ALTER TABLE answer ADD CONSTRAINT fk_answer_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_answer_question ***************/
+ALTER TABLE answer ADD CONSTRAINT fk_answer_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: last_edited_by_id_refs_id_192b0170 ***************/
+ALTER TABLE answer ADD CONSTRAINT last_edited_by_id_refs_id_192b0170
+ FOREIGN KEY (last_edited_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: locked_by_id_refs_id_192b0170 ***************/
+ALTER TABLE answer ADD CONSTRAINT locked_by_id_refs_id_192b0170
+ FOREIGN KEY (locked_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_answer_revision_auth_user ***************/
+ALTER TABLE answer_revision ADD CONSTRAINT fk_answer_revision_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_group_permissions_auth_group ***************/
+ALTER TABLE auth_group_permissions ADD CONSTRAINT fk_auth_group_permissions_auth_group
+ FOREIGN KEY (group_id) REFERENCES auth_group (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_group_permissions_auth_permission ***************/
+ALTER TABLE auth_group_permissions ADD CONSTRAINT fk_auth_group_permissions_auth_permission
+ FOREIGN KEY (permission_id) REFERENCES auth_permission (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_message_auth_user ***************/
+ALTER TABLE auth_message ADD CONSTRAINT fk_auth_message_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_permission_django_content_type ***************/
+ALTER TABLE auth_permission ADD CONSTRAINT fk_auth_permission_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_groups_auth_group ***************/
+ALTER TABLE auth_user_groups ADD CONSTRAINT fk_auth_user_groups_auth_group
+ FOREIGN KEY (group_id) REFERENCES auth_group (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_groups_auth_user ***************/
+ALTER TABLE auth_user_groups ADD CONSTRAINT fk_auth_user_groups_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_user_permissions_auth_permission ***************/
+ALTER TABLE auth_user_user_permissions ADD CONSTRAINT fk_auth_user_user_permissions_auth_permission
+ FOREIGN KEY (permission_id) REFERENCES auth_permission (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_user_permissions_auth_user ***************/
+ALTER TABLE auth_user_user_permissions ADD CONSTRAINT fk_auth_user_user_permissions_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_award_auth_user ***************/
+ALTER TABLE award ADD CONSTRAINT fk_award_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_award_badge ***************/
+ALTER TABLE award ADD CONSTRAINT fk_award_badge
+ FOREIGN KEY (badge_id) REFERENCES badge (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_books_auth_user ***************/
+ALTER TABLE book ADD CONSTRAINT fk_books_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_author_info_auth_user ***************/
+ALTER TABLE book_author_info ADD CONSTRAINT fk_book_author_info_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_author_rss_auth_user ***************/
+ALTER TABLE book_author_rss ADD CONSTRAINT fk_book_author_rss_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_question_book ***************/
+ALTER TABLE book_question ADD CONSTRAINT fk_book_question_book
+ FOREIGN KEY (book_id) REFERENCES book (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_question_question ***************/
+ALTER TABLE book_question ADD CONSTRAINT fk_book_question_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_comment_auth_user ***************/
+ALTER TABLE `comment` ADD CONSTRAINT fk_comment_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_comment_django_content_type ***************/
+ALTER TABLE `comment` ADD CONSTRAINT fk_comment_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_admin_log_auth_user ***************/
+ALTER TABLE django_admin_log ADD CONSTRAINT fk_django_admin_log_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_admin_log_django_content_type ***************/
+ALTER TABLE django_admin_log ADD CONSTRAINT fk_django_admin_log_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_authopenid_userassociation_auth_user ***************/
+ALTER TABLE django_authopenid_userassociation ADD CONSTRAINT fk_django_authopenid_userassociation_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_authopenid_userpasswordqueue_auth_user ***************/
+ALTER TABLE django_authopenid_userpasswordqueue ADD CONSTRAINT fk_django_authopenid_userpasswordqueue_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_favorite_question_auth_user ***************/
+ALTER TABLE favorite_question ADD CONSTRAINT fk_favorite_question_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_favorite_question_question ***************/
+ALTER TABLE favorite_question ADD CONSTRAINT fk_favorite_question_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_flagged_item_auth_user ***************/
+ALTER TABLE flagged_item ADD CONSTRAINT fk_flagged_item_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_flagged_item_django_content_type ***************/
+ALTER TABLE flagged_item ADD CONSTRAINT fk_flagged_item_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: closed_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT closed_by_id_refs_id_56e9d00c
+ FOREIGN KEY (closed_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: deleted_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT deleted_by_id_refs_id_56e9d00c
+ FOREIGN KEY (deleted_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_auth_user ***************/
+ALTER TABLE question ADD CONSTRAINT fk_question_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: last_activity_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT last_activity_by_id_refs_id_56e9d00c
+ FOREIGN KEY (last_activity_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: last_edited_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT last_edited_by_id_refs_id_56e9d00c
+ FOREIGN KEY (last_edited_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: locked_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT locked_by_id_refs_id_56e9d00c
+ FOREIGN KEY (locked_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_revision_auth_user ***************/
+ALTER TABLE question_revision ADD CONSTRAINT fk_question_revision_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_revision_question ***************/
+ALTER TABLE question_revision ADD CONSTRAINT fk_question_revision_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_tags_question ***************/
+ALTER TABLE question_tags ADD CONSTRAINT fk_question_tags_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_tags_tag ***************/
+ALTER TABLE question_tags ADD CONSTRAINT fk_question_tags_tag
+ FOREIGN KEY (tag_id) REFERENCES tag (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_repute_auth_user ***************/
+ALTER TABLE repute ADD CONSTRAINT fk_repute_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_repute_question ***************/
+ALTER TABLE repute ADD CONSTRAINT fk_repute_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_tag_auth_user ***************/
+ALTER TABLE tag ADD CONSTRAINT fk_tag_auth_user
+ FOREIGN KEY (created_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_badge_auth_user ***************/
+ALTER TABLE user_badge ADD CONSTRAINT fk_user_badge_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_badge_badge ***************/
+ALTER TABLE user_badge ADD CONSTRAINT fk_user_badge_badge
+ FOREIGN KEY (badge_id) REFERENCES badge (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_favorite_questions_auth_user ***************/
+ALTER TABLE user_favorite_questions ADD CONSTRAINT fk_user_favorite_questions_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_favorite_questions_question ***************/
+ALTER TABLE user_favorite_questions ADD CONSTRAINT fk_user_favorite_questions_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_vote_auth_user ***************/
+ALTER TABLE vote ADD CONSTRAINT fk_vote_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_vote_django_content_type ***************/
+ALTER TABLE vote ADD CONSTRAINT fk_vote_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
\ No newline at end of file
diff --git a/sql_scripts/cnprog_new_install_2009_04_07.sql b/sql_scripts/cnprog_new_install_2009_04_07.sql
new file mode 100644
index 00000000..ff9016fa
--- /dev/null
+++ b/sql_scripts/cnprog_new_install_2009_04_07.sql
@@ -0,0 +1,24 @@
+USE cnprog;
+
+
+/************ Add Foreign Keys to Database ***************/
+
+/************ Foreign Key: fk_activity_auth_user ***************/
+ALTER TABLE activity ADD CONSTRAINT fk_activity_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_revision_auth_user ***************/
+ALTER TABLE question_revision ADD CONSTRAINT fk_question_revision_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_revision_question ***************/
+ALTER TABLE question_revision ADD CONSTRAINT fk_question_revision_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_repute_auth_user ***************/
+ALTER TABLE repute ADD CONSTRAINT fk_repute_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_repute_question ***************/
+ALTER TABLE repute ADD CONSTRAINT fk_repute_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
\ No newline at end of file
diff --git a/sql_scripts/cnprog_new_install_2009_04_09.sql b/sql_scripts/cnprog_new_install_2009_04_09.sql
new file mode 100644
index 00000000..f4424852
--- /dev/null
+++ b/sql_scripts/cnprog_new_install_2009_04_09.sql
@@ -0,0 +1,904 @@
+USE cnprog;
+
+
+/************ Update: Tables ***************/
+
+/******************** Add Table: activity ************************/
+
+/* Build Table Structure */
+CREATE TABLE activity
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ activity_type SMALLINT NOT NULL,
+ active_at DATETIME NOT NULL,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ is_auditted TINYINT NULL DEFAULT 0
+) ENGINE=MyISAM AUTO_INCREMENT=103 DEFAULT CHARSET=latin1;
+
+/* Table Items: activity */
+
+/* Add Indexes for: activity */
+CREATE INDEX activity_content_type_id ON activity (content_type_id);
+CREATE INDEX activity_user_id ON activity (user_id);
+
+/******************** Add Table: answer ************************/
+
+/* Build Table Structure */
+CREATE TABLE answer
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ author_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ wiki TINYINT NOT NULL,
+ wikified_at DATETIME NULL,
+ accepted TINYINT NOT NULL,
+ deleted TINYINT NOT NULL,
+ deleted_by_id INTEGER NULL,
+ locked TINYINT NOT NULL,
+ locked_by_id INTEGER NULL,
+ locked_at DATETIME NULL,
+ score INTEGER NOT NULL,
+ comment_count INTEGER UNSIGNED NOT NULL,
+ offensive_flag_count SMALLINT NOT NULL,
+ last_edited_at DATETIME NULL,
+ last_edited_by_id INTEGER NULL,
+ html LONGTEXT NOT NULL,
+ vote_up_count INTEGER NOT NULL,
+ vote_down_count INTEGER NOT NULL,
+ accepted_at DATETIME NULL
+) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
+
+/* Table Items: answer */
+
+/* Add Indexes for: answer */
+CREATE INDEX answer_author_id ON answer (author_id);
+CREATE INDEX answer_deleted_by_id ON answer (deleted_by_id);
+CREATE INDEX answer_last_edited_by_id ON answer (last_edited_by_id);
+CREATE INDEX answer_locked_by_id ON answer (locked_by_id);
+CREATE INDEX answer_question_id ON answer (question_id);
+
+/******************** Add Table: answer_revision ************************/
+
+/* Build Table Structure */
+CREATE TABLE answer_revision
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ answer_id INTEGER NOT NULL,
+ revision INTEGER UNSIGNED NOT NULL,
+ author_id INTEGER NOT NULL,
+ revised_at DATETIME NOT NULL,
+ summary TEXT NOT NULL,
+ `text` LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+/* Table Items: answer_revision */
+
+/* Add Indexes for: answer_revision */
+CREATE INDEX answer_revision_answer_id ON answer_revision (answer_id);
+CREATE INDEX answer_revision_author_id ON answer_revision (author_id);
+
+/******************** Add Table: auth_group ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_group
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(80) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_group */
+
+/* Add Indexes for: auth_group */
+CREATE UNIQUE INDEX name ON auth_group (name);
+
+/******************** Add Table: auth_group_permissions ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_group_permissions
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ group_id INTEGER NOT NULL,
+ permission_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_group_permissions */
+
+/* Add Indexes for: auth_group_permissions */
+CREATE UNIQUE INDEX group_id ON auth_group_permissions (group_id, permission_id);
+CREATE INDEX permission_id_refs_id_5886d21f ON auth_group_permissions (permission_id);
+
+/******************** Add Table: auth_message ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_message
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ message LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_message */
+
+/* Add Indexes for: auth_message */
+CREATE INDEX auth_message_user_id ON auth_message (user_id);
+
+/******************** Add Table: auth_permission ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_permission
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(50) NOT NULL,
+ content_type_id INTEGER NOT NULL,
+ codename VARCHAR(100) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_permission */
+
+/* Add Indexes for: auth_permission */
+CREATE INDEX auth_permission_content_type_id ON auth_permission (content_type_id);
+CREATE UNIQUE INDEX content_type_id ON auth_permission (content_type_id, codename);
+
+/******************** Add Table: auth_user ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_user
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ username VARCHAR(30) NOT NULL,
+ first_name VARCHAR(30) NOT NULL,
+ last_name VARCHAR(30) NOT NULL,
+ email VARCHAR(75) NOT NULL,
+ password VARCHAR(128) NOT NULL,
+ is_staff TINYINT NOT NULL,
+ is_active TINYINT NOT NULL,
+ is_superuser TINYINT NOT NULL,
+ last_login DATETIME NOT NULL,
+ date_joined DATETIME NOT NULL,
+ gold SMALLINT NOT NULL DEFAULT 0,
+ silver SMALLINT UNSIGNED NOT NULL DEFAULT 0,
+ bronze SMALLINT UNSIGNED NOT NULL DEFAULT 0,
+ reputation INTEGER UNSIGNED NULL DEFAULT 1,
+ gravatar VARCHAR(128) NULL,
+ questions_per_page SMALLINT UNSIGNED NULL DEFAULT 10,
+ last_seen DATETIME NULL,
+ real_name VARCHAR(100) NULL,
+ website VARCHAR(200) NULL,
+ location VARCHAR(100) NULL,
+ date_of_birth DATETIME NULL,
+ about TEXT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_user */
+
+/* Add Indexes for: auth_user */
+CREATE UNIQUE INDEX username ON auth_user (username);
+
+/******************** Add Table: auth_user_groups ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_user_groups
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ group_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_user_groups */
+
+/* Add Indexes for: auth_user_groups */
+CREATE INDEX group_id_refs_id_f116770 ON auth_user_groups (group_id);
+CREATE UNIQUE INDEX user_id ON auth_user_groups (user_id, group_id);
+
+/******************** Add Table: auth_user_user_permissions ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_user_user_permissions
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ permission_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_user_user_permissions */
+
+/* Add Indexes for: auth_user_user_permissions */
+CREATE INDEX permission_id_refs_id_67e79cb ON auth_user_user_permissions (permission_id);
+CREATE UNIQUE INDEX user_id ON auth_user_user_permissions (user_id, permission_id);
+
+/******************** Add Table: award ************************/
+
+/* Build Table Structure */
+CREATE TABLE award
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ badge_id INTEGER NOT NULL,
+ awarded_at DATETIME NOT NULL,
+ notified TINYINT NOT NULL,
+ content_type_id INTEGER NULL,
+ object_id INTEGER NULL
+) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
+
+/* Table Items: award */
+
+/* Add Indexes for: award */
+CREATE INDEX award_badge_id ON award (badge_id);
+CREATE INDEX award_user_id ON award (user_id);
+
+/******************** Add Table: badge ************************/
+
+/* Build Table Structure */
+CREATE TABLE badge
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(50) NOT NULL,
+ `type` SMALLINT NOT NULL,
+ slug VARCHAR(50) NOT NULL,
+ description TEXT NOT NULL,
+ multiple TINYINT NOT NULL,
+ awarded_count INTEGER UNSIGNED NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
+
+/* Table Items: badge */
+
+/* Add Indexes for: badge */
+CREATE INDEX badge_slug ON badge (slug);
+CREATE UNIQUE INDEX name ON badge (name, `type`);
+
+/******************** Add Table: book ************************/
+
+/* Build Table Structure */
+CREATE TABLE book
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ title VARCHAR(255) NOT NULL,
+ short_name VARCHAR(255) NOT NULL,
+ author VARCHAR(255) NOT NULL,
+ user_id INTEGER NULL,
+ price DECIMAL(10, 2) NULL,
+ pages SMALLINT NULL,
+ published_at DATE NOT NULL,
+ publication VARCHAR(255) NOT NULL,
+ cover_img VARCHAR(255) NULL,
+ tagnames VARCHAR(125) NULL,
+ added_at DATETIME NOT NULL,
+ last_edited_at DATETIME NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book */
+
+/* Add Indexes for: book */
+CREATE UNIQUE INDEX book_short_name_Idx ON book (short_name);
+CREATE INDEX fk_books_auth_user ON book (user_id);
+
+/******************** Add Table: book_author_info ************************/
+
+/* Build Table Structure */
+CREATE TABLE book_author_info
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ blog_url VARCHAR(255) NULL,
+ user_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ last_edited_at DATETIME NOT NULL,
+ book_id INTEGER NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book_author_info */
+
+/* Add Indexes for: book_author_info */
+CREATE INDEX fk_book_author_info_auth_user ON book_author_info (user_id);
+CREATE INDEX fk_book_author_info_book ON book_author_info (book_id);
+
+/******************** Add Table: book_author_rss ************************/
+
+/* Build Table Structure */
+CREATE TABLE book_author_rss
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ title VARCHAR(255) NOT NULL,
+ url VARCHAR(255) NOT NULL,
+ rss_created_at DATETIME NOT NULL,
+ user_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ book_id INTEGER NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book_author_rss */
+
+/* Add Indexes for: book_author_rss */
+CREATE INDEX fk_book_author_rss_auth_user ON book_author_rss (user_id);
+CREATE INDEX fk_book_author_rss_book ON book_author_rss (book_id);
+
+/******************** Add Table: book_question ************************/
+
+/* Build Table Structure */
+CREATE TABLE book_question
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ book_id INTEGER NOT NULL,
+ question_id INTEGER NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book_question */
+
+/* Add Indexes for: book_question */
+CREATE INDEX fk_book_question_book ON book_question (book_id);
+CREATE INDEX fk_book_question_question ON book_question (question_id);
+
+/******************** Add Table: `comment` ************************/
+
+/* Build Table Structure */
+CREATE TABLE `comment`
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ user_id INTEGER NOT NULL,
+ `comment` TEXT NOT NULL,
+ added_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
+
+/* Table Items: `comment` */
+
+/* Add Indexes for: comment */
+CREATE INDEX comment_content_type_id ON `comment` (content_type_id);
+CREATE INDEX comment_user_id ON `comment` (user_id);
+CREATE INDEX content_type_id ON `comment` (content_type_id, object_id, user_id);
+
+/******************** Add Table: django_admin_log ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_admin_log
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ action_time DATETIME NOT NULL,
+ user_id INTEGER NOT NULL,
+ content_type_id INTEGER NULL,
+ object_id LONGTEXT NULL,
+ object_repr VARCHAR(200) NOT NULL,
+ action_flag SMALLINT UNSIGNED NOT NULL,
+ change_message LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+/* Table Items: django_admin_log */
+
+/* Add Indexes for: django_admin_log */
+CREATE INDEX django_admin_log_content_type_id ON django_admin_log (content_type_id);
+CREATE INDEX django_admin_log_user_id ON django_admin_log (user_id);
+
+/******************** Add Table: django_authopenid_association ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_association
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ server_url LONGTEXT NOT NULL,
+ handle VARCHAR(255) NOT NULL,
+ secret LONGTEXT NOT NULL,
+ issued INTEGER NOT NULL,
+ lifetime INTEGER NOT NULL,
+ assoc_type LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+/******************** Add Table: django_authopenid_nonce ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_nonce
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ server_url VARCHAR(255) NOT NULL,
+ `timestamp` INTEGER NOT NULL,
+ salt VARCHAR(40) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;
+
+/******************** Add Table: django_authopenid_userassociation ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_userassociation
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ openid_url VARCHAR(255) NOT NULL,
+ user_id INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+/* Table Items: django_authopenid_userassociation */
+
+/* Add Indexes for: django_authopenid_userassociation */
+CREATE UNIQUE INDEX user_id ON django_authopenid_userassociation (user_id);
+
+/******************** Add Table: django_authopenid_userpasswordqueue ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_userpasswordqueue
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ new_password VARCHAR(30) NOT NULL,
+ confirm_key VARCHAR(40) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: django_authopenid_userpasswordqueue */
+
+/* Add Indexes for: django_authopenid_userpasswordqueue */
+CREATE UNIQUE INDEX user_id ON django_authopenid_userpasswordqueue (user_id);
+
+/******************** Add Table: django_content_type ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_content_type
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(100) NOT NULL,
+ app_label VARCHAR(100) NOT NULL,
+ model VARCHAR(100) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
+
+/* Table Items: django_content_type */
+
+/* Add Indexes for: django_content_type */
+CREATE UNIQUE INDEX app_label ON django_content_type (app_label, model);
+
+/******************** Add Table: django_session ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_session
+(
+ session_key VARCHAR(40) NOT NULL,
+ session_data LONGTEXT NOT NULL,
+ expire_date DATETIME NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: django_session */
+ALTER TABLE django_session ADD CONSTRAINT pkdjango_session
+ PRIMARY KEY (session_key);
+
+/******************** Add Table: django_site ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_site
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ domain VARCHAR(100) NOT NULL,
+ name VARCHAR(50) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+/******************** Add Table: favorite_question ************************/
+
+/* Build Table Structure */
+CREATE TABLE favorite_question
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ user_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+/* Table Items: favorite_question */
+
+/* Add Indexes for: favorite_question */
+CREATE INDEX favorite_question_question_id ON favorite_question (question_id);
+CREATE INDEX favorite_question_user_id ON favorite_question (user_id);
+
+/******************** Add Table: flagged_item ************************/
+
+/* Build Table Structure */
+CREATE TABLE flagged_item
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ user_id INTEGER NOT NULL,
+ flagged_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+/* Table Items: flagged_item */
+
+/* Add Indexes for: flagged_item */
+CREATE UNIQUE INDEX content_type_id ON flagged_item (content_type_id, object_id, user_id);
+CREATE INDEX flagged_item_content_type_id ON flagged_item (content_type_id);
+CREATE INDEX flagged_item_user_id ON flagged_item (user_id);
+
+/******************** Add Table: question ************************/
+
+/* Build Table Structure */
+CREATE TABLE question
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ title TEXT NOT NULL,
+ author_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ wiki TINYINT NOT NULL,
+ wikified_at DATETIME NULL,
+ answer_accepted TINYINT NOT NULL,
+ closed TINYINT NOT NULL,
+ closed_by_id INTEGER NULL,
+ closed_at DATETIME NULL,
+ close_reason SMALLINT NULL,
+ deleted TINYINT NOT NULL,
+ deleted_at DATETIME NULL,
+ deleted_by_id INTEGER NULL,
+ locked TINYINT NOT NULL,
+ locked_by_id INTEGER NULL,
+ locked_at DATETIME NULL,
+ score INTEGER NOT NULL,
+ answer_count INTEGER UNSIGNED NOT NULL,
+ comment_count INTEGER UNSIGNED NOT NULL,
+ view_count INTEGER UNSIGNED NOT NULL,
+ offensive_flag_count SMALLINT NOT NULL,
+ favourite_count INTEGER UNSIGNED NOT NULL,
+ last_edited_at DATETIME NULL,
+ last_edited_by_id INTEGER NULL,
+ last_activity_at DATETIME NOT NULL,
+ last_activity_by_id INTEGER NOT NULL,
+ tagnames VARCHAR(125) NOT NULL,
+ summary VARCHAR(180) NOT NULL,
+ html LONGTEXT NOT NULL,
+ vote_up_count INTEGER NOT NULL,
+ vote_down_count INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
+
+/* Table Items: question */
+
+/* Add Indexes for: question */
+CREATE INDEX question_author_id ON question (author_id);
+CREATE INDEX question_closed_by_id ON question (closed_by_id);
+CREATE INDEX question_deleted_by_id ON question (deleted_by_id);
+CREATE INDEX question_last_activity_by_id ON question (last_activity_by_id);
+CREATE INDEX question_last_edited_by_id ON question (last_edited_by_id);
+CREATE INDEX question_locked_by_id ON question (locked_by_id);
+
+/******************** Add Table: question_revision ************************/
+
+/* Build Table Structure */
+CREATE TABLE question_revision
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ revision INTEGER UNSIGNED NOT NULL,
+ title TEXT NOT NULL,
+ author_id INTEGER NOT NULL,
+ revised_at DATETIME NOT NULL,
+ tagnames VARCHAR(125) NOT NULL,
+ summary TEXT NOT NULL,
+ `text` LONGTEXT NOT NULL
+) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
+
+/* Table Items: question_revision */
+
+/* Add Indexes for: question_revision */
+CREATE INDEX question_revision_author_id ON question_revision (author_id);
+CREATE INDEX question_revision_question_id ON question_revision (question_id);
+
+/******************** Add Table: question_tags ************************/
+
+/* Build Table Structure */
+CREATE TABLE question_tags
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ tag_id INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
+
+/* Table Items: question_tags */
+
+/* Add Indexes for: question_tags */
+CREATE UNIQUE INDEX question_id ON question_tags (question_id, tag_id);
+CREATE INDEX tag_id_refs_id_43fcb953 ON question_tags (tag_id);
+
+/******************** Add Table: repute ************************/
+
+/* Build Table Structure */
+CREATE TABLE repute
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ positive SMALLINT NOT NULL,
+ negative SMALLINT NOT NULL,
+ question_id INTEGER NOT NULL,
+ reputed_at DATETIME NOT NULL,
+ reputation_type SMALLINT NOT NULL,
+ reputation INTEGER NOT NULL
+) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=latin1;
+
+/* Table Items: repute */
+
+/* Add Indexes for: repute */
+CREATE INDEX repute_question_id ON repute (question_id);
+CREATE INDEX repute_user_id ON repute (user_id);
+
+/******************** Add Table: tag ************************/
+
+/* Build Table Structure */
+CREATE TABLE tag
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ created_by_id INTEGER NOT NULL,
+ used_count INTEGER UNSIGNED NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+/* Table Items: tag */
+
+/* Add Indexes for: tag */
+CREATE UNIQUE INDEX name ON tag (name);
+CREATE INDEX tag_created_by_id ON tag (created_by_id);
+
+/******************** Add Table: user_badge ************************/
+
+/* Build Table Structure */
+CREATE TABLE user_badge
+(
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ badge_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: user_badge */
+
+/* Add Indexes for: user_badge */
+CREATE INDEX fk_user_badge_auth_user ON user_badge (user_id);
+CREATE INDEX fk_user_badge_badge ON user_badge (badge_id);
+
+/******************** Add Table: user_favorite_questions ************************/
+
+/* Build Table Structure */
+CREATE TABLE user_favorite_questions
+(
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ question_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: user_favorite_questions */
+
+/* Add Indexes for: user_favorite_questions */
+CREATE INDEX fk_user_favorite_questions_auth_user ON user_favorite_questions (user_id);
+CREATE INDEX fk_user_favorite_questions_question ON user_favorite_questions (question_id);
+
+/******************** Add Table: vote ************************/
+
+/* Build Table Structure */
+CREATE TABLE vote
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ user_id INTEGER NOT NULL,
+ vote SMALLINT NOT NULL,
+ voted_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+
+/* Table Items: vote */
+
+/* Add Indexes for: vote */
+CREATE UNIQUE INDEX content_type_id ON vote (content_type_id, object_id, user_id);
+CREATE INDEX vote_content_type_id ON vote (content_type_id);
+CREATE INDEX vote_user_id ON vote (user_id);
+
+
+/************ Add Foreign Keys to Database ***************/
+/*-----------------------------------------------------------
+Warning: Versions of MySQL prior to 4.1.2 require indexes on all columns involved in a foreign key. The following indexes may be required:
+fk_auth_group_permissions_auth_group may require an index on table: auth_group_permissions, column: group_id
+fk_auth_user_groups_auth_user may require an index on table: auth_user_groups, column: user_id
+fk_auth_user_user_permissions_auth_user may require an index on table: auth_user_user_permissions, column: user_id
+fk_question_tags_question may require an index on table: question_tags, column: question_id
+-----------------------------------------------------------
+*/
+
+/************ Foreign Key: fk_activity_auth_user ***************/
+ALTER TABLE activity ADD CONSTRAINT fk_activity_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: deleted_by_id_refs_id_192b0170 ***************/
+ALTER TABLE answer ADD CONSTRAINT deleted_by_id_refs_id_192b0170
+ FOREIGN KEY (deleted_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_answer_auth_user ***************/
+ALTER TABLE answer ADD CONSTRAINT fk_answer_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_answer_question ***************/
+ALTER TABLE answer ADD CONSTRAINT fk_answer_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: last_edited_by_id_refs_id_192b0170 ***************/
+ALTER TABLE answer ADD CONSTRAINT last_edited_by_id_refs_id_192b0170
+ FOREIGN KEY (last_edited_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: locked_by_id_refs_id_192b0170 ***************/
+ALTER TABLE answer ADD CONSTRAINT locked_by_id_refs_id_192b0170
+ FOREIGN KEY (locked_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_answer_revision_auth_user ***************/
+ALTER TABLE answer_revision ADD CONSTRAINT fk_answer_revision_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_group_permissions_auth_group ***************/
+ALTER TABLE auth_group_permissions ADD CONSTRAINT fk_auth_group_permissions_auth_group
+ FOREIGN KEY (group_id) REFERENCES auth_group (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_group_permissions_auth_permission ***************/
+ALTER TABLE auth_group_permissions ADD CONSTRAINT fk_auth_group_permissions_auth_permission
+ FOREIGN KEY (permission_id) REFERENCES auth_permission (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_message_auth_user ***************/
+ALTER TABLE auth_message ADD CONSTRAINT fk_auth_message_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_permission_django_content_type ***************/
+ALTER TABLE auth_permission ADD CONSTRAINT fk_auth_permission_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_groups_auth_group ***************/
+ALTER TABLE auth_user_groups ADD CONSTRAINT fk_auth_user_groups_auth_group
+ FOREIGN KEY (group_id) REFERENCES auth_group (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_groups_auth_user ***************/
+ALTER TABLE auth_user_groups ADD CONSTRAINT fk_auth_user_groups_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_user_permissions_auth_permission ***************/
+ALTER TABLE auth_user_user_permissions ADD CONSTRAINT fk_auth_user_user_permissions_auth_permission
+ FOREIGN KEY (permission_id) REFERENCES auth_permission (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_user_permissions_auth_user ***************/
+ALTER TABLE auth_user_user_permissions ADD CONSTRAINT fk_auth_user_user_permissions_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_award_auth_user ***************/
+ALTER TABLE award ADD CONSTRAINT fk_award_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_award_badge ***************/
+ALTER TABLE award ADD CONSTRAINT fk_award_badge
+ FOREIGN KEY (badge_id) REFERENCES badge (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_books_auth_user ***************/
+ALTER TABLE book ADD CONSTRAINT fk_books_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_author_info_auth_user ***************/
+ALTER TABLE book_author_info ADD CONSTRAINT fk_book_author_info_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_author_info_book ***************/
+ALTER TABLE book_author_info ADD CONSTRAINT fk_book_author_info_book
+ FOREIGN KEY (book_id) REFERENCES book (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_author_rss_auth_user ***************/
+ALTER TABLE book_author_rss ADD CONSTRAINT fk_book_author_rss_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_author_rss_book ***************/
+ALTER TABLE book_author_rss ADD CONSTRAINT fk_book_author_rss_book
+ FOREIGN KEY (book_id) REFERENCES book (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_question_book ***************/
+ALTER TABLE book_question ADD CONSTRAINT fk_book_question_book
+ FOREIGN KEY (book_id) REFERENCES book (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_question_question ***************/
+ALTER TABLE book_question ADD CONSTRAINT fk_book_question_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_comment_auth_user ***************/
+ALTER TABLE `comment` ADD CONSTRAINT fk_comment_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_comment_django_content_type ***************/
+ALTER TABLE `comment` ADD CONSTRAINT fk_comment_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_admin_log_auth_user ***************/
+ALTER TABLE django_admin_log ADD CONSTRAINT fk_django_admin_log_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_admin_log_django_content_type ***************/
+ALTER TABLE django_admin_log ADD CONSTRAINT fk_django_admin_log_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_authopenid_userassociation_auth_user ***************/
+ALTER TABLE django_authopenid_userassociation ADD CONSTRAINT fk_django_authopenid_userassociation_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_authopenid_userpasswordqueue_auth_user ***************/
+ALTER TABLE django_authopenid_userpasswordqueue ADD CONSTRAINT fk_django_authopenid_userpasswordqueue_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_favorite_question_auth_user ***************/
+ALTER TABLE favorite_question ADD CONSTRAINT fk_favorite_question_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_favorite_question_question ***************/
+ALTER TABLE favorite_question ADD CONSTRAINT fk_favorite_question_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_flagged_item_auth_user ***************/
+ALTER TABLE flagged_item ADD CONSTRAINT fk_flagged_item_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_flagged_item_django_content_type ***************/
+ALTER TABLE flagged_item ADD CONSTRAINT fk_flagged_item_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: closed_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT closed_by_id_refs_id_56e9d00c
+ FOREIGN KEY (closed_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: deleted_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT deleted_by_id_refs_id_56e9d00c
+ FOREIGN KEY (deleted_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_auth_user ***************/
+ALTER TABLE question ADD CONSTRAINT fk_question_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: last_activity_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT last_activity_by_id_refs_id_56e9d00c
+ FOREIGN KEY (last_activity_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: last_edited_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT last_edited_by_id_refs_id_56e9d00c
+ FOREIGN KEY (last_edited_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: locked_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT locked_by_id_refs_id_56e9d00c
+ FOREIGN KEY (locked_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_revision_auth_user ***************/
+ALTER TABLE question_revision ADD CONSTRAINT fk_question_revision_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_revision_question ***************/
+ALTER TABLE question_revision ADD CONSTRAINT fk_question_revision_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_tags_question ***************/
+ALTER TABLE question_tags ADD CONSTRAINT fk_question_tags_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_tags_tag ***************/
+ALTER TABLE question_tags ADD CONSTRAINT fk_question_tags_tag
+ FOREIGN KEY (tag_id) REFERENCES tag (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_repute_auth_user ***************/
+ALTER TABLE repute ADD CONSTRAINT fk_repute_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_repute_question ***************/
+ALTER TABLE repute ADD CONSTRAINT fk_repute_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_tag_auth_user ***************/
+ALTER TABLE tag ADD CONSTRAINT fk_tag_auth_user
+ FOREIGN KEY (created_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_badge_auth_user ***************/
+ALTER TABLE user_badge ADD CONSTRAINT fk_user_badge_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_badge_badge ***************/
+ALTER TABLE user_badge ADD CONSTRAINT fk_user_badge_badge
+ FOREIGN KEY (badge_id) REFERENCES badge (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_favorite_questions_auth_user ***************/
+ALTER TABLE user_favorite_questions ADD CONSTRAINT fk_user_favorite_questions_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_favorite_questions_question ***************/
+ALTER TABLE user_favorite_questions ADD CONSTRAINT fk_user_favorite_questions_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_vote_auth_user ***************/
+ALTER TABLE vote ADD CONSTRAINT fk_vote_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_vote_django_content_type ***************/
+ALTER TABLE vote ADD CONSTRAINT fk_vote_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
\ No newline at end of file
diff --git a/sql_scripts/update_2009_01_13_001.sql b/sql_scripts/update_2009_01_13_001.sql
new file mode 100644
index 00000000..165d1125
--- /dev/null
+++ b/sql_scripts/update_2009_01_13_001.sql
@@ -0,0 +1,62 @@
+-- phpMyAdmin SQL Dump
+-- version 3.0.0-beta
+-- http://www.phpmyadmin.net
+--
+-- Host: localhost
+-- Generation Time: Jan 12, 2009 at 08:55 PM
+-- Server version: 5.0.67
+-- PHP Version: 5.2.6
+
+SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+
+--
+-- Database: `twogeekt_lanai`
+--
+
+--
+-- Dumping data for table `badge`
+--
+
+INSERT INTO `badge` (`id`, `name`, `type`, `slug`, `description`, `multiple`, `awarded_count`) VALUES
+(1, '鐐肩嫳娉曞笀', 3, '鐐肩嫳娉曞笀', '鍒犻櫎鑷繁鏈3涓互涓婅禐鎴愮エ鐨勫笘瀛', 1, 0),
+(2, '鍘嬪姏鐧介', 3, '鍘嬪姏鐧介', '鍒犻櫎鑷繁鏈3涓互涓婂弽瀵圭エ鐨勫笘瀛', 1, 0),
+(3, '浼樼鍥炵瓟', 3, '浼樼鍥炵瓟', '鍥炵瓟濂借瘎10娆′互涓', 1, 0),
+(4, '浼樼闂', 3, '浼樼闂', '闂濂借瘎10娆′互涓', 1, 0),
+(5, '璇勮瀹', 3, '璇勮瀹', '璇勮10娆′互涓', 1, 0),
+(6, '娴佽闂', 3, '娴佽闂', '闂鐨勬祻瑙堥噺瓒呰繃1000浜烘', 1, 0),
+(7, '宸¢诲叺', 3, '宸¢诲叺', '绗竴娆℃爣璁板瀮鍦惧笘瀛', 1, 0),
+(8, '娓呮磥宸', 3, '娓呮磥宸', '绗竴娆℃挙閿鎶曠エ', 1, 0),
+(9, '鎵硅瘎瀹', 3, '鎵硅瘎瀹', '绗竴娆″弽瀵圭エ', 1, 0),
+(10, '灏忕紪', 3, '灏忕紪', '绗竴娆$紪杈戞洿鏂', 1, 0),
+(11, '鏉戦暱', 3, '鏉戦暱', '绗竴娆¢噸鏂版爣绛', 1, 0),
+(12, '瀛﹁', 3, '瀛﹁', '绗竴娆℃爣璁扮瓟妗', 1, 0),
+(13, '瀛︾敓', 3, '瀛︾敓', '绗竴娆℃彁闂苟涓旀湁涓娆′互涓婅禐鎴愮エ', 1, 0),
+(14, '鏀寔鑰', 3, '鏀寔鑰', '绗竴娆¤禐鎴愮エ', 1, 0),
+(15, '鏁欏笀', 3, '鏁欏笀', '绗竴娆″洖绛旈棶棰樺苟涓斿緱鍒颁竴涓互涓婅禐鎴愮エ', 1, 0),
+(16, '鑷紶浣滆', 3, '鑷紶浣滆', '瀹屾暣濉啓鐢ㄦ埛璧勬枡鎵鏈夐夐」', 1, 0),
+(17, '鑷鎴愭墠', 3, '鑷鎴愭墠', '鍥炵瓟鑷繁鐨勯棶棰樺苟涓旀湁3涓互涓婅禐鎴愮エ', 1, 0),
+(18, '鏈鏈変环鍊煎洖绛', 1, '鏈鏈変环鍊煎洖绛', '鍥炵瓟瓒呰繃100娆¤禐鎴愮エ', 1, 0),
+(19, '鏈鏈変环鍊奸棶棰', 1, '鏈鏈変环鍊奸棶棰', '闂瓒呰繃100娆¤禐鎴愮エ', 1, 0),
+(20, '涓囦汉杩', 1, '涓囦汉杩', '闂琚100浜轰互涓婃敹钘', 1, 0),
+(21, '钁楀悕闂', 1, '钁楀悕闂', '闂鐨勬祻瑙堥噺瓒呰繃10000浜烘', 1, 0),
+(22, 'alpha鐢ㄦ埛', 2, 'alpha鐢ㄦ埛', '鍐呮祴鏈熼棿鐨勬椿璺冪敤鎴', 1, 0),
+(23, '鏋佸ソ鍥炵瓟', 2, '鏋佸ソ鍥炵瓟', '鍥炵瓟瓒呰繃25娆¤禐鎴愮エ', 1, 0),
+(24, '鏋佸ソ闂', 2, '鏋佸ソ闂', '闂瓒呰繃25娆¤禐鎴愮エ', 1, 0),
+(25, '鍙楁杩庨棶棰', 2, '鍙楁杩庨棶棰', '闂琚25浜轰互涓婃敹钘', 1, 0),
+(26, '浼樼甯傛皯', 2, '浼樼甯傛皯', '鎶曠エ300娆′互涓', 1, 0),
+(27, '缂栬緫涓讳换', 2, '缂栬緫涓讳换', '缂栬緫浜100涓笘瀛', 1, 0),
+(28, '閫氭墠', 2, '閫氭墠', '鍦ㄥ涓爣绛鹃鍩熸椿璺', 1, 0),
+(29, '涓撳', 2, '涓撳', '鍦ㄤ竴涓爣绛鹃鍩熸椿璺冨嚭浼', 1, 0),
+(30, '鑰侀笩', 2, '鑰侀笩', '娲昏穬瓒呰繃涓骞寸殑鐢ㄦ埛', 1, 0),
+(31, '鏈鍙楀叧娉ㄩ棶棰', 2, '鏈鍙楀叧娉ㄩ棶棰', '闂鐨勬祻瑙堥噺瓒呰繃2500浜烘', 1, 0),
+(32, '瀛﹂棶瀹', 2, '瀛﹂棶瀹', '绗竴娆″洖绛旇鎶曡禐鎴愮エ10娆′互涓', 1, 0),
+(33, 'beta鐢ㄦ埛', 2, 'beta鐢ㄦ埛', 'beta鏈熼棿娲昏穬鍙備笌', 1, 0),
+(34, '瀵煎笀', 2, '瀵煎笀', '琚寚瀹氫负鏈浣崇瓟妗堝苟涓旇禐鎴愮エ40浠ヤ笂', 1, 0),
+(35, '宸笀', 2, '宸笀', '鍦ㄦ彁闂60澶╀箣鍚庡洖绛斿苟涓旇禐鎴愮エ5娆′互涓', 1, 0),
+(36, '鍒嗙被涓撳', 2, '鍒嗙被涓撳', '鍒涘缓鐨勬爣绛捐50涓互涓婇棶棰樹娇鐢', 1, 0);
diff --git a/sql_scripts/update_2009_01_13_002.sql b/sql_scripts/update_2009_01_13_002.sql
new file mode 100644
index 00000000..c223cb8c
--- /dev/null
+++ b/sql_scripts/update_2009_01_13_002.sql
@@ -0,0 +1 @@
+ALTER TABLE activity ADD COLUMN is_auditted tinyint(1) DEFAULT 0
\ No newline at end of file
diff --git a/sql_scripts/update_2009_01_18_001.sql b/sql_scripts/update_2009_01_18_001.sql
new file mode 100644
index 00000000..6f29fa32
--- /dev/null
+++ b/sql_scripts/update_2009_01_18_001.sql
@@ -0,0 +1,62 @@
+-- phpMyAdmin SQL Dump
+-- version 3.0.0-beta
+-- http://www.phpmyadmin.net
+--
+-- Host: localhost
+-- Generation Time: Jan 12, 2009 at 08:55 PM
+-- Server version: 5.0.67
+-- PHP Version: 5.2.6
+
+SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+
+--
+-- Database: `twogeekt_lanai`
+--
+
+--
+-- Dumping data for table `badge`
+--
+
+INSERT INTO `badge` (`id`, `name`, `type`, `slug`, `description`, `multiple`, `awarded_count`) VALUES
+(1, '鐐肩嫳娉曞笀', 3, '鐐肩嫳娉曞笀', '鍒犻櫎鑷繁鏈3涓互涓婅禐鎴愮エ鐨勫笘瀛', 1, 0),
+(2, '鍘嬪姏鐧介', 3, '鍘嬪姏鐧介', '鍒犻櫎鑷繁鏈3涓互涓婂弽瀵圭エ鐨勫笘瀛', 1, 0),
+(3, '浼樼鍥炵瓟', 3, '浼樼鍥炵瓟', '鍥炵瓟濂借瘎10娆′互涓', 1, 0),
+(4, '浼樼闂', 3, '浼樼闂', '闂濂借瘎10娆′互涓', 1, 0),
+(5, '璇勮瀹', 3, '璇勮瀹', '璇勮10娆′互涓', 0, 0),
+(6, '娴佽闂', 3, '娴佽闂', '闂鐨勬祻瑙堥噺瓒呰繃1000浜烘', 1, 0),
+(7, '宸¢诲叺', 3, '宸¢诲叺', '绗竴娆℃爣璁板瀮鍦惧笘瀛', 0, 0),
+(8, '娓呮磥宸', 3, '娓呮磥宸', '绗竴娆℃挙閿鎶曠エ', 0, 0),
+(9, '鎵硅瘎瀹', 3, '鎵硅瘎瀹', '绗竴娆″弽瀵圭エ', 0, 0),
+(10, '灏忕紪', 3, '灏忕紪', '绗竴娆$紪杈戞洿鏂', 0, 0),
+(11, '鏉戦暱', 3, '鏉戦暱', '绗竴娆¢噸鏂版爣绛', 0, 0),
+(12, '瀛﹁', 3, '瀛﹁', '绗竴娆℃爣璁扮瓟妗', 0, 0),
+(13, '瀛︾敓', 3, '瀛︾敓', '绗竴娆℃彁闂苟涓旀湁涓娆′互涓婅禐鎴愮エ', 0, 0),
+(14, '鏀寔鑰', 3, '鏀寔鑰', '绗竴娆¤禐鎴愮エ', 0, 0),
+(15, '鏁欏笀', 3, '鏁欏笀', '绗竴娆″洖绛旈棶棰樺苟涓斿緱鍒颁竴涓互涓婅禐鎴愮エ', 0, 0),
+(16, '鑷紶浣滆', 3, '鑷紶浣滆', '瀹屾暣濉啓鐢ㄦ埛璧勬枡鎵鏈夐夐」', 0, 0),
+(17, '鑷鎴愭墠', 3, '鑷鎴愭墠', '鍥炵瓟鑷繁鐨勯棶棰樺苟涓旀湁3涓互涓婅禐鎴愮エ', 1, 0),
+(18, '鏈鏈変环鍊煎洖绛', 1, '鏈鏈変环鍊煎洖绛', '鍥炵瓟瓒呰繃100娆¤禐鎴愮エ', 1, 0),
+(19, '鏈鏈変环鍊奸棶棰', 1, '鏈鏈変环鍊奸棶棰', '闂瓒呰繃100娆¤禐鎴愮エ', 1, 0),
+(20, '涓囦汉杩', 1, '涓囦汉杩', '闂琚100浜轰互涓婃敹钘', 1, 0),
+(21, '钁楀悕闂', 1, '钁楀悕闂', '闂鐨勬祻瑙堥噺瓒呰繃10000浜烘', 1, 0),
+(22, 'alpha鐢ㄦ埛', 2, 'alpha鐢ㄦ埛', '鍐呮祴鏈熼棿鐨勬椿璺冪敤鎴', 0, 0),
+(23, '鏋佸ソ鍥炵瓟', 2, '鏋佸ソ鍥炵瓟', '鍥炵瓟瓒呰繃25娆¤禐鎴愮エ', 1, 0),
+(24, '鏋佸ソ闂', 2, '鏋佸ソ闂', '闂瓒呰繃25娆¤禐鎴愮エ', 1, 0),
+(25, '鍙楁杩庨棶棰', 2, '鍙楁杩庨棶棰', '闂琚25浜轰互涓婃敹钘', 1, 0),
+(26, '浼樼甯傛皯', 2, '浼樼甯傛皯', '鎶曠エ300娆′互涓', 0, 0),
+(27, '缂栬緫涓讳换', 2, '缂栬緫涓讳换', '缂栬緫浜100涓笘瀛', 0, 0),
+(28, '閫氭墠', 2, '閫氭墠', '鍦ㄥ涓爣绛鹃鍩熸椿璺', 0, 0),
+(29, '涓撳', 2, '涓撳', '鍦ㄤ竴涓爣绛鹃鍩熸椿璺冨嚭浼', 0, 0),
+(30, '鑰侀笩', 2, '鑰侀笩', '娲昏穬瓒呰繃涓骞寸殑鐢ㄦ埛', 0, 0),
+(31, '鏈鍙楀叧娉ㄩ棶棰', 2, '鏈鍙楀叧娉ㄩ棶棰', '闂鐨勬祻瑙堥噺瓒呰繃2500浜烘', 1, 0),
+(32, '瀛﹂棶瀹', 2, '瀛﹂棶瀹', '绗竴娆″洖绛旇鎶曡禐鎴愮エ10娆′互涓', 0, 0),
+(33, 'beta鐢ㄦ埛', 2, 'beta鐢ㄦ埛', 'beta鏈熼棿娲昏穬鍙備笌', 0, 0),
+(34, '瀵煎笀', 2, '瀵煎笀', '琚寚瀹氫负鏈浣崇瓟妗堝苟涓旇禐鎴愮エ40浠ヤ笂', 1, 0),
+(35, '宸笀', 2, '宸笀', '鍦ㄦ彁闂60澶╀箣鍚庡洖绛斿苟涓旇禐鎴愮エ5娆′互涓', 1, 0),
+(36, '鍒嗙被涓撳', 2, '鍒嗙被涓撳', '鍒涘缓鐨勬爣绛捐50涓互涓婇棶棰樹娇鐢', 1, 0);
diff --git a/sql_scripts/update_2009_01_24.sql b/sql_scripts/update_2009_01_24.sql
new file mode 100644
index 00000000..45b83935
--- /dev/null
+++ b/sql_scripts/update_2009_01_24.sql
@@ -0,0 +1,2 @@
+ALTER TABLE award ADD COLUMN `content_type_id` int(11);
+ALTER TABLE award ADD COLUMN `object_id` int(10);
\ No newline at end of file
diff --git a/sql_scripts/update_2009_01_25_001.sql b/sql_scripts/update_2009_01_25_001.sql
new file mode 100644
index 00000000..1f1942e3
--- /dev/null
+++ b/sql_scripts/update_2009_01_25_001.sql
@@ -0,0 +1,2 @@
+锘緼LTER TABLE `award` ADD `content_type_id` INT NULL
+ALTER TABLE `award` ADD `object_id` INT NULL
\ No newline at end of file
diff --git a/sql_scripts/update_2009_02_26_001.sql b/sql_scripts/update_2009_02_26_001.sql
new file mode 100644
index 00000000..9cc80974
--- /dev/null
+++ b/sql_scripts/update_2009_02_26_001.sql
@@ -0,0 +1,19 @@
+锘緼LTER TABLE answer ADD COLUMN `accepted_at` datetime default null;
+
+/* Update accepted_at column with answer added datetime for existing data */
+UPDATE answer
+SET accepted_at = added_at
+WHERE accepted = 1 AND accepted_at IS NULL;
+
+/* workround for c# url problem on bluehost server */
+UPDATE tag
+SET name = 'csharp'
+WHERE name = 'c#'
+
+UPDATE question
+SET tagnames = replace(tagnames, 'c#', 'csharp')
+WHERE tagnames like '%c#%'
+
+UPDATE question_revision
+SET tagnames = replace(tagnames, 'c#', 'csharp')
+WHERE tagnames like '%c#%'
\ No newline at end of file
diff --git a/sql_scripts/update_2009_04_10_001.sql b/sql_scripts/update_2009_04_10_001.sql
new file mode 100644
index 00000000..b0d05ac7
--- /dev/null
+++ b/sql_scripts/update_2009_04_10_001.sql
@@ -0,0 +1,3 @@
+锘緼LTER TABLE Tag ADD COLUMN deleted_at datetime default null;
+ALTER TABLE Tag ADD COLUMN deleted_by_id INTEGER NULL;
+ALTER TABLE Tag ADD COLUMN deleted TINYINT NOT NULL;
\ No newline at end of file
diff --git a/sql_scripts/update_2009_12_24_001.sql b/sql_scripts/update_2009_12_24_001.sql
new file mode 100644
index 00000000..3d082c2f
--- /dev/null
+++ b/sql_scripts/update_2009_12_24_001.sql
@@ -0,0 +1,5 @@
+alter table question add column `vote_up_count` int(11) NOT NULL;
+alter table question add column `vote_down_count` int(11) NOT NULL;
+
+alter table answer add column `vote_up_count` int(11) NOT NULL;
+alter table answer add column `vote_down_count` int(11) NOT NULL;
\ No newline at end of file
diff --git a/sql_scripts/update_2009_12_27_001.sql b/sql_scripts/update_2009_12_27_001.sql
new file mode 100644
index 00000000..e2da7d4d
--- /dev/null
+++ b/sql_scripts/update_2009_12_27_001.sql
@@ -0,0 +1,3 @@
+ALTER TABLE comment DROP INDEX content_type_id;
+
+ALTER TABLE comment ADD INDEX `content_type_id` (`content_type_id`,`object_id`,`user_id`);
\ No newline at end of file
diff --git a/sql_scripts/update_2009_12_27_002.sql b/sql_scripts/update_2009_12_27_002.sql
new file mode 100644
index 00000000..a36470bf
--- /dev/null
+++ b/sql_scripts/update_2009_12_27_002.sql
@@ -0,0 +1 @@
+ALTER TABLE `vote` ADD `voted_at` DATETIME NOT NULL
\ No newline at end of file
diff --git a/templates/404.html b/templates/404.html
new file mode 100644
index 00000000..02725854
--- /dev/null
+++ b/templates/404.html
@@ -0,0 +1,48 @@
+{% extends "base_content.html" %}
+{% block title %}{% spaceless %}404 Error{% endspaceless %}{% endblock %}
+{% block forestyle%}
+
+{% endblock %}
+{% block forejs %}
+
+{% endblock %}
+{% block content %}
+
{% blocktrans %}This is where you can change the email address associated with your account. Please keep this email address up to date so we can send you a password-reset email if you request one.{% endblocktrans %}
";o.append(m)};var e=function(n,m){j(n);var l="#"+m;var k=$(l+" textarea");$.ajax({type:"POST",url:"/"+d+"s/"+n+"/comments/",dataType:"json",data:{comment:k.val()},success:function(o){c(n,o);k.val("");commentsFactory[d].updateTextCounter(k);enableSubmitButton(l)},error:function(o,q,p){removeLoader();showMessage(l,o.responseText);enableSubmitButton(l)}})};return{init:function(){$("a[id^='comments-link-"+d+"-']").unbind("click").click(function(){commentsFactory[d].show($(this).attr("id").substr(("comments-link-"+d+"-").length))})},show:function(l){var k=b(l);a(l,k);f(l,k);k.show();if(h(l,k)){k.find("textarea").get(0).focus()}k.siblings("a").unbind("click").click(function(){commentsFactory[d].hide(l)}).text("闅愯棌璇勮")},hide:function(n){var m=b(n);var k=m.children("div.comments").children().length;var l=k==0?"娣诲姞璇勮":"璇勮 ("+k+")";m.hide();m.siblings("a").unbind("click").click(function(){commentsFactory[d].show(n)}).html(l);m.children("div.comments").children().hide()},deleteComment:function(m,l,k){if(confirm("鐪熻鍒犻櫎姝よ瘎璁哄悧锛")){m.hide();j(l);$.post(k,{dataNeeded:"forIIS7"},function(n){c(l,n)},"json")}},updateTextCounter:function(l){var n=l.value?l.value.length:0;var m=n>270?"#f00":n>200?"#f60":"#999";var k=$(l).siblings("span.text-counter");k.html("杩樺彲鍐"+(300-n)+" 瀛楃").css("color",m)}}}var questionComments=createComments("question");var answerComments=createComments("answer");$().ready(function(){questionComments.init();answerComments.init()});var commentsFactory={question:questionComments,answer:answerComments};var PR_SHOULD_USE_CONTINUATION=true;var PR_TAB_WIDTH=8;var PR_normalizedHtml;var PR;var prettyPrintOne;var prettyPrint;function _pr_isIE6(){var a=navigator&&navigator.userAgent&&/\bMSIE 6\./.test(navigator.userAgent);_pr_isIE6=function(){return a};return a}(function(){function W(av){av=av.split(/ /g);var aw={};for(var au=av.length;--au>=0;){var at=av[au];if(at){aw[at]=null}}return aw}var n="break continue do else for if return while ";var D=n+"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 w=D+"catch class delete false import new operator private protected public this throw true try ";var p=w+"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 G=w+"boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient ";var ap=G+"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 F=w+"debugger eval export function get null set undefined var with Infinity NaN ";var y="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 ab=n+"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 k=n+"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 aa=n+"case done elif esac eval fi function in local set then until ";var M=(p+ap+F+y+ab+k+aa);var O="str";var L="kwd";var o="com";var al="typ";var Y="lit";var ah="pun";var V="pln";var q="tag";var U="dec";var ad="src";var ao="atn";var s="atv";var ak="nocode";function aq(at){return(at>="a"&&at<="z")||(at>="A"&&at<="Z")}function N(aw,au,at,av){aw.unshift(at,av||0);try{au.splice.apply(au,aw)}finally{aw.splice(0,2)}}var ai=function(){var av=["!","!=","!==","#","%","%=","&","&&","&&=","&=","(","*","*=","+=",",","-=","->","/","/=",":","::",";","<","<<","<<=","<=","=","==","===",">",">=",">>",">>=",">>>",">>>=","?","@","[","^","^=","^^","^^=","{","|","|=","||","||=","~","break","case","continue","delete","do","else","finally","instanceof","return","throw","try","typeof"];var aw="(?:(?:(?:^|[^0-9.])\\.{1,3})|(?:(?:^|[^\\+])\\+)|(?:(?:^|[^\\-])-)";for(var at=0;at:&])/g,"\\$1")}}aw+="|^)\\s*$";return new RegExp(aw)}();var S=/&/g;var Z=//g;var K=/\"/g;function E(at){return at.replace(S,"&").replace(Z,"<").replace(x,">").replace(K,""")}function r(at){return at.replace(S,"&").replace(Z,"<").replace(x,">")}var e=/</g;var C=/>/g;var d=/'/g;var i=/"/g;var ar=/&/g;var J=/ /g;function t(aw){var ay=aw.indexOf("&");if(ay<0){return aw}for(--ay;(ay=aw.indexOf("",ay+1))>=0;){var at=aw.indexOf(";",ay);if(at>=0){var av=aw.substring(ay+3,at);var ax=10;if(av&&av.charAt(0)==="x"){av=av.substring(1);ax=16}var au=parseInt(av,ax);if(!isNaN(au)){aw=(aw.substring(0,ay)+String.fromCharCode(au)+aw.substring(at+1))}}}return aw.replace(e,"<").replace(C,">").replace(d,"'").replace(i,'"').replace(ar,"&").replace(J," ")}function R(at){return"XMP"===at.tagName}function an(ax,av){switch(ax.nodeType){case 1:var au=ax.tagName.toLowerCase();av.push("<",au);for(var aw=0;aw");for(var ay=ax.firstChild;ay;ay=ay.nextSibling){an(ay,av)}if(ax.firstChild||!/^(?:br|link|img)$/.test(au)){av.push("",au,">")}break;case 2:av.push(ax.name.toLowerCase(),'="',E(ax.value),'"');break;case 3:case 4:av.push(r(ax.nodeValue));break}}var am=null;function b(aw){if(null===am){var au=document.createElement("PRE");au.appendChild(document.createTextNode('\n'));am=!/=0;aw-=at.length){ax.push(at.substring(0,aw))}aC=ay+1;break;case"\n":au=0;break;default:++au}}if(!ax){return az}ax.push(az.substring(aC));return ax.join("")}}var Q=/(?:[^<]+|||<\/?[a-zA-Z][^>]*>|<)/g;var v=/^|$)/,null],[ad,/^<\?[\s\S]*?(?:\?>|$)/,null],[ad,/^<%[\s\S]*?(?:%>|$)/,null],[ad,/^<(script|style|xmp)\b[^>]*>[\s\S]*?<\/\1\b[^>]*>/i,null],[q,/^<\/?\w[^<>]*>/,null]]);var z=/^(<[^>]*>)([\s\S]*)(<\/[^>]*>)$/;function ae(ay){var av=a(ay);for(var ax=0;ax\/=]+/,null,"<>/="]],[[q,/^[\w:\-]+/,/^],[s,/^[\w\-]+/,/^=/],[ao,/^[\w:\-]+/,null],[V,/^\s+/,null," \t\r\n"]]);function I(ay,au){for(var aw=0;aw=2&&/^[\"\']/.test(aG)&&aG.charAt(0)===aG.charAt(au-1));var aw;var ay;var aC;if(aA){ay=ax+1;aC=aD-1;aw=aG}else{ay=ax+1;aC=aD-1;aw=aG.substring(1,aG.length-1)}var aH=af(aw);for(var aE=0,az=aH.length;aEaI){if(ax&&ax!==aC){aB.push("");ax=null}if(!ax&&aC){ax=aC;aB.push('')}var aL=r(az(aJ.substring(aI,aK))).replace(aE?aF:aw,"$1 ");aE=aD.test(aL);aB.push(aL.replace(aA," "));aI=aK}}while(true){var at;if(av");ax=null}aB.push(au[av+1]);av+=2}else{if(aH")}return aB.join("")}var A={};function g(av,aw){for(var at=aw.length;--at>=0;){var au=aw[at];if(!A.hasOwnProperty(au)){A[au]=av}else{if("console" in window){console.log("cannot override language handler %s",au)}}}}g(af,["default-code"]);g(l,["default-markup","html","htm","xhtml","xml","xsl"]);g(m({keywords:p,hashComments:true,cStyleComments:true}),["c","cc","cpp","cs","cxx","cyc"]);g(m({keywords:G,cStyleComments:true}),["java"]);g(m({keywords:aa,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);g(m({keywords:ab,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);g(m({keywords:y,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);g(m({keywords:k,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);g(m({keywords:F,cStyleComments:true,regexLiterals:true}),["js"]);function H(ay,aw){try{var az=B(ay);var av=az.source;var at=az.tags;if(!A.hasOwnProperty(aw)){aw=/^\s*=0){var aD=aH.className.match(/\blang-(\w+)\b/);if(aD){aD=aD[1]}var aM=false;for(var aB=aH.parentNode;aB;aB=aB.parentNode){if((aB.tagName==="pre"||aB.tagName==="code"||aB.tagName==="xmp")&&aB.className&&aB.className.indexOf("prettyprint")>=0){aM=true;break}}if(!aM){var aJ=b(aH);aJ=aJ.replace(/(?:\r\n?|\n)$/,"");var aN=H(aJ,aD);if(!R(aH)){aH.innerHTML=aN}else{var aC=document.createElement("PRE");for(var aF=0;aF=0;){var aL=aI[aE];aL.parentNode.replaceChild(document.createTextNode("\r\n"),aL)}}}}}if(at
' + msg + '
(鐐瑰嚮娑堟伅妗嗗叧闂)
');
+
+ div.click(function(event) {
+ $(".vote-notification").fadeOut("fast", function() { $(this).remove(); });
+ });
+
+ object.parent().append(div);
+ div.fadeIn("fast");
+};
+
+var notify = function() {
+ var visible = false;
+ return {
+ show: function(html) {
+ if (html) {
+ $("body").css("margin-top", "2.2em");
+ $(".notify span").html(html);
+ }
+ $(".notify").fadeIn("slow");
+ visible = true;
+ },
+ close: function(doPostback) {
+ if (doPostback) {
+ $.post("/messages/markread/", { formdata: "required" });
+ }
+ $(".notify").fadeOut("fast");
+ $("body").css("margin-top", "0");
+ visible = false;
+ },
+ isVisible: function() { return visible; }
+ };
+} ();
+
+function appendLoader(containerSelector) {
+ $(containerSelector).append('');
+}
+
+function removeLoader() {
+ $("img.ajax-loader").remove();
+}
+
+function setupFormValidation(formSelector, validationRules, validationMessages, onSubmitCallback) {
+ enableSubmitButton(formSelector);
+ $(formSelector).validate({
+ rules: (validationRules ? validationRules : {}),
+ messages: (validationMessages ? validationMessages : {}),
+ errorElement: "span",
+ errorClass: "form-error",
+ errorPlacement: function(error, element) {
+ var span = element.next().find("span.form-error");
+ if (span.length == 0) {
+ span = element.parent().find("span.form-error");
+ }
+ span.replaceWith(error);
+ },
+ submitHandler: function(form) {
+ disableSubmitButton(formSelector);
+
+ if (onSubmitCallback)
+ onSubmitCallback();
+ else
+ form.submit();
+ }
+ });
+}
+
+function enableSubmitButton(formSelector) {
+ setSubmitButtonDisabled(formSelector, false);
+}
+function disableSubmitButton(formSelector) {
+ setSubmitButtonDisabled(formSelector, true);
+}
+function setSubmitButtonDisabled(formSelector, isDisabled) {
+ $(formSelector).find("input[type='submit']").attr("disabled", isDisabled ? "true" : "");
+}
+
+var CPValidator = function(){
+ return {
+ getQuestionFormRules : function(){
+ return {
+ tags: {
+ required: true,
+ maxlength: 105
+ },
+ text: {
+ required: true,
+ minlength: 10
+ },
+ title: {
+ required: true,
+ minlength: 10
+ }
+ };
+ },
+ getQuestionFormMessages: function(){
+ return {
+ tags: {
+ required: " 鏍囩涓嶈兘涓虹┖銆",
+ maxlength: " 鏈澶5涓爣绛撅紝姣忎釜鏍囩闀垮害灏忎簬20涓瓧绗︺"
+ },
+ text: {
+ required: " 鍐呭涓嶈兘涓虹┖銆",
+ minlength: jQuery.format(" 璇疯緭鍏ヨ嚦灏 {0} 瀛楃銆")
+ },
+ title: {
+ required: " 璇疯緭鍏ユ爣棰樸",
+ minlength: jQuery.format(" 璇疯緭鍏ヨ嚦灏 {0} 瀛楃銆")
+ }
+ };
+ }
+ };
+}();
+//Search Engine Keyword Highlight with Javascript
+//http://scott.yang.id.au/code/se-hilite/
+Hilite={elementid:"content",exact:true,max_nodes:1000,onload:true,style_name:"hilite",style_name_suffix:true,debug_referrer:""};Hilite.search_engines=[["local","q"],["cnprog\\.","q"],["google\\.","q"],["search\\.yahoo\\.","p"],["search\\.msn\\.","q"],["search\\.live\\.","query"],["search\\.aol\\.","userQuery"],["ask\\.com","q"],["altavista\\.","q"],["feedster\\.","q"],["search\\.lycos\\.","q"],["alltheweb\\.","q"],["technorati\\.com/search/([^\\?/]+)",1],["dogpile\\.com/info\\.dogpl/search/web/([^\\?/]+)",1,true]];Hilite.decodeReferrer=function(d){var g=null;var e=new RegExp("");for(var c=0;c2&&f[2]){a=decodeURIComponent(a)}a=a.replace(/\'|"/g,"");a=a.split(/[\s,\+\.]+/);return a}break}}return null};Hilite.decodeReferrerQS=function(f,d){var b=f.indexOf("?");var c;if(b>=0){var a=new String(f.substring(b+1));b=0;c=0;while((b>=0)&&((c=a.indexOf("=",b))>=0)){var e,g;e=a.substring(b,c);b=a.indexOf("&",c)+1;if(e==d){if(b<=0){return a.substring(c+1)}else{return a.substring(c+1,b-1)}}else{if(b<=0){return null}}}}return null};Hilite.hiliteElement=function(f,e){if(!e||f.childNodes.length==0){return}var c=new Array();for(var b=0;b0){c++;if(c>=Hilite.max_nodes){var b=function(){Hilite.walkElements(d,f,e)};setTimeout(b,50);return}if(d.nodeType==1){if(!a.test(d.tagName)&&d.childNodes.length>0){d=d.childNodes[0];f++;continue}}else{if(d.nodeType==3){d=e(d)}}if(d.nextSibling){d=d.nextSibling}else{while(f>0){d=d.parentNode;f--;if(d.nextSibling){d=d.nextSibling;break}}}}};if(Hilite.onload){if(window.attachEvent){window.attachEvent("onload",Hilite.hilite)}else{if(window.addEventListener){window.addEventListener("load",Hilite.hilite,false)}else{var __onload=window.onload;window.onload=function(){Hilite.hilite();__onload()}}}};
\ No newline at end of file
diff --git a/templates/content/js/compress.bat b/templates/content/js/compress.bat
new file mode 100644
index 00000000..aa31271c
--- /dev/null
+++ b/templates/content/js/compress.bat
@@ -0,0 +1,6 @@
+#java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 wmd\wmd.js -o wmd\wmd-min.js
+#java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 wmd\showdown.js -o wmd\showdown-min.js
+#java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 com.cnprog.post.js -o com.cnprog.post.pack.js
+java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 se_hilite_src.js -o se_hilite.js
+
+pause
\ No newline at end of file
diff --git a/templates/content/js/excanvas.pack.js b/templates/content/js/excanvas.pack.js
new file mode 100644
index 00000000..71d6fbd9
--- /dev/null
+++ b/templates/content/js/excanvas.pack.js
@@ -0,0 +1 @@
+if(!window.CanvasRenderingContext2D){(function(){var m=Math;var mr=m.round;var ms=m.sin;var mc=m.cos;var Z=10;var Z2=Z/2;var G_vmlCanvasManager_={init:function(opt_doc){var doc=opt_doc||document;if(/MSIE/.test(navigator.userAgent)&&!window.opera){var self=this;doc.attachEvent("onreadystatechange",function(){self.init_(doc)})}},init_:function(doc){if(doc.readyState=="complete"){if(!doc.namespaces["g_vml_"]){doc.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml")}var ss=doc.createStyleSheet();ss.cssText="canvas{display:inline-block;overflow:hidden;"+"text-align:left;width:300px;height:150px}"+"g_vml_\\:*{behavior:url(#default#VML)}";var els=doc.getElementsByTagName("canvas");for(var i=0;i"){var tagName="/"+el.tagName;var ns;while((ns=el.nextSibling)&&ns.tagName!=tagName){ns.removeNode()}if(ns){ns.removeNode()}}el.parentNode.replaceChild(newEl,el);return newEl},initElement:function(el){el=this.fixElement_(el);el.getContext=function(){if(this.context_){return this.context_}return this.context_=new CanvasRenderingContext2D_(this)};el.attachEvent('onpropertychange',onPropertyChange);el.attachEvent('onresize',onResize);var attrs=el.attributes;if(attrs.width&&attrs.width.specified){el.style.width=attrs.width.nodeValue+"px"}else{el.width=el.clientWidth}if(attrs.height&&attrs.height.specified){el.style.height=attrs.height.nodeValue+"px"}else{el.height=el.clientHeight}return el}};function onPropertyChange(e){var el=e.srcElement;switch(e.propertyName){case'width':el.style.width=el.attributes.width.nodeValue+"px";el.getContext().clearRect();break;case'height':el.style.height=el.attributes.height.nodeValue+"px";el.getContext().clearRect();break}}function onResize(e){var el=e.srcElement;if(el.firstChild){el.firstChild.style.width=el.clientWidth+'px';el.firstChild.style.height=el.clientHeight+'px'}}G_vmlCanvasManager_.init();var dec2hex=[];for(var i=0;i<16;i++){for(var j=0;j<16;j++){dec2hex[i*16+j]=i.toString(16)+j.toString(16)}}function createMatrixIdentity(){return[[1,0,0],[0,1,0],[0,0,1]]}function matrixMultiply(m1,m2){var result=createMatrixIdentity();for(var x=0;x<3;x++){for(var y=0;y<3;y++){var sum=0;for(var z=0;z<3;z++){sum+=m1[x][z]*m2[z][y]}result[x][y]=sum}}return result}function copyState(o1,o2){o2.fillStyle=o1.fillStyle;o2.lineCap=o1.lineCap;o2.lineJoin=o1.lineJoin;o2.lineWidth=o1.lineWidth;o2.miterLimit=o1.miterLimit;o2.shadowBlur=o1.shadowBlur;o2.shadowColor=o1.shadowColor;o2.shadowOffsetX=o1.shadowOffsetX;o2.shadowOffsetY=o1.shadowOffsetY;o2.strokeStyle=o1.strokeStyle;o2.arcScaleX_=o1.arcScaleX_;o2.arcScaleY_=o1.arcScaleY_}function processStyle(styleString){var str,alpha=1;styleString=String(styleString);if(styleString.substring(0,3)=="rgb"){var start=styleString.indexOf("(",3);var end=styleString.indexOf(")",start+1);var guts=styleString.substring(start+1,end).split(",");str="#";for(var i=0;i<3;i++){str+=dec2hex[Number(guts[i])]}if((guts.length==4)&&(styleString.substr(3,1)=="a")){alpha=guts[3]}}else{str=styleString}return[str,alpha]}function processLineCap(lineCap){switch(lineCap){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function CanvasRenderingContext2D_(surfaceElement){this.m_=createMatrixIdentity();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=Z*1;this.globalAlpha=1;this.canvas=surfaceElement;var el=surfaceElement.ownerDocument.createElement('div');el.style.width=surfaceElement.clientWidth+'px';el.style.height=surfaceElement.clientHeight+'px';el.style.overflow='hidden';el.style.position='absolute';surfaceElement.appendChild(el);this.element_=el;this.arcScaleX_=1;this.arcScaleY_=1}var contextPrototype=CanvasRenderingContext2D_.prototype;contextPrototype.clearRect=function(){this.element_.innerHTML="";this.currentPath_=[]};contextPrototype.beginPath=function(){this.currentPath_=[]};contextPrototype.moveTo=function(aX,aY){this.currentPath_.push({type:"moveTo",x:aX,y:aY});this.currentX_=aX;this.currentY_=aY};contextPrototype.lineTo=function(aX,aY){this.currentPath_.push({type:"lineTo",x:aX,y:aY});this.currentX_=aX;this.currentY_=aY};contextPrototype.bezierCurveTo=function(aCP1x,aCP1y,aCP2x,aCP2y,aX,aY){this.currentPath_.push({type:"bezierCurveTo",cp1x:aCP1x,cp1y:aCP1y,cp2x:aCP2x,cp2y:aCP2y,x:aX,y:aY});this.currentX_=aX;this.currentY_=aY};contextPrototype.quadraticCurveTo=function(aCPx,aCPy,aX,aY){var cp1x=this.currentX_+2.0/3.0*(aCPx-this.currentX_);var cp1y=this.currentY_+2.0/3.0*(aCPy-this.currentY_);var cp2x=cp1x+(aX-this.currentX_)/3.0;var cp2y=cp1y+(aY-this.currentY_)/3.0;this.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,aX,aY)};contextPrototype.arc=function(aX,aY,aRadius,aStartAngle,aEndAngle,aClockwise){aRadius*=Z;var arcType=aClockwise?"at":"wa";var xStart=aX+(mc(aStartAngle)*aRadius)-Z2;var yStart=aY+(ms(aStartAngle)*aRadius)-Z2;var xEnd=aX+(mc(aEndAngle)*aRadius)-Z2;var yEnd=aY+(ms(aEndAngle)*aRadius)-Z2;if(xStart==xEnd&&!aClockwise){xStart+=0.125}this.currentPath_.push({type:arcType,x:aX,y:aY,radius:aRadius,xStart:xStart,yStart:yStart,xEnd:xEnd,yEnd:yEnd})};contextPrototype.rect=function(aX,aY,aWidth,aHeight){this.moveTo(aX,aY);this.lineTo(aX+aWidth,aY);this.lineTo(aX+aWidth,aY+aHeight);this.lineTo(aX,aY+aHeight);this.closePath()};contextPrototype.strokeRect=function(aX,aY,aWidth,aHeight){this.beginPath();this.moveTo(aX,aY);this.lineTo(aX+aWidth,aY);this.lineTo(aX+aWidth,aY+aHeight);this.lineTo(aX,aY+aHeight);this.closePath();this.stroke()};contextPrototype.fillRect=function(aX,aY,aWidth,aHeight){this.beginPath();this.moveTo(aX,aY);this.lineTo(aX+aWidth,aY);this.lineTo(aX+aWidth,aY+aHeight);this.lineTo(aX,aY+aHeight);this.closePath();this.fill()};contextPrototype.createLinearGradient=function(aX0,aY0,aX1,aY1){var gradient=new CanvasGradient_("gradient");return gradient};contextPrototype.createRadialGradient=function(aX0,aY0,aR0,aX1,aY1,aR1){var gradient=new CanvasGradient_("gradientradial");gradient.radius1_=aR0;gradient.radius2_=aR1;gradient.focus_.x=aX0;gradient.focus_.y=aY0;return gradient};contextPrototype.drawImage=function(image,var_args){var dx,dy,dw,dh,sx,sy,sw,sh;var oldRuntimeWidth=image.runtimeStyle.width;var oldRuntimeHeight=image.runtimeStyle.height;image.runtimeStyle.width='auto';image.runtimeStyle.height='auto';var w=image.width;var h=image.height;image.runtimeStyle.width=oldRuntimeWidth;image.runtimeStyle.height=oldRuntimeHeight;if(arguments.length==3){dx=arguments[1];dy=arguments[2];sx=sy=0;sw=dw=w;sh=dh=h}else if(arguments.length==5){dx=arguments[1];dy=arguments[2];dw=arguments[3];dh=arguments[4];sx=sy=0;sw=w;sh=h}else if(arguments.length==9){sx=arguments[1];sy=arguments[2];sw=arguments[3];sh=arguments[4];dx=arguments[5];dy=arguments[6];dw=arguments[7];dh=arguments[8]}else{throw"Invalid number of arguments";}var d=this.getCoords_(dx,dy);var w2=sw/2;var h2=sh/2;var vmlStr=[];var W=10;var H=10;vmlStr.push(' ','','');this.element_.insertAdjacentHTML("BeforeEnd",vmlStr.join(""))};contextPrototype.stroke=function(aFill){var lineStr=[];var lineOpen=false;var a=processStyle(aFill?this.fillStyle:this.strokeStyle);var color=a[0];var opacity=a[1]*this.globalAlpha;var W=10;var H=10;lineStr.push('max.x){max.x=c.x}if(min.y==null||c.ymax.y){max.y=c.y}}}lineStr.push(' ">');if(typeof this.fillStyle=="object"){var focus={x:"50%",y:"50%"};var width=(max.x-min.x);var height=(max.y-min.y);var dimension=(width>height)?width:height;focus.x=mr((this.fillStyle.focus_.x/width)*100+50)+"%";focus.y=mr((this.fillStyle.focus_.y/height)*100+50)+"%";var colors=[];if(this.fillStyle.type_=="gradientradial"){var inside=(this.fillStyle.radius1_/dimension*100);var expansion=(this.fillStyle.radius2_/dimension*100)-inside}else{var inside=0;var expansion=100}var insidecolor={offset:null,color:null};var outsidecolor={offset:null,color:null};this.fillStyle.colors_.sort(function(cs1,cs2){return cs1.offset-cs2.offset});for(var i=0;iinsidecolor.offset||insidecolor.offset==null){insidecolor.offset=fs.offset;insidecolor.color=fs.color}if(fs.offset')}else if(aFill){lineStr.push('')}else{lineStr.push('')}lineStr.push("");this.element_.insertAdjacentHTML("beforeEnd",lineStr.join(""))};contextPrototype.fill=function(){this.stroke(true)};contextPrototype.closePath=function(){this.currentPath_.push({type:"close"})};contextPrototype.getCoords_=function(aX,aY){return{x:Z*(aX*this.m_[0][0]+aY*this.m_[1][0]+this.m_[2][0])-Z2,y:Z*(aX*this.m_[0][1]+aY*this.m_[1][1]+this.m_[2][1])-Z2}};contextPrototype.save=function(){var o={};copyState(this,o);this.aStack_.push(o);this.mStack_.push(this.m_);this.m_=matrixMultiply(createMatrixIdentity(),this.m_)};contextPrototype.restore=function(){copyState(this.aStack_.pop(),this);this.m_=this.mStack_.pop()};contextPrototype.translate=function(aX,aY){var m1=[[1,0,0],[0,1,0],[aX,aY,1]];this.m_=matrixMultiply(m1,this.m_)};contextPrototype.rotate=function(aRot){var c=mc(aRot);var s=ms(aRot);var m1=[[c,s,0],[-s,c,0],[0,0,1]];this.m_=matrixMultiply(m1,this.m_)};contextPrototype.scale=function(aX,aY){this.arcScaleX_*=aX;this.arcScaleY_*=aY;var m1=[[aX,0,0],[0,aY,0],[0,0,1]];this.m_=matrixMultiply(m1,this.m_)};contextPrototype.clip=function(){};contextPrototype.arcTo=function(){};contextPrototype.createPattern=function(){return new CanvasPattern_};function CanvasGradient_(aType){this.type_=aType;this.radius1_=0;this.radius2_=0;this.colors_=[];this.focus_={x:0,y:0}}CanvasGradient_.prototype.addColorStop=function(aOffset,aColor){aColor=processStyle(aColor);this.colors_.push({offset:1-aOffset,color:aColor})};function CanvasPattern_(){}G_vmlCanvasManager=G_vmlCanvasManager_;CanvasRenderingContext2D=CanvasRenderingContext2D_;CanvasGradient=CanvasGradient_;CanvasPattern=CanvasPattern_})()}
diff --git a/templates/content/js/flot-build.bat b/templates/content/js/flot-build.bat
new file mode 100644
index 00000000..28304966
--- /dev/null
+++ b/templates/content/js/flot-build.bat
@@ -0,0 +1,3 @@
+java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 jquery.flot.js -o jquery.flot.pack.js
+
+pause
\ No newline at end of file
diff --git a/templates/content/js/jquery-1.2.6.js b/templates/content/js/jquery-1.2.6.js
new file mode 100644
index 00000000..88e661ee
--- /dev/null
+++ b/templates/content/js/jquery-1.2.6.js
@@ -0,0 +1,3549 @@
+(function(){
+/*
+ * jQuery 1.2.6 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $
+ * $Rev: 5685 $
+ */
+
+// Map over jQuery in case of overwrite
+var _jQuery = window.jQuery,
+// Map over the $ in case of overwrite
+ _$ = window.$;
+
+var jQuery = window.jQuery = window.$ = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context );
+};
+
+// A simple way to check for HTML strings or ID strings
+// (both of which we optimize for)
+var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,
+
+// Is it a simple selector
+ isSimple = /^.[^:#\[\.]*$/,
+
+// Will speed up references to undefined, and allows munging its name.
+ undefined;
+
+jQuery.fn = jQuery.prototype = {
+ init: function( selector, context ) {
+ // Make sure that a selection was provided
+ selector = selector || document;
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+ // Handle HTML strings
+ if ( typeof selector == "string" ) {
+ // Are we dealing with HTML string or an ID?
+ var match = quickExpr.exec( selector );
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] )
+ selector = jQuery.clean( [ match[1] ], context );
+
+ // HANDLE: $("#id")
+ else {
+ var elem = document.getElementById( match[3] );
+
+ // Make sure an element was located
+ if ( elem ){
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id != match[3] )
+ return jQuery().find( selector );
+
+ // Otherwise, we inject the element directly into the jQuery object
+ return jQuery( elem );
+ }
+ selector = [];
+ }
+
+ // HANDLE: $(expr, [context])
+ // (which is just equivalent to: $(content).find(expr)
+ } else
+ return jQuery( context ).find( selector );
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) )
+ return jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector );
+
+ return this.setArray(jQuery.makeArray(selector));
+ },
+
+ // The current version of jQuery being used
+ jquery: "1.2.6",
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ // The number of elements contained in the matched element set
+ length: 0,
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == undefined ?
+
+ // Return a 'clean' array
+ jQuery.makeArray( this ) :
+
+ // Return just the object
+ this[ num ];
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+ // Build a new jQuery matched element set
+ var ret = jQuery( elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Force the current matched set of elements to become
+ // the specified array of elements (destroying the stack in the process)
+ // You should use pushStack() in order to do this, but maintain the stack
+ setArray: function( elems ) {
+ // Resetting the length to 0, then using the native Array push
+ // is a super-fast way to populate an object with array-like properties
+ this.length = 0;
+ Array.prototype.push.apply( this, elems );
+
+ return this;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+ var ret = -1;
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem && elem.jquery ? elem[0] : elem
+ , this );
+ },
+
+ attr: function( name, value, type ) {
+ var options = name;
+
+ // Look for the case where we're accessing a style value
+ if ( name.constructor == String )
+ if ( value === undefined )
+ return this[0] && jQuery[ type || "attr" ]( this[0], name );
+
+ else {
+ options = {};
+ options[ name ] = value;
+ }
+
+ // Check to see if we're setting style values
+ return this.each(function(i){
+ // Set all the styles
+ for ( name in options )
+ jQuery.attr(
+ type ?
+ this.style :
+ this,
+ name, jQuery.prop( this, options[ name ], type, i, name )
+ );
+ });
+ },
+
+ css: function( key, value ) {
+ // ignore negative width and height values
+ if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
+ value = undefined;
+ return this.attr( key, value, "curCSS" );
+ },
+
+ text: function( text ) {
+ if ( typeof text != "object" && text != null )
+ return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+
+ var ret = "";
+
+ jQuery.each( text || this, function(){
+ jQuery.each( this.childNodes, function(){
+ if ( this.nodeType != 8 )
+ ret += this.nodeType != 1 ?
+ this.nodeValue :
+ jQuery.fn.text( [ this ] );
+ });
+ });
+
+ return ret;
+ },
+
+ wrapAll: function( html ) {
+ if ( this[0] )
+ // The elements to wrap the target around
+ jQuery( html, this[0].ownerDocument )
+ .clone()
+ .insertBefore( this[0] )
+ .map(function(){
+ var elem = this;
+
+ while ( elem.firstChild )
+ elem = elem.firstChild;
+
+ return elem;
+ })
+ .append(this);
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ return this.each(function(){
+ jQuery( this ).contents().wrapAll( html );
+ });
+ },
+
+ wrap: function( html ) {
+ return this.each(function(){
+ jQuery( this ).wrapAll( html );
+ });
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, false, function(elem){
+ if (this.nodeType == 1)
+ this.appendChild( elem );
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, true, function(elem){
+ if (this.nodeType == 1)
+ this.insertBefore( elem, this.firstChild );
+ });
+ },
+
+ before: function() {
+ return this.domManip(arguments, false, false, function(elem){
+ this.parentNode.insertBefore( elem, this );
+ });
+ },
+
+ after: function() {
+ return this.domManip(arguments, false, true, function(elem){
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ },
+
+ end: function() {
+ return this.prevObject || jQuery( [] );
+ },
+
+ find: function( selector ) {
+ var elems = jQuery.map(this, function(elem){
+ return jQuery.find( selector, elem );
+ });
+
+ return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ?
+ jQuery.unique( elems ) :
+ elems );
+ },
+
+ clone: function( events ) {
+ // Do the clone
+ var ret = this.map(function(){
+ if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) {
+ // IE copies events bound via attachEvent when
+ // using cloneNode. Calling detachEvent on the
+ // clone will also remove the events from the orignal
+ // In order to get around this, we use innerHTML.
+ // Unfortunately, this means some modifications to
+ // attributes in IE that are actually only stored
+ // as properties will not be copied (such as the
+ // the name attribute on an input).
+ var clone = this.cloneNode(true),
+ container = document.createElement("div");
+ container.appendChild(clone);
+ return jQuery.clean([container.innerHTML])[0];
+ } else
+ return this.cloneNode(true);
+ });
+
+ // Need to set the expando to null on the cloned set if it exists
+ // removeData doesn't work here, IE removes it from the original as well
+ // this is primarily for IE but the data expando shouldn't be copied over in any browser
+ var clone = ret.find("*").andSelf().each(function(){
+ if ( this[ expando ] != undefined )
+ this[ expando ] = null;
+ });
+
+ // Copy the events from the original to the clone
+ if ( events === true )
+ this.find("*").andSelf().each(function(i){
+ if (this.nodeType == 3)
+ return;
+ var events = jQuery.data( this, "events" );
+
+ for ( var type in events )
+ for ( var handler in events[ type ] )
+ jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data );
+ });
+
+ // Return the cloned set
+ return ret;
+ },
+
+ filter: function( selector ) {
+ return this.pushStack(
+ jQuery.isFunction( selector ) &&
+ jQuery.grep(this, function(elem, i){
+ return selector.call( elem, i );
+ }) ||
+
+ jQuery.multiFilter( selector, this ) );
+ },
+
+ not: function( selector ) {
+ if ( selector.constructor == String )
+ // test special case where just one selector is passed in
+ if ( isSimple.test( selector ) )
+ return this.pushStack( jQuery.multiFilter( selector, this, true ) );
+ else
+ selector = jQuery.multiFilter( selector, this );
+
+ var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+ return this.filter(function() {
+ return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
+ });
+ },
+
+ add: function( selector ) {
+ return this.pushStack( jQuery.unique( jQuery.merge(
+ this.get(),
+ typeof selector == 'string' ?
+ jQuery( selector ) :
+ jQuery.makeArray( selector )
+ )));
+ },
+
+ is: function( selector ) {
+ return !!selector && jQuery.multiFilter( selector, this ).length > 0;
+ },
+
+ hasClass: function( selector ) {
+ return this.is( "." + selector );
+ },
+
+ val: function( value ) {
+ if ( value == undefined ) {
+
+ if ( this.length ) {
+ var elem = this[0];
+
+ // We need to handle select boxes special
+ if ( jQuery.nodeName( elem, "select" ) ) {
+ var index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type == "select-one";
+
+ // Nothing was selected
+ if ( index < 0 )
+ return null;
+
+ // Loop through all the selected options
+ for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+ var option = options[ i ];
+
+ if ( option.selected ) {
+ // Get the specifc value for the option
+ value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value;
+
+ // We don't need an array for one selects
+ if ( one )
+ return value;
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+
+ // Everything else, we just grab the value
+ } else
+ return (this[0].value || "").replace(/\r/g, "");
+
+ }
+
+ return undefined;
+ }
+
+ if( value.constructor == Number )
+ value += '';
+
+ return this.each(function(){
+ if ( this.nodeType != 1 )
+ return;
+
+ if ( value.constructor == Array && /radio|checkbox/.test( this.type ) )
+ this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+ jQuery.inArray(this.name, value) >= 0);
+
+ else if ( jQuery.nodeName( this, "select" ) ) {
+ var values = jQuery.makeArray(value);
+
+ jQuery( "option", this ).each(function(){
+ this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
+ jQuery.inArray( this.text, values ) >= 0);
+ });
+
+ if ( !values.length )
+ this.selectedIndex = -1;
+
+ } else
+ this.value = value;
+ });
+ },
+
+ html: function( value ) {
+ return value == undefined ?
+ (this[0] ?
+ this[0].innerHTML :
+ null) :
+ this.empty().append( value );
+ },
+
+ replaceWith: function( value ) {
+ return this.after( value ).remove();
+ },
+
+ eq: function( i ) {
+ return this.slice( i, i + 1 );
+ },
+
+ slice: function() {
+ return this.pushStack( Array.prototype.slice.apply( this, arguments ) );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function(elem, i){
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ },
+
+ data: function( key, value ){
+ var parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ if ( data === undefined && this.length )
+ data = jQuery.data( this[0], key );
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ } else
+ return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
+ jQuery.data( this, key, value );
+ });
+ },
+
+ removeData: function( key ){
+ return this.each(function(){
+ jQuery.removeData( this, key );
+ });
+ },
+
+ domManip: function( args, table, reverse, callback ) {
+ var clone = this.length > 1, elems;
+
+ return this.each(function(){
+ if ( !elems ) {
+ elems = jQuery.clean( args, this.ownerDocument );
+
+ if ( reverse )
+ elems.reverse();
+ }
+
+ var obj = this;
+
+ if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) )
+ obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") );
+
+ var scripts = jQuery( [] );
+
+ jQuery.each(elems, function(){
+ var elem = clone ?
+ jQuery( this ).clone( true )[0] :
+ this;
+
+ // execute all scripts after the elements have been injected
+ if ( jQuery.nodeName( elem, "script" ) )
+ scripts = scripts.add( elem );
+ else {
+ // Remove any inner scripts for later evaluation
+ if ( elem.nodeType == 1 )
+ scripts = scripts.add( jQuery( "script", elem ).remove() );
+
+ // Inject the elements into the document
+ callback.call( obj, elem );
+ }
+ });
+
+ scripts.each( evalScript );
+ });
+ }
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+function evalScript( i, elem ) {
+ if ( elem.src )
+ jQuery.ajax({
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+
+ else
+ jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+
+ if ( elem.parentNode )
+ elem.parentNode.removeChild( elem );
+}
+
+function now(){
+ return +new Date;
+}
+
+jQuery.extend = jQuery.fn.extend = function() {
+ // copy reference to target object
+ var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+ // Handle a deep copy situation
+ if ( target.constructor == Boolean ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target != "object" && typeof target != "function" )
+ target = {};
+
+ // extend jQuery itself if only one argument is passed
+ if ( length == i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ )
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null )
+ // Extend the base object
+ for ( var name in options ) {
+ var src = target[ name ], copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy )
+ continue;
+
+ // Recurse if we're merging object values
+ if ( deep && copy && typeof copy == "object" && !copy.nodeType )
+ target[ name ] = jQuery.extend( deep,
+ // Never move original objects, clone them
+ src || ( copy.length != null ? [ ] : { } )
+ , copy );
+
+ // Don't bring in undefined values
+ else if ( copy !== undefined )
+ target[ name ] = copy;
+
+ }
+
+ // Return the modified object
+ return target;
+};
+
+var expando = "jQuery" + now(), uuid = 0, windowData = {},
+ // exclude the following css properties to add px
+ exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
+ // cache defaultView
+ defaultView = document.defaultView || {};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ window.$ = _$;
+
+ if ( deep )
+ window.jQuery = _jQuery;
+
+ return jQuery;
+ },
+
+ // See test/unit/core.js for details concerning this function.
+ isFunction: function( fn ) {
+ return !!fn && typeof fn != "string" && !fn.nodeName &&
+ fn.constructor != Array && /^[\s[]?function/.test( fn + "" );
+ },
+
+ // check if an element is in a (or is an) XML document
+ isXMLDoc: function( elem ) {
+ return elem.documentElement && !elem.body ||
+ elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+ },
+
+ // Evalulates a script in a global context
+ globalEval: function( data ) {
+ data = jQuery.trim( data );
+
+ if ( data ) {
+ // Inspired by code by Andrea Giammarchi
+ // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+ var head = document.getElementsByTagName("head")[0] || document.documentElement,
+ script = document.createElement("script");
+
+ script.type = "text/javascript";
+ if ( jQuery.browser.msie )
+ script.text = data;
+ else
+ script.appendChild( document.createTextNode( data ) );
+
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709).
+ head.insertBefore( script, head.firstChild );
+ head.removeChild( script );
+ }
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+ },
+
+ cache: {},
+
+ data: function( elem, name, data ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ];
+
+ // Compute a unique ID for the element
+ if ( !id )
+ id = elem[ expando ] = ++uuid;
+
+ // Only generate the data cache if we're
+ // trying to access or manipulate it
+ if ( name && !jQuery.cache[ id ] )
+ jQuery.cache[ id ] = {};
+
+ // Prevent overriding the named cache with undefined values
+ if ( data !== undefined )
+ jQuery.cache[ id ][ name ] = data;
+
+ // Return the named cache data, or the ID for the element
+ return name ?
+ jQuery.cache[ id ][ name ] :
+ id;
+ },
+
+ removeData: function( elem, name ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ];
+
+ // If we want to remove a specific section of the element's data
+ if ( name ) {
+ if ( jQuery.cache[ id ] ) {
+ // Remove the section of cache data
+ delete jQuery.cache[ id ][ name ];
+
+ // If we've removed all the data, remove the element's cache
+ name = "";
+
+ for ( name in jQuery.cache[ id ] )
+ break;
+
+ if ( !name )
+ jQuery.removeData( elem );
+ }
+
+ // Otherwise, we want to remove all of the element's data
+ } else {
+ // Clean up the element expando
+ try {
+ delete elem[ expando ];
+ } catch(e){
+ // IE has trouble directly removing the expando
+ // but it's ok with using removeAttribute
+ if ( elem.removeAttribute )
+ elem.removeAttribute( expando );
+ }
+
+ // Completely remove the data cache
+ delete jQuery.cache[ id ];
+ }
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0, length = object.length;
+
+ if ( args ) {
+ if ( length == undefined ) {
+ for ( name in object )
+ if ( callback.apply( object[ name ], args ) === false )
+ break;
+ } else
+ for ( ; i < length; )
+ if ( callback.apply( object[ i++ ], args ) === false )
+ break;
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( length == undefined ) {
+ for ( name in object )
+ if ( callback.call( object[ name ], name, object[ name ] ) === false )
+ break;
+ } else
+ for ( var value = object[0];
+ i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
+ }
+
+ return object;
+ },
+
+ prop: function( elem, value, type, i, name ) {
+ // Handle executable functions
+ if ( jQuery.isFunction( value ) )
+ value = value.call( elem, i );
+
+ // Handle passing in a number to a CSS property
+ return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ?
+ value + "px" :
+ value;
+ },
+
+ className: {
+ // internal only, use addClass("class")
+ add: function( elem, classNames ) {
+ jQuery.each((classNames || "").split(/\s+/), function(i, className){
+ if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
+ elem.className += (elem.className ? " " : "") + className;
+ });
+ },
+
+ // internal only, use removeClass("class")
+ remove: function( elem, classNames ) {
+ if (elem.nodeType == 1)
+ elem.className = classNames != undefined ?
+ jQuery.grep(elem.className.split(/\s+/), function(className){
+ return !jQuery.className.has( classNames, className );
+ }).join(" ") :
+ "";
+ },
+
+ // internal only, use hasClass("class")
+ has: function( elem, className ) {
+ return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+ }
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {};
+ // Remember the old values, and insert the new ones
+ for ( var name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ callback.call( elem );
+
+ // Revert the old values
+ for ( var name in options )
+ elem.style[ name ] = old[ name ];
+ },
+
+ css: function( elem, name, force ) {
+ if ( name == "width" || name == "height" ) {
+ var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+
+ function getWH() {
+ val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+ var padding = 0, border = 0;
+ jQuery.each( which, function() {
+ padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+ border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+ });
+ val -= Math.round(padding + border);
+ }
+
+ if ( jQuery(elem).is(":visible") )
+ getWH();
+ else
+ jQuery.swap( elem, props, getWH );
+
+ return Math.max(0, val);
+ }
+
+ return jQuery.curCSS( elem, name, force );
+ },
+
+ curCSS: function( elem, name, force ) {
+ var ret, style = elem.style;
+
+ // A helper method for determining if an element's values are broken
+ function color( elem ) {
+ if ( !jQuery.browser.safari )
+ return false;
+
+ // defaultView is cached
+ var ret = defaultView.getComputedStyle( elem, null );
+ return !ret || ret.getPropertyValue("color") == "";
+ }
+
+ // We need to handle opacity special in IE
+ if ( name == "opacity" && jQuery.browser.msie ) {
+ ret = jQuery.attr( style, "opacity" );
+
+ return ret == "" ?
+ "1" :
+ ret;
+ }
+ // Opera sometimes will give the wrong display answer, this fixes it, see #2037
+ if ( jQuery.browser.opera && name == "display" ) {
+ var save = style.outline;
+ style.outline = "0 solid black";
+ style.outline = save;
+ }
+
+ // Make sure we're using the right name for getting the float value
+ if ( name.match( /float/i ) )
+ name = styleFloat;
+
+ if ( !force && style && style[ name ] )
+ ret = style[ name ];
+
+ else if ( defaultView.getComputedStyle ) {
+
+ // Only "float" is needed here
+ if ( name.match( /float/i ) )
+ name = "float";
+
+ name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+ var computedStyle = defaultView.getComputedStyle( elem, null );
+
+ if ( computedStyle && !color( elem ) )
+ ret = computedStyle.getPropertyValue( name );
+
+ // If the element isn't reporting its values properly in Safari
+ // then some display: none elements are involved
+ else {
+ var swap = [], stack = [], a = elem, i = 0;
+
+ // Locate all of the parent display: none elements
+ for ( ; a && color(a); a = a.parentNode )
+ stack.unshift(a);
+
+ // Go through and make them visible, but in reverse
+ // (It would be better if we knew the exact display type that they had)
+ for ( ; i < stack.length; i++ )
+ if ( color( stack[ i ] ) ) {
+ swap[ i ] = stack[ i ].style.display;
+ stack[ i ].style.display = "block";
+ }
+
+ // Since we flip the display style, we have to handle that
+ // one special, otherwise get the value
+ ret = name == "display" && swap[ stack.length - 1 ] != null ?
+ "none" :
+ ( computedStyle && computedStyle.getPropertyValue( name ) ) || "";
+
+ // Finally, revert the display styles back
+ for ( i = 0; i < swap.length; i++ )
+ if ( swap[ i ] != null )
+ stack[ i ].style.display = swap[ i ];
+ }
+
+ // We should always get a number back from opacity
+ if ( name == "opacity" && ret == "" )
+ ret = "1";
+
+ } else if ( elem.currentStyle ) {
+ var camelCase = name.replace(/\-(\w)/g, function(all, letter){
+ return letter.toUpperCase();
+ });
+
+ ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+ // Remember the original values
+ var left = style.left, rsLeft = elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ style.left = ret || 0;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret;
+ },
+
+ clean: function( elems, context ) {
+ var ret = [];
+ context = context || document;
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if (typeof context.createElement == 'undefined')
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+ jQuery.each(elems, function(i, elem){
+ if ( !elem )
+ return;
+
+ if ( elem.constructor == Number )
+ elem += '';
+
+ // Convert html string into DOM nodes
+ if ( typeof elem == "string" ) {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
+ return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
+ all :
+ front + ">" + tag + ">";
+ });
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div");
+
+ var wrap =
+ // option or optgroup
+ !tags.indexOf("", "" ] ||
+
+ !tags.indexOf("", "" ] ||
+
+ tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+ [ 1, "
");});
+
+ // atx-style headers:
+ // # Header 1
+ // ## Header 2
+ // ## Header 2 with closing hashes ##
+ // ...
+ // ###### Header 6
+ //
+
+ /*
+ text = text.replace(/
+ ^(\#{1,6}) // $1 = string of #'s
+ [ \t]*
+ (.+?) // $2 = Header text
+ [ \t]*
+ \#* // optional closing #'s (not counted)
+ \n+
+ /gm, function() {...});
+ */
+
+ text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
+ function(wholeMatch,m1,m2) {
+ var h_level = m1.length;
+ return hashBlock("" + _RunSpanGamut(m2) + "");
+ });
+
+ return text;
+}
+
+// This declaration keeps Dojo compressor from outputting garbage:
+var _ProcessListItems;
+
+var _DoLists = function(text) {
+//
+// Form HTML ordered (numbered) and unordered (bulleted) lists.
+//
+
+ // attacklab: add sentinel to hack around khtml/safari bug:
+ // http://bugs.webkit.org/show_bug.cgi?id=11231
+ text += "~0";
+
+ // Re-usable pattern to match any entirel ul or ol list:
+
+ /*
+ var whole_list = /
+ ( // $1 = whole list
+ ( // $2
+ [ ]{0,3} // attacklab: g_tab_width - 1
+ ([*+-]|\d+[.]) // $3 = first list item marker
+ [ \t]+
+ )
+ [^\r]+?
+ ( // $4
+ ~0 // sentinel for workaround; should be $
+ |
+ \n{2,}
+ (?=\S)
+ (?! // Negative lookahead for another list item marker
+ [ \t]*
+ (?:[*+-]|\d+[.])[ \t]+
+ )
+ )
+ )/g
+ */
+ var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
+
+ if (g_list_level) {
+ text = text.replace(whole_list,function(wholeMatch,m1,m2) {
+ var list = m1;
+ var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";
+
+ // Turn double returns into triple returns, so that we can make a
+ // paragraph for the last item in a list, if necessary:
+ list = list.replace(/\n{2,}/g,"\n\n\n");;
+ var result = _ProcessListItems(list);
+
+ // Trim any trailing whitespace, to put the closing `$list_type>`
+ // up on the preceding line, to get it past the current stupid
+ // HTML block parser. This is a hack to work around the terrible
+ // hack that is the HTML block parser.
+ result = result.replace(/\s+$/,"");
+ result = "<"+list_type+">" + result + ""+list_type+">\n";
+ return result;
+ });
+ } else {
+ whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
+ text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {
+ var runup = m1;
+ var list = m2;
+
+ var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";
+ // Turn double returns into triple returns, so that we can make a
+ // paragraph for the last item in a list, if necessary:
+ var list = list.replace(/\n{2,}/g,"\n\n\n");;
+ var result = _ProcessListItems(list);
+ result = runup + "<"+list_type+">\n" + result + ""+list_type+">\n";
+ return result;
+ });
+ }
+
+ // attacklab: strip sentinel
+ text = text.replace(/~0/,"");
+
+ return text;
+}
+
+_ProcessListItems = function(list_str) {
+//
+// Process the contents of a single ordered or unordered list, splitting it
+// into individual list items.
+//
+ // The $g_list_level global keeps track of when we're inside a list.
+ // Each time we enter a list, we increment it; when we leave a list,
+ // we decrement. If it's zero, we're not in a list anymore.
+ //
+ // We do this because when we're not inside a list, we want to treat
+ // something like this:
+ //
+ // I recommend upgrading to version
+ // 8. Oops, now this line is treated
+ // as a sub-list.
+ //
+ // As a single paragraph, despite the fact that the second line starts
+ // with a digit-period-space sequence.
+ //
+ // Whereas when we're inside a list (or sub-list), that line will be
+ // treated as the start of a sub-list. What a kludge, huh? This is
+ // an aspect of Markdown's syntax that's hard to parse perfectly
+ // without resorting to mind-reading. Perhaps the solution is to
+ // change the syntax rules such that sub-lists must start with a
+ // starting cardinal number; e.g. "1." or "a.".
+
+ g_list_level++;
+
+ // trim trailing blank lines:
+ list_str = list_str.replace(/\n{2,}$/,"\n");
+
+ // attacklab: add sentinel to emulate \z
+ list_str += "~0";
+
+ /*
+ list_str = list_str.replace(/
+ (\n)? // leading line = $1
+ (^[ \t]*) // leading whitespace = $2
+ ([*+-]|\d+[.]) [ \t]+ // list marker = $3
+ ([^\r]+? // list item text = $4
+ (\n{1,2}))
+ (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
+ /gm, function(){...});
+ */
+ list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
+ function(wholeMatch,m1,m2,m3,m4){
+ var item = m4;
+ var leading_line = m1;
+ var leading_space = m2;
+
+ if (leading_line || (item.search(/\n{2,}/)>-1)) {
+ item = _RunBlockGamut(_Outdent(item));
+ }
+ else {
+ // Recursion for sub-lists:
+ item = _DoLists(_Outdent(item));
+ item = item.replace(/\n$/,""); // chomp(item)
+ item = _RunSpanGamut(item);
+ }
+
+ return "
` blocks.
+//
+
+ /*
+ text = text.replace(text,
+ /(?:\n\n|^)
+ ( // $1 = the code block -- one or more lines, starting with a space/tab
+ (?:
+ (?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
+ .*\n+
+ )+
+ )
+ (\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
+ /g,function(){...});
+ */
+
+ // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
+ text += "~0";
+
+ text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
+ function(wholeMatch,m1,m2) {
+ var codeblock = m1;
+ var nextChar = m2;
+
+ codeblock = _EncodeCode( _Outdent(codeblock));
+ codeblock = _Detab(codeblock);
+ codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
+ codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
+
+ codeblock = "
" + codeblock + "\n
";
+
+ return hashBlock(codeblock) + nextChar;
+ }
+ );
+
+ // attacklab: strip sentinel
+ text = text.replace(/~0/,"");
+
+ return text;
+}
+
+var hashBlock = function(text) {
+ text = text.replace(/(^\n+|\n+$)/g,"");
+ return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";
+}
+
+
+var _DoCodeSpans = function(text) {
+//
+// * Backtick quotes are used for spans.
+//
+// * You can use multiple backticks as the delimiters if you want to
+// include literal backticks in the code span. So, this input:
+//
+// Just type ``foo `bar` baz`` at the prompt.
+//
+// Will translate to:
+//
+//
Just type foo `bar` baz at the prompt.
+//
+// There's no arbitrary limit to the number of backticks you
+// can use as delimters. If you need three consecutive backticks
+// in your code, use four for delimiters, etc.
+//
+// * You can use spaces to get literal backticks at the edges:
+//
+// ... type `` `bar` `` ...
+//
+// Turns to:
+//
+// ... type `bar` ...
+//
+
+ /*
+ text = text.replace(/
+ (^|[^\\]) // Character before opening ` can't be a backslash
+ (`+) // $2 = Opening run of `
+ ( // $3 = The code block
+ [^\r]*?
+ [^`] // attacklab: work around lack of lookbehind
+ )
+ \2 // Matching closer
+ (?!`)
+ /gm, function(){...});
+ */
+
+ text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
+ function(wholeMatch,m1,m2,m3,m4) {
+ var c = m3;
+ c = c.replace(/^([ \t]*)/g,""); // leading whitespace
+ c = c.replace(/[ \t]*$/g,""); // trailing whitespace
+ c = _EncodeCode(c);
+ return m1+""+c+"";
+ });
+
+ return text;
+}
+
+
+var _EncodeCode = function(text) {
+//
+// Encode/escape certain characters inside Markdown code runs.
+// The point is that in code, these characters are literals,
+// and lose their special Markdown meanings.
+//
+ // Encode all ampersands; HTML entities are not
+ // entities within a Markdown code span.
+ text = text.replace(/&/g,"&");
+
+ // Do the angle bracket song and dance:
+ text = text.replace(//g,">");
+
+ // Now, escape characters that are magic in Markdown:
+ text = escapeCharacters(text,"\*_{}[]\\",false);
+
+// jj the line above breaks this:
+//---
+
+//* Item
+
+// 1. Subitem
+
+// special char: *
+//---
+
+ return text;
+}
+
+
+var _DoItalicsAndBold = function(text) {
+
+ // must go first:
+ text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\1/g,
+ "$2");
+
+ text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
+ "$2");
+
+ return text;
+}
+
+
+var _DoBlockQuotes = function(text) {
+
+ /*
+ text = text.replace(/
+ ( // Wrap whole match in $1
+ (
+ ^[ \t]*>[ \t]? // '>' at the start of a line
+ .+\n // rest of the first line
+ (.+\n)* // subsequent consecutive lines
+ \n* // blanks
+ )+
+ )
+ /gm, function(){...});
+ */
+
+ text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
+ function(wholeMatch,m1) {
+ var bq = m1;
+
+ // attacklab: hack around Konqueror 3.5.4 bug:
+ // "----------bug".replace(/^-/g,"") == "bug"
+
+ bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting
+
+ // attacklab: clean up hack
+ bq = bq.replace(/~0/g,"");
+
+ bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines
+ bq = _RunBlockGamut(bq); // recurse
+
+ bq = bq.replace(/(^|\n)/g,"$1 ");
+ // These leading spaces screw with
content, so we need to fix that:
+ bq = bq.replace(
+ /(\s*
[^\r]+?<\/pre>)/gm,
+ function(wholeMatch,m1) {
+ var pre = m1;
+ // attacklab: hack around Konqueror 3.5.4 bug:
+ pre = pre.replace(/^ /mg,"~0");
+ pre = pre.replace(/~0/g,"");
+ return pre;
+ });
+
+ return hashBlock("
\n" + bq + "\n
");
+ });
+ return text;
+}
+
+
+var _FormParagraphs = function(text) {
+//
+// Params:
+// $text - string to process with html
tags
+//
+
+ // Strip leading and trailing lines:
+ text = text.replace(/^\n+/g,"");
+ text = text.replace(/\n+$/g,"");
+
+ var grafs = text.split(/\n{2,}/g);
+ var grafsOut = new Array();
+
+ //
+ // Wrap
tags.
+ //
+ var end = grafs.length;
+ for (var i=0; i= 0) {
+ grafsOut.push(str);
+ }
+ else if (str.search(/\S/) >= 0) {
+ str = _RunSpanGamut(str);
+ str = str.replace(/^([ \t]*)/g,"
");
+ str += "
"
+ grafsOut.push(str);
+ }
+
+ }
+
+ //
+ // Unhashify HTML blocks
+ //
+ end = grafsOut.length;
+ for (var i=0; i= 0) {
+ var blockText = g_html_blocks[RegExp.$1];
+ blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs
+ grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);
+ }
+ }
+
+ return grafsOut.join("\n\n");
+}
+
+
+var _EncodeAmpsAndAngles = function(text) {
+// Smart processing for ampersands and angle brackets that need to be encoded.
+
+ // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
+ // http://bumppo.net/projects/amputator/
+ text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&");
+
+ // Encode naked <'s
+ text = text.replace(/<(?![a-z\/?\$!])/gi,"<");
+
+ return text;
+}
+
+
+var _EncodeBackslashEscapes = function(text) {
+//
+// Parameter: String.
+// Returns: The string, with after processing the following backslash
+// escape sequences.
+//
+
+ // attacklab: The polite way to do this is with the new
+ // escapeCharacters() function:
+ //
+ // text = escapeCharacters(text,"\\",true);
+ // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
+ //
+ // ...but we're sidestepping its use of the (slow) RegExp constructor
+ // as an optimization for Firefox. This function gets called a LOT.
+
+ text = text.replace(/\\(\\)/g,escapeCharacters_callback);
+ text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);
+ return text;
+}
+
+
+var _DoAutoLinks = function(text) {
+
+ text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"$1");
+
+ // Email addresses:
+
+ /*
+ text = text.replace(/
+ <
+ (?:mailto:)?
+ (
+ [-.\w]+
+ \@
+ [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
+ )
+ >
+ /gi, _DoAutoLinks_callback());
+ */
+ text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
+ function(wholeMatch,m1) {
+ return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
+ }
+ );
+
+ return text;
+}
+
+
+var _EncodeEmailAddress = function(addr) {
+//
+// Input: an email address, e.g. "foo@example.com"
+//
+// Output: the email address as a mailto link, with each character
+// of the address encoded as either a decimal or hex entity, in
+// the hopes of foiling most address harvesting spam bots. E.g.:
+//
+// foo
+// @example.com
+//
+// Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
+// mailing list:
+//
+
+ // attacklab: why can't javascript speak hex?
+ function char2hex(ch) {
+ var hexDigits = '0123456789ABCDEF';
+ var dec = ch.charCodeAt(0);
+ return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15));
+ }
+
+ var encode = [
+ function(ch){return ""+ch.charCodeAt(0)+";";},
+ function(ch){return ""+char2hex(ch)+";";},
+ function(ch){return ch;}
+ ];
+
+ addr = "mailto:" + addr;
+
+ addr = addr.replace(/./g, function(ch) {
+ if (ch == "@") {
+ // this *must* be encoded. I insist.
+ ch = encode[Math.floor(Math.random()*2)](ch);
+ } else if (ch !=":") {
+ // leave ':' alone (to spot mailto: later)
+ var r = Math.random();
+ // roughly 10% raw, 45% hex, 45% dec
+ ch = (
+ r > .9 ? encode[2](ch) :
+ r > .45 ? encode[1](ch) :
+ encode[0](ch)
+ );
+ }
+ return ch;
+ });
+
+ addr = "" + addr + "";
+ addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part
+
+ return addr;
+}
+
+
+var _UnescapeSpecialChars = function(text) {
+//
+// Swap back in all the special characters we've hidden.
+//
+ text = text.replace(/~E(\d+)E/g,
+ function(wholeMatch,m1) {
+ var charCodeToReplace = parseInt(m1);
+ return String.fromCharCode(charCodeToReplace);
+ }
+ );
+ return text;
+}
+
+
+var _Outdent = function(text) {
+//
+// Remove one level of line-leading tabs or spaces
+//
+
+ // attacklab: hack around Konqueror 3.5.4 bug:
+ // "----------bug".replace(/^-/g,"") == "bug"
+
+ text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width
+
+ // attacklab: clean up hack
+ text = text.replace(/~0/g,"")
+
+ return text;
+}
+
+var _Detab = function(text) {
+// attacklab: Detab's completely rewritten for speed.
+// In perl we could fix it by anchoring the regexp with \G.
+// In javascript we're less fortunate.
+
+ // expand first n-1 tabs
+ text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width
+
+ // replace the nth with two sentinels
+ text = text.replace(/\t/g,"~A~B");
+
+ // use the sentinel to anchor our regex so it doesn't explode
+ text = text.replace(/~B(.+?)~A/g,
+ function(wholeMatch,m1,m2) {
+ var leadingText = m1;
+ var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width
+
+ // there *must* be a better way to do this:
+ for (var i=0; i";var D="