Permalink
Browse files

adding all files again

  • Loading branch information...
1 parent 5b45e52 commit 35b666224a05fbd249b1c51a0a48fb95b246fe9a @evgenyfadeev evgenyfadeev committed Jul 10, 2009
Showing with 18,342 additions and 0 deletions.
  1. +12 −0 LICENSE
  2. 0 __init__.py
  3. +8 −0 cnprog.wsgi
  4. +40 −0 django_authopenid/__init__.py
  5. +9 −0 django_authopenid/admin.py
  6. +435 −0 django_authopenid/forms.py
  7. +24 −0 django_authopenid/middleware.py
  8. +160 −0 django_authopenid/mimeparse.py
  9. +71 −0 django_authopenid/models.py
  10. +27 −0 django_authopenid/urls.py
  11. +145 −0 django_authopenid/util.py
  12. +787 −0 django_authopenid/views.py
  13. 0 forum/__init__.py
  14. +71 −0 forum/admin.py
  15. +443 −0 forum/auth.py
  16. +87 −0 forum/const.py
  17. +66 −0 forum/diff.py
  18. +43 −0 forum/feed.py
  19. +193 −0 forum/forms.py
  20. 0 forum/management/__init__.py
  21. 0 forum/management/commands/__init__.py
  22. +35 −0 forum/management/commands/base_command.py
  23. +58 −0 forum/management/commands/clean_award_badges.py
  24. +347 −0 forum/management/commands/multi_award_badges.py
  25. +327 −0 forum/management/commands/once_award_badges.py
  26. +7 −0 forum/management/commands/sample_command.py
  27. +208 −0 forum/managers.py
  28. +653 −0 forum/models.py
  29. 0 forum/templatetags/__init__.py
  30. +83 −0 forum/templatetags/extra_filters.py
  31. +240 −0 forum/templatetags/extra_tags.py
  32. +74 −0 forum/user.py
  33. +1,962 −0 forum/views.py
  34. +7 −0 junk
  35. +557 −0 lanai.psproj
  36. +2,540 −0 locale/en/LC_MESSAGES/django.po
  37. BIN locale/zh-cn/LC_MESSAGES/django.mo
  38. +1,430 −0 locale/zh-cn/LC_MESSAGES/django.po
  39. 0 log/cnprog.log
  40. +11 −0 manage.py
  41. +146 −0 settings.py
  42. +1,498 −0 sql_scripts/cnprog.xml
  43. +811 −0 sql_scripts/cnprog_new_install.sql
  44. +456 −0 sql_scripts/cnprog_new_install_2009_02_28.sql
  45. +891 −0 sql_scripts/cnprog_new_install_2009_03_31.sql
  46. +24 −0 sql_scripts/cnprog_new_install_2009_04_07.sql
  47. +904 −0 sql_scripts/cnprog_new_install_2009_04_09.sql
  48. +62 −0 sql_scripts/update_2009_01_13_001.sql
  49. +1 −0 sql_scripts/update_2009_01_13_002.sql
  50. +62 −0 sql_scripts/update_2009_01_18_001.sql
  51. +2 −0 sql_scripts/update_2009_01_24.sql
  52. +2 −0 sql_scripts/update_2009_01_25_001.sql
  53. +19 −0 sql_scripts/update_2009_02_26_001.sql
  54. +3 −0 sql_scripts/update_2009_04_10_001.sql
  55. +5 −0 sql_scripts/update_2009_12_24_001.sql
  56. +3 −0 sql_scripts/update_2009_12_27_001.sql
  57. +1 −0 sql_scripts/update_2009_12_27_002.sql
  58. +49 −0 templates/404.html
  59. +35 −0 templates/500.html
  60. +21 −0 templates/about.html
  61. +30 −0 templates/allfiles
  62. +88 −0 templates/answer_edit.html
  63. +54 −0 templates/answer_edit_tips.html
  64. +140 −0 templates/ask.html
  65. +35 −0 templates/authopenid/changeemail.html
  66. +33 −0 templates/authopenid/changeopenid.html
  67. +33 −0 templates/authopenid/changepw.html
  68. +67 −0 templates/authopenid/complete.html
  69. +12 −0 templates/authopenid/confirm_email.txt
  70. +38 −0 templates/authopenid/delete.html
  71. +13 −0 templates/authopenid/failure.html
  72. +8 −0 templates/authopenid/htmlfiles
  73. +34 −0 templates/authopenid/sendpw.html
  74. +14 −0 templates/authopenid/sendpw_email.txt
  75. +41 −0 templates/authopenid/settings.html
  76. +95 −0 templates/authopenid/signin.html
  77. +52 −0 templates/authopenid/signup.html
  78. +14 −0 templates/authopenid/yadis.xrdf
  79. +37 −0 templates/badge.html
  80. +78 −0 templates/badges.html
  81. +91 −0 templates/base.html
  82. +86 −0 templates/base_content.html
  83. +152 −0 templates/book.html
  84. +36 −0 templates/close.html
  85. BIN templates/content/images/box-arrow.gif
  86. BIN templates/content/images/bullet_green.gif
  87. BIN templates/content/images/cc-88x31.png
  88. BIN templates/content/images/cc-wiki.png
  89. BIN templates/content/images/close-small-hover.png
  90. BIN templates/content/images/close-small.png
  91. BIN templates/content/images/cnprog_logo_200_56.gif
  92. BIN templates/content/images/dash.gif
  93. BIN templates/content/images/djangomade124x25_grey.gif
  94. BIN templates/content/images/dot-g.gif
  95. BIN templates/content/images/dot-list.gif
  96. BIN templates/content/images/edit.png
  97. BIN templates/content/images/expander-arrow-hide.gif
  98. BIN templates/content/images/expander-arrow-show.gif
  99. BIN templates/content/images/favicon.gif
  100. BIN templates/content/images/feed-icon-small.png
  101. BIN templates/content/images/grippie.png
  102. BIN templates/content/images/indicator.gif
  103. BIN templates/content/images/logo.png
  104. BIN templates/content/images/logo1.png
  105. BIN templates/content/images/logo2.png
  106. BIN templates/content/images/medala.gif
  107. BIN templates/content/images/medala_on.gif
  108. BIN templates/content/images/new.gif
  109. BIN templates/content/images/nophoto.png
  110. BIN templates/content/images/openid.gif
  111. BIN templates/content/images/openid/aol.gif
  112. BIN templates/content/images/openid/blogger.ico
  113. BIN templates/content/images/openid/claimid.ico
  114. BIN templates/content/images/openid/facebook.gif
  115. BIN templates/content/images/openid/flickr.ico
  116. BIN templates/content/images/openid/google.gif
  117. BIN templates/content/images/openid/livejournal.ico
  118. BIN templates/content/images/openid/myopenid.ico
  119. BIN templates/content/images/openid/openid-inputicon.gif
  120. BIN templates/content/images/openid/openid.gif
  121. BIN templates/content/images/openid/technorati.ico
  122. BIN templates/content/images/openid/verisign.ico
  123. BIN templates/content/images/openid/vidoop.ico
  124. BIN templates/content/images/openid/wordpress.ico
  125. BIN templates/content/images/openid/yahoo.gif
  126. BIN templates/content/images/quest-bg.gif
  127. BIN templates/content/images/vote-accepted-on.png
  128. BIN templates/content/images/vote-accepted.png
  129. BIN templates/content/images/vote-arrow-down-on.png
  130. BIN templates/content/images/vote-arrow-down.png
  131. BIN templates/content/images/vote-arrow-up-on.png
  132. BIN templates/content/images/vote-arrow-up.png
  133. BIN templates/content/images/vote-favorite-off.png
  134. BIN templates/content/images/vote-favorite-on.png
  135. +68 −0 templates/content/js/com.cnprog.editor.js
  136. +90 −0 templates/content/js/com.cnprog.i18n.js
  137. +616 −0 templates/content/js/com.cnprog.post.js
  138. +122 −0 templates/content/js/com.cnprog.utils.js
  139. +6 −0 templates/content/js/compress.bat
  140. +1 −0 templates/content/js/excanvas.pack.js
  141. +3 −0 templates/content/js/flot-build.bat
Sorry, we could not display the entire diff because it was too big.
View
12 LICENSE
@@ -0,0 +1,12 @@
+��Ȩ����(c) 2008 CNProg.com
+
+���2.0�汾Apache���֤("���֤")��Ȩ��
+��ݱ����֤���û����Բ�ʹ�ô��ļ���
+�û��ɴ�������ַ������֤������
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+��������÷�����Ҫ������ͬ�⣬
+������֤�ַ�������ǻ���"��ԭ��"���ṩ��
+���κ���ʾ�Ļ�ʾ�ı�֤�������
+��������֤����£��ض����ԵĹ�ϽȨ�޺����ơ�
View
0 __init__.py
No changes.
View
8 cnprog.wsgi
@@ -0,0 +1,8 @@
+import os
+import sys
+
+sys.path.append('/var/www/vhosts')
+os.environ['DJANGO_SETTINGS_MODULE'] = 'cnprog.settings'
+
+import django.core.handlers.wsgi
+application = django.core.handlers.wsgi.WSGIHandler()
View
40 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 <ORGANIZATION> nor the names
+# * of its contributors may be used to endorse or promote products
+# * derived from this software without specific prior written
+# * permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+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"
View
9 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)
View
435 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 <ORGANIZATION> nor the names
+# * of its contributors may be used to endorse or promote products
+# * derived from this software without specific prior written
+# * permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+from django 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(_('invalid user name'))
+ if self.cleaned_data['username'] in RESERVED_NAMES:
+ raise forms.ValidationError(_('sorry, this name can not be used, please try another'))
+ if len(self.cleaned_data['username']) < 3:
+ raise forms.ValidationError(_('username too short'))
+ 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(_('this name is already in use - please try anoter'))
+ raise forms.ValidationError(_('this name is already in use - please try anoter'))
+
+ 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=_('choose a username'))
+ email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
+ maxlength=200)), label=_('your email address'))
+ password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
+ label=_('choose password'))
+ password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
+ label=_('retype password'))
+
+ 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']
View
24 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
View
160 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()
View
71 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
View
27 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'),
+)
View
145 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 '<OpenID: %s>' % 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<int(time.time()) - nonce.SKEW).delete()
+
+ def cleanupAssociations(self):
+ Association.objects.extra(where=['issued + lifetimeint<(%s)' % time.time()]).delete()
+
+ def getAuthKey(self):
+ # Use first AUTH_KEY_LEN characters of md5 hash of SECRET_KEY
+ return md5.new(settings.SECRET_KEY).hexdigest()[:self.AUTH_KEY_LEN]
+
+ def isDumb(self):
+ return False
+
+def from_openid_response(openid_response):
+ """ return openid object from response """
+ issued = int(time.time())
+ sreg_resp = sreg.SRegResponse.fromSuccessResponse(openid_response) \
+ or []
+
+ return OpenID(
+ openid_response.identity_url, issued, openid_response.signed_fields,
+ dict(sreg_resp)
+ )
View
787 django_authopenid/views.py
@@ -0,0 +1,787 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2007, 2008, Benoît Chesneau
+# Copyright (c) 2007 Simon Willison, original work on django-openid
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# * notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# * notice, this list of conditions and the following disclaimer in the
+# * documentation and/or other materials provided with the
+# * distribution. Neither the name of the <ORGANIZATION> nor the names
+# * of its contributors may be used to endorse or promote products
+# * derived from this software without specific prior written
+# * permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from django.http import HttpResponseRedirect, get_host
+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)
View
0 forum/__init__.py
No changes.
View
71 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)
View
443 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