summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-01-24 19:53:24 -0500
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-01-24 20:03:45 -0500
commitad2e22b999b3b795f60e0f95abcaf3b339567294 (patch)
treeebea9e231463d878ff869e4a74ecd4620e743a95
parentc4da893b2e28dbd2a04f8c6f61c52936119b1148 (diff)
downloadaskbot-ad2e22b999b3b795f60e0f95abcaf3b339567294.tar.gz
askbot-ad2e22b999b3b795f60e0f95abcaf3b339567294.tar.bz2
askbot-ad2e22b999b3b795f60e0f95abcaf3b339567294.zip
recaptcha for conventional registration\n\
simpler email subscription form at registration\n\ fixed urls in rss feed\n\ added experimental remote password login api (cleartext password for remote site entered locally)\n\ included example for Mediawiki Authentication plugin\n\ very simple message to everyone management command
-rw-r--r--INSTALL9
-rw-r--r--cnprog.wsgi5
-rw-r--r--cron/send_email_alerts6
-rw-r--r--django_authopenid/forms.py185
-rwxr-xr-xdjango_authopenid/urls.py33
-rw-r--r--django_authopenid/util.py16
-rwxr-xr-xdjango_authopenid/views.py103
-rw-r--r--dos2unix.sh2
-rw-r--r--drop-auth.sql8
-rw-r--r--forum/feed.py6
-rw-r--r--forum/forms.py48
-rw-r--r--forum/management/commands/message_to_everyone.py12
-rw-r--r--forum/management/commands/multi_award_badges.py2
-rw-r--r--forum/management/commands/send_email_alerts.py7
-rw-r--r--forum/management/commands/subscribe_everyone.py2
-rw-r--r--forum/models.py4
-rw-r--r--forum/sitemap.py3
-rw-r--r--forum/urls.py2
-rw-r--r--forum/views.py17
-rw-r--r--locale/en/LC_MESSAGES/django.mobin26686 -> 26619 bytes
-rw-r--r--locale/en/LC_MESSAGES/django.po2
-rw-r--r--mediawiki/PHPSerialize.py149
-rw-r--r--mediawiki/PHPUnserialize.py187
-rw-r--r--mediawiki/README106
-rw-r--r--mediawiki/UserRegister.alias.php7
-rw-r--r--mediawiki/UserRegister.body.php14
-rw-r--r--mediawiki/UserRegister.i18n.php6
-rw-r--r--mediawiki/UserRegister.php24
-rw-r--r--mediawiki/WsgiInjectableSpecialPage.php80
-rw-r--r--mediawiki/__init__.py0
-rw-r--r--mediawiki/api.py (renamed from django_authopenid/external_login.py)41
-rw-r--r--mediawiki/auth.py59
-rw-r--r--mediawiki/forms.py164
-rw-r--r--mediawiki/junk.py2
-rw-r--r--mediawiki/middleware.py57
-rw-r--r--mediawiki/models.py312
-rw-r--r--mediawiki/templatetags/__init__.py0
-rw-r--r--mediawiki/templatetags/mediawikitags.py62
-rw-r--r--mediawiki/views.py192
-rw-r--r--middleware/anon_user.py4
-rw-r--r--middleware/cancel.py2
-rwxr-xr-xsettings.py42
-rwxr-xr-xsettings_local.py.dist12
-rw-r--r--sql_scripts/100108_upgrade_ef.sql4
-rw-r--r--sql_scripts/badges.sql37
-rw-r--r--templates/about.html23
-rw-r--r--templates/authopenid/complete.html14
-rw-r--r--templates/authopenid/external_legacy_login_info.html2
-rw-r--r--templates/authopenid/signup.html14
-rw-r--r--templates/badge.html2
-rw-r--r--templates/badges.html4
-rwxr-xr-xtemplates/base.html4
-rw-r--r--templates/base_content.html6
-rw-r--r--templates/content/images/logo.pngbin1902 -> 0 bytes
-rw-r--r--templates/content/jquery-openid/images/local-login.pngbin2522 -> 0 bytes
-rw-r--r--templates/content/jquery-openid/jquery.openid.js2
-rw-r--r--templates/content/js/com.cnprog.admin.js2
-rw-r--r--templates/content/js/com.cnprog.editor.js2
-rw-r--r--templates/content/js/com.cnprog.i18n.js2
-rw-r--r--templates/content/js/com.cnprog.post.js63
-rw-r--r--templates/content/js/com.cnprog.tag_selector.js45
-rw-r--r--templates/content/js/com.cnprog.utils.js51
-rw-r--r--templates/content/js/mediawiki-login.js29
-rw-r--r--templates/content/style/mediawiki-login.css63
-rw-r--r--templates/content/style/style.css22
-rw-r--r--templates/footer.html23
-rw-r--r--templates/header.html6
-rw-r--r--templates/mediawiki/mediawiki_signup.html9
-rw-r--r--templates/mediawiki/mediawiki_signup_content.html110
-rw-r--r--templates/mediawiki/thanks_for_joining.html76
-rw-r--r--templates/mediawiki/welcome_email.txt28
-rw-r--r--templates/mediawiki/welcome_professor_email.txt19
-rw-r--r--templates/notarobot.html15
-rw-r--r--templates/tag_selector.html2
-rw-r--r--urls.py2
-rw-r--r--utils/forms.py151
76 files changed, 2431 insertions, 395 deletions
diff --git a/INSTALL b/INSTALL
index 612cd371..72cc76bf 100644
--- a/INSTALL
+++ b/INSTALL
@@ -47,6 +47,15 @@ http://github.com/dcramer/django-sphinx/tree/master/djangosphinx
8. sphinx search engine (optional, works together with djangosphinx)
http://sphinxsearch.com/downloads.html
+9. recaptcha_django
+http://code.google.com/p/recaptcha-django/
+
+10. python recaptcha module
+http://code.google.com/p/recaptcha/
+Notice that you will need to register with recaptcha.net and receive
+recaptcha public and private keys that need to be saved in your
+settings_local.py file
+
NOTES: django_authopenid is included into CNPROG code
and is significantly modified. http://code.google.com/p/django-authopenid/
no need to install this library
diff --git a/cnprog.wsgi b/cnprog.wsgi
index bd3745ee..a1147de0 100644
--- a/cnprog.wsgi
+++ b/cnprog.wsgi
@@ -1,8 +1,7 @@
-#example wsgi setup script
import os
import sys
-sys.path.append('/path/above_forum')
-sys.path.append('/path/above_forum/forum_dir')
+sys.path.append('/path/above/forum')
+sys.path.append('/path/above/forum/forum_dir')
os.environ['DJANGO_SETTINGS_MODULE'] = 'forum_dir.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
diff --git a/cron/send_email_alerts b/cron/send_email_alerts
index e9e433be..6358b599 100644
--- a/cron/send_email_alerts
+++ b/cron/send_email_alerts
@@ -1,4 +1,4 @@
-PYTHONPATH=/dir/just/above_forum
+PYTHONPATH=/path/to/dir/above/forum
export PYTHONPATH
-APP_ROOT=$PYTHONPATH/CNPROG
-/usr/local/bin/python $APP_ROOT/manage.py send_email_alerts >> $APP_ROOT/log/django.lanai.log
+APP_ROOT=$PYTHONPATH/nmr-forum2
+/path/to/python $APP_ROOT/manage.py send_email_alerts
diff --git a/django_authopenid/forms.py b/django_authopenid/forms.py
index 6781401e..2fe6db74 100644
--- a/django_authopenid/forms.py
+++ b/django_authopenid/forms.py
@@ -35,11 +35,12 @@ 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 external_login
import types
import re
from django.utils.safestring import mark_safe
-
+from recaptcha_django import ReCaptchaField
+from utils.forms import NextUrlField, UserNameField, UserEmailField, SetPasswordForm
+EXTERNAL_LOGIN_APP = settings.LOAD_EXTERNAL_LOGIN_APP()
# needed for some linux distributions like debian
try:
@@ -47,7 +48,7 @@ try:
except ImportError:
from yadis import xri
-from django_authopenid.util import clean_next
+from utils.forms import clean_next
from django_authopenid.models import ExternalLoginData
__all__ = ['OpenidSigninForm', 'ClassicLoginForm', 'OpenidVerifyForm',
@@ -55,102 +56,6 @@ __all__ = ['OpenidSigninForm', 'ClassicLoginForm', 'OpenidVerifyForm',
'ChangeEmailForm', 'EmailPasswordForm', 'DeleteForm',
'ChangeOpenidForm']
-class NextUrlField(forms.CharField):
- def __init__(self):
- super(NextUrlField,self).__init__(max_length = 255,widget = forms.HiddenInput(),required = False)
- def clean(self,value):
- return clean_next(value)
-
-attrs_dict = { 'class': 'required login' }
-
-class UserNameField(forms.CharField):
- 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')
- def __init__(self,must_exist=False,skip_clean=False,label=_('choose a username'),**kw):
- self.must_exist = must_exist
- self.skip_clean = skip_clean
- super(UserNameField,self).__init__(max_length=30,
- widget=forms.TextInput(attrs=attrs_dict),
- label=label,
- error_messages={'required':_('user name is required'),
- 'taken':_('sorry, this name is taken, please choose another'),
- 'forbidden':_('sorry, this name is not allowed, please choose another'),
- 'missing':_('sorry, there is no user with this name'),
- 'multiple-taken':_('sorry, we have a serious error - user name is taken by several users'),
- 'invalid':_('user name can only consist of letters, empty space and underscore'),
- },
- **kw
- )
-
- def clean(self,username):
- """ validate username """
- username = super(UserNameField,self).clean(username.strip())
- if self.skip_clean == True:
- return username
- if hasattr(self, 'user_instance'):
- if username == self.user_instance.username:
- return username
- if not username_re.search(username):
- raise forms.ValidationError(self.error_messages['invalid'])
- if username in self.RESERVED_NAMES:
- raise forms.ValidationError(self.error_messages['forbidden'])
- try:
- user = User.objects.get(
- username__exact = username
- )
- if user:
- if self.must_exist:
- return username
- else:
- raise forms.ValidationError(self.error_messages['taken'])
- except User.DoesNotExist:
- if self.must_exist:
- raise forms.ValidationError(self.error_messages['missing'])
- else:
- return username
- except User.MultipleObjectsReturned:
- raise forms.ValidationError(self.error_messages['multiple-taken'])
-
-class UserEmailField(forms.EmailField):
- def __init__(self,skip_clean=False,**kw):
- self.skip_clean = skip_clean
- super(UserEmailField,self).__init__(widget=forms.TextInput(attrs=dict(attrs_dict,
- maxlength=200)), label=mark_safe(_('your email address')),
- error_messages={'required':_('email address is required'),
- 'invalid':_('please enter a valid email address'),
- 'taken':_('this email is already used by someone else, please choose another'),
- },
- **kw
- )
-
- def clean(self,email):
- """ validate if email exist in database
- from legacy register
- return: raise error if it exist """
- email = super(UserEmailField,self).clean(email.strip())
- if self.skip_clean:
- return email
- if settings.EMAIL_UNIQUE == True:
- try:
- user = User.objects.get(email = email)
- raise forms.ValidationError(self.error_messsages['taken'])
- except User.DoesNotExist:
- return email
- except User.MultipleObjectsReturned:
- raise forms.ValidationError(self.error_messages['taken'])
- else:
- return email
-
-def clean_nonempty_field_method(self,field):
- value = None
- if field in self.cleaned_data:
- value = str(self.cleaned_data[field]).strip()
- if value == '':
- value = None
- self.cleaned_data[field] = value
- return value
-
class OpenidSigninForm(forms.Form):
""" signin form """
openid_url = forms.CharField(max_length=255, widget=forms.widgets.TextInput(attrs={'class': 'openid-login-input', 'size':80}))
@@ -171,7 +76,8 @@ class ClassicLoginForm(forms.Form):
next = NextUrlField()
username = UserNameField(required=False,skip_clean=True)
password = forms.CharField(max_length=128,
- widget=forms.widgets.PasswordInput(attrs=attrs_dict), required=False)
+ widget=forms.widgets.PasswordInput(attrs={'class':'required login'}),
+ required=False)
def __init__(self, data=None, files=None, auto_id='id_%s',
prefix=None, initial=None):
@@ -179,13 +85,21 @@ class ClassicLoginForm(forms.Form):
prefix, initial)
self.user_cache = None
- clean_nonempty_field = clean_nonempty_field_method
+ def _clean_nonempty_field(self,field):
+ value = None
+ if field in self.cleaned_data:
+ value = str(self.cleaned_data[field]).strip()
+ if value == '':
+ value = None
+ self.cleaned_data[field] = value
+ return value
+
def clean_username(self):
- return self.clean_nonempty_field('username')
+ return self._clean_nonempty_field('username')
def clean_password(self):
- return self.clean_nonempty_field('password')
+ return self._clean_nonempty_field('password')
def clean(self):
"""
@@ -208,7 +122,7 @@ class ClassicLoginForm(forms.Form):
if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
pw_ok = False
try:
- pw_ok = external_login.check_password(username,password)
+ pw_ok = EXTERNAL_LOGIN_APP.api.check_password(username,password)
except forms.ValidationError, e:
error_list.extend(e.messages)
if pw_ok:
@@ -274,7 +188,7 @@ class OpenidVerifyForm(forms.Form):
next = NextUrlField()
username = UserNameField(must_exist=True)
password = forms.CharField(max_length=128,
- widget=forms.widgets.PasswordInput(attrs=attrs_dict))
+ widget=forms.widgets.PasswordInput(attrs={'class':'required login'}))
def __init__(self, data=None, files=None, auto_id='id_%s',
prefix=None, initial=None):
@@ -302,53 +216,19 @@ class OpenidVerifyForm(forms.Form):
""" get authenticated user """
return self.user_cache
-
-attrs_dict = { 'class': 'required' }
-username_re = re.compile(r'^[\w ]+$')
-
-class ClassicRegisterForm(forms.Form):
+class ClassicRegisterForm(SetPasswordForm):
""" legacy registration form """
next = NextUrlField()
username = UserNameField()
email = UserEmailField()
- password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
- label=_('choose password'),
- error_messages={'required':_('password is required')},
- )
- password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
- label=mark_safe(_('retype password')),
- error_messages={'required':_('please, retype your password'),
- 'nomatch':_('sorry, entered passwords did not match, please try again')},
- required=False
- )
-
- def clean_password2(self):
- """
- Validates that the two password inputs match.
-
- """
- self.cleaned_data['password2'] = self.cleaned_data.get('password2','')
- if self.cleaned_data['password2'] == '':
- del self.cleaned_data['password2']
- raise forms.ValidationError(self.fields['password2'].error_messages['required'])
- if 'password1' in self.cleaned_data \
- and self.cleaned_data['password1'] == \
- self.cleaned_data['password2']:
- return self.cleaned_data['password2']
- else:
- del self.cleaned_data['password2']
- del self.cleaned_data['password1']
- raise forms.ValidationError(self.fields['password2'].error_messages['nomatch'])
-
-class ChangePasswordForm(forms.Form):
+ #fields password1 and password2 are inherited
+ recaptcha = ReCaptchaField()
+
+class ChangePasswordForm(SetPasswordForm):
""" change password form """
- oldpw = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
+ oldpw = forms.CharField(widget=forms.PasswordInput(attrs={'class':'required'}),
label=mark_safe(_('Current password')))
- password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
- label=mark_safe(_('New password')))
- password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
- label=mark_safe(_('Retype new password')))
def __init__(self, data=None, user=None, *args, **kwargs):
if user is None:
@@ -362,17 +242,6 @@ class ChangePasswordForm(forms.Form):
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 """
@@ -416,8 +285,8 @@ class ChangeopenidForm(forms.Form):
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))
+ confirm = forms.CharField(widget=forms.CheckboxInput(attrs={'class':'required'}))
+ password = forms.CharField(widget=forms.PasswordInput(attrs={'class':'required'}))
def __init__(self, data=None, files=None, auto_id='id_%s',
prefix=None, initial=None, user=None):
diff --git a/django_authopenid/urls.py b/django_authopenid/urls.py
index 112cbbe1..e1986d19 100755
--- a/django_authopenid/urls.py
+++ b/django_authopenid/urls.py
@@ -1,7 +1,21 @@
# -*- coding: utf-8 -*-
from django.conf.urls.defaults import patterns, url
from django.utils.translation import ugettext as _
+from django.conf import settings
+#print 'stuff to import %s' % settings.EXTERNAL_LOGIN_APP.__name__ + '.views'
+#try:
+# settings.EXTERNAL_LOGIN_APP = __import__('mediawiki.views')
+#print 'stuff to import %s' % settings.EXTERNAL_LOGIN_APP.__name__ + '.views'
+#try:
+# print 'imported fine'
+# print settings.EXTERNAL_LOGIN_APP.__dict__.keys()
+#except:
+# print 'dammit!'
+#from mediawiki.views import signup_view
+#settings.EXTERNAL_LOGIN_APP.views.signup_view()
+
+#print settings.EXTERNAL_LOGIN_APP.__dict__.keys()
urlpatterns = patterns('django_authopenid.views',
# yadis rdf
url(r'^yadis.xrdf$', 'xrdf', name='yadis_xrdf'),
@@ -12,7 +26,6 @@ urlpatterns = patterns('django_authopenid.views',
url(r'^%s$' % _('signout/'), 'signout', name='user_signout'),
url(r'^%s%s$' % (_('signin/'), _('complete/')), 'complete_signin',
name='user_complete_signin'),
- url('^%s$' % _('external-login/'),'external_legacy_login_info', name='user_external_legacy_login_issues'),
url(r'^%s$' % _('register/'), 'register', name='user_register'),
url(r'^%s$' % _('signup/'), 'signup', name='user_signup'),
#disable current sendpw function
@@ -29,3 +42,21 @@ urlpatterns = patterns('django_authopenid.views',
url(r'^%s$' % _('openid/'), 'changeopenid', name='user_changeopenid'),
url(r'^%s$' % _('delete/'), 'delete', name='user_delete'),
)
+
+#todo move these out of this file completely
+if settings.USE_EXTERNAL_LEGACY_LOGIN:
+ from forum.forms import NotARobotForm
+ EXTERNAL_LOGIN_APP = settings.LOAD_EXTERNAL_LOGIN_APP()
+ urlpatterns += patterns('',
+ url('^%s$' % _('external-login/forgot-password/'),\
+ 'django_authopenid.views.external_legacy_login_info', \
+ name='user_external_legacy_login_issues'),
+ url('^%s$' % _('external-login/signup/'), \
+ EXTERNAL_LOGIN_APP.views.signup,\
+ name='user_external_legacy_login_signup'),
+# url('^%s$' % _('external-login/signup/'), \
+# EXTERNAL_LOGIN_APP.forms.RegisterFormWizard( \
+# [EXTERNAL_LOGIN_APP.forms.RegisterForm, \
+# NotARobotForm]),\
+# name='user_external_legacy_login_signup'),
+ )
diff --git a/django_authopenid/util.py b/django_authopenid/util.py
index edb6808e..165756e0 100644
--- a/django_authopenid/util.py
+++ b/django_authopenid/util.py
@@ -6,7 +6,6 @@ import openid.store
from django.db.models.query import Q
from django.conf import settings
-from django.http import str_to_unicode
from django.core.urlresolvers import reverse
# needed for some linux distributions like debian
@@ -16,25 +15,12 @@ except:
from yadis import xri
import time, base64, hashlib, operator
-import urllib
+from utils.forms import clean_next, get_next_url
from models import Association, Nonce
__all__ = ['OpenID', 'DjangoOpenIDStore', 'from_openid_response', 'clean_next']
-DEFAULT_NEXT = '/' + getattr(settings, 'FORUM_SCRIPT_ALIAS')
-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
-
-def get_next_url(request):
- return clean_next(request.REQUEST.get('next'))
-
class OpenID:
def __init__(self, openid_, issued, attrs=None, sreg_=None):
self.openid = openid_
diff --git a/django_authopenid/views.py b/django_authopenid/views.py
index d087d215..b09dea7c 100755
--- a/django_authopenid/views.py
+++ b/django_authopenid/views.py
@@ -32,7 +32,7 @@
from django.http import HttpResponseRedirect, get_host, Http404, \
HttpResponseServerError
-from django.shortcuts import render_to_response as render
+from django.shortcuts import render_to_response
from django.template import RequestContext, loader, Context
from django.conf import settings
from django.contrib.auth.models import User
@@ -61,23 +61,23 @@ import re
import urllib
-from forum.forms import EditUserEmailFeedsForm
-from django_authopenid.util import OpenID, DjangoOpenIDStore, from_openid_response, get_next_url
+from forum.forms import SimpleEmailSubscribeForm
+from django_authopenid.util import OpenID, DjangoOpenIDStore, from_openid_response
from django_authopenid.models import UserAssociation, UserPasswordQueue, ExternalLoginData
from django_authopenid.forms import OpenidSigninForm, ClassicLoginForm, OpenidRegisterForm, \
OpenidVerifyForm, ClassicRegisterForm, ChangePasswordForm, ChangeEmailForm, \
ChangeopenidForm, DeleteForm, EmailPasswordForm
-import external_login
import logging
+from utils.forms import get_next_url
+
+EXTERNAL_LOGIN_APP = settings.LOAD_EXTERNAL_LOGIN_APP()
def login(request,user):
from django.contrib.auth import login as _login
from forum.models import user_logged_in #custom signal
- print 'in login call'
-
if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- external_login.login(request,user)
+ EXTERNAL_LOGIN_APP.api.login(request,user)
#1) get old session key
session_key = request.session.session_key
@@ -90,7 +90,7 @@ def logout(request):
from django.contrib.auth import logout as _logout#for login I've added wrapper below - called login
_logout(request)
if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- external_login.logout(request)
+ EXTERNAL_LOGIN_APP.api.logout(request)
def get_url_host(request):
if request.is_secure():
@@ -158,7 +158,7 @@ def default_on_success(request, identity_url, openid_response):
def default_on_failure(request, message):
""" default failure action on signin """
- return render('openid_failure.html', {
+ return render_to_response('openid_failure.html', {
'message': message
})
@@ -184,7 +184,7 @@ def signin(request,newquestion=False,newanswer=False):
"""
request.encoding = 'UTF-8'
on_failure = signin_failure
- email_feeds_form = EditUserEmailFeedsForm()
+ email_feeds_form = SimpleEmailSubscribeForm()
next = get_next_url(request)
form_signin = OpenidSigninForm(initial={'next':next})
form_auth = ClassicLoginForm(initial={'next':next})
@@ -206,35 +206,31 @@ def signin(request,newquestion=False,newanswer=False):
request.session['external_username'] = username
request.session['external_password'] = password
- #2) see if username clashes with some existing user
+ #2) try to extract user email and nickname from external service
+ email = EXTERNAL_LOGIN_APP.api.get_email(username,password)
+ screen_name = EXTERNAL_LOGIN_APP.api.get_screen_name(username,password)
+
+ #3) see if username clashes with some existing user
#if so, we have to prompt the user to pick a different name
- username_taken = User.is_username_taken(username)
- #try:
- # User.objects.get(username=username)
- # username_taken = True
- #except User.DoesNotExist:
- # username_taken = False
-
- #3) try to extract user email from external service
- email = external_login.get_email(username,password)
-
- email_feeds_form = EditUserEmailFeedsForm()
- form_data = {'username':username,'email':email,'next':next}
+ username_taken = User.is_username_taken(screen_name)
+
+ email_feeds_form = SimpleEmailSubscribeForm()
+ form_data = {'username':screen_name,'email':email,'next':next}
form = OpenidRegisterForm(initial=form_data)
- template_data = {'form1':form,'username':username,\
+ template_data = {'form1':form,'username':screen_name,\
'email_feeds_form':email_feeds_form,\
'provider':mark_safe(settings.EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME),\
'login_type':'legacy',\
'gravatar_faq_url':reverse('faq') + '#gravatar',\
'external_login_name_is_taken':username_taken}
- return render('authopenid/complete.html',template_data,\
+ return render_to_response('authopenid/complete.html',template_data,\
context_instance=RequestContext(request))
else:
#user existed, external password is ok
user = form_auth.get_user()
login(request,user)
response = HttpResponseRedirect(get_next_url(request))
- external_login.set_login_cookies(response,user)
+ EXTERNAL_LOGIN_APP.api.set_login_cookies(response,user)
return response
else:
#regular password authentication
@@ -246,7 +242,7 @@ def signin(request,newquestion=False,newanswer=False):
#register externally logged in password user with a new local account
if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
form = OpenidRegisterForm(request.POST)
- email_feeds_form = EditUserEmailFeedsForm(request.POST)
+ email_feeds_form = SimpleEmailSubscribeForm(request.POST)
form1_is_valid = form.is_valid()
form2_is_valid = email_feeds_form.is_valid()
if form1_is_valid and form2_is_valid:
@@ -254,10 +250,10 @@ def signin(request,newquestion=False,newanswer=False):
username = form.cleaned_data['username']
password = request.session.get('external_password',None)
email = form.cleaned_data['email']
- print 'got email addr %s' % email
if password and username:
User.objects.create_user(username,email,password)
user = authenticate(username=username,password=password)
+ EXTERNAL_LOGIN_APP.api.connect_local_user_to_external_user(user,username,password)
external_username = request.session['external_username']
eld = ExternalLoginData.objects.get(external_username=external_username)
eld.user = user
@@ -266,7 +262,9 @@ def signin(request,newquestion=False,newanswer=False):
email_feeds_form.save(user)
del request.session['external_username']
del request.session['external_password']
- return HttpResponseRedirect(reverse('index'))
+ response = HttpResponseRedirect(reverse('index'))
+ EXTERNAL_LOGIN_APP.api.set_login_cookies(response, user)
+ return response
else:
if password:
del request.session['external_username']
@@ -281,7 +279,7 @@ def signin(request,newquestion=False,newanswer=False):
'email_feeds_form':email_feeds_form,'provider':provider,\
'gravatar_faq_url':reverse('faq') + '#gravatar',\
'external_login_name_is_taken':username_taken}
- return render('authopenid/complete.html',data,
+ return render_to_response('authopenid/complete.html',data,
context_instance=RequestContext(request))
else:
raise Http404
@@ -319,7 +317,7 @@ def signin(request,newquestion=False,newanswer=False):
if len(alist) > 0:
answer = alist[0]
- return render('authopenid/signin.html', {
+ return render_to_response('authopenid/signin.html', {
'question':question,
'answer':answer,
'form1': form_auth,
@@ -400,14 +398,14 @@ def register(request):
'next': next,
'username': nickname,
})
- email_feeds_form = EditUserEmailFeedsForm()
+ email_feeds_form = SimpleEmailSubscribeForm()
user_ = None
is_redirect = False
if request.POST:
if 'bnewaccount' in request.POST.keys():
form1 = OpenidRegisterForm(request.POST)
- email_feeds_form = EditUserEmailFeedsForm(request.POST)
+ email_feeds_form = SimpleEmailSubscribeForm(request.POST)
if form1.is_valid() and email_feeds_form.is_valid():
next = form1.cleaned_data['next']
is_redirect = True
@@ -469,7 +467,7 @@ def register(request):
else:
provider_logo = providers[provider_name]
- return render('authopenid/complete.html', {
+ return render_to_response('authopenid/complete.html', {
'form1': form1,
'form2': form2,
'email_feeds_form': email_feeds_form,
@@ -490,7 +488,7 @@ def signin_failure(request, message):
form_signin = OpenidSigninForm(initial={'next': next})
form_auth = ClassicLoginForm(initial={'next': next})
- return render('authopenid/signin.html', {
+ return render_to_response('authopenid/signin.html', {
'msg': message,
'form1': form_auth,
'form2': form_signin,
@@ -506,11 +504,11 @@ def signup(request):
templates: authopenid/signup.html, authopenid/confirm_email.txt
"""
if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
+ return HttpResponseRedirect(reverse('user_external_legacy_login_signup'))
next = get_next_url(request)
if request.POST:
form = ClassicRegisterForm(request.POST)
- email_feeds_form = EditUserEmailFeedsForm(request.POST)
+ email_feeds_form = SimpleEmailSubscribeForm(request.POST)
#validation outside if to remember form values
form1_is_valid = form.is_valid()
@@ -523,7 +521,7 @@ def signup(request):
user_ = User.objects.create_user( username,email,password )
if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- external_login.create_user(username,email,password)
+ EXTERNAL_LOGIN_APP.api.create_user(username,email,password)
user_.backend = "django.contrib.auth.backends.ModelBackend"
login(request, user_)
@@ -545,8 +543,8 @@ def signup(request):
return HttpResponseRedirect(next)
else:
form = ClassicRegisterForm(initial={'next':next})
- email_feeds_form = EditUserEmailFeedsForm()
- return render('authopenid/signup.html', {
+ email_feeds_form = SimpleEmailSubscribeForm()
+ return render_to_response('authopenid/signup.html', {
'form': form,
'email_feeds_form': email_feeds_form
}, context_instance=RequestContext(request))
@@ -571,7 +569,7 @@ def xrdf(request):
return_to = [
"%s%s" % (url_host, reverse('user_complete_signin'))
]
- return render('authopenid/yadis.xrdf', {
+ return render_to_response('authopenid/yadis.xrdf', {
'return_to': return_to
}, context_instance=RequestContext(request))
@@ -599,7 +597,7 @@ def account_settings(request):
is_openid = False
- return render('authopenid/settings.html', {
+ return render_to_response('authopenid/settings.html', {
'msg': msg,
'is_openid': is_openid
}, context_instance=RequestContext(request))
@@ -633,7 +631,7 @@ def changepw(request):
else:
form = ChangePasswordForm(user=user_)
- return render('authopenid/changepw.html', {'form': form },
+ return render_to_response('authopenid/changepw.html', {'form': form },
context_instance=RequestContext(request))
def find_email_validation_messages(user):
@@ -697,7 +695,7 @@ def send_email_key(request):
if settings.EMAIL_VALIDATION != 'off':
if request.user.email_isvalid:
- return render('authopenid/changeemail.html',
+ return render_to_response('authopenid/changeemail.html',
{ 'email': request.user.email,
'action_type': 'key_not_sent',
'change_link': reverse('user_changeemail')},
@@ -712,7 +710,7 @@ def send_email_key(request):
#internal server view used as return value by other views
def validation_email_sent(request):
- return render('authopenid/changeemail.html',
+ return render_to_response('authopenid/changeemail.html',
{ 'email': request.user.email,
'change_email_url': reverse('user_changeemail'),
'action_type': 'validate', },
@@ -730,7 +728,7 @@ def verifyemail(request,id=None,key=None):
user.email_isvalid = True
clear_email_validation_message(user)
user.save()
- return render('authopenid/changeemail.html', {
+ return render_to_response('authopenid/changeemail.html', {
'action_type': 'validation_complete',
}, context_instance=RequestContext(request))
raise Http404
@@ -773,7 +771,7 @@ def changeemail(request, action='change'):
form = ChangeEmailForm(initial={'email': user_.email},
user=user_)
- output = render('authopenid/changeemail.html', {
+ output = render_to_response('authopenid/changeemail.html', {
'form': form,
'email': user_.email,
'action_type': action,
@@ -857,7 +855,7 @@ def changeopenid(request):
changeopenid_failure, redirect_to)
form = ChangeopenidForm(initial={'openid_url': openid_url }, user=user_)
- return render('authopenid/changeopenid.html', {
+ return render_to_response('authopenid/changeopenid.html', {
'form': form,
'has_openid': has_openid,
'msg': msg
@@ -934,7 +932,7 @@ def delete(request):
form = DeleteForm(user=user_)
msg = request.GET.get('msg','')
- return render('authopenid/delete.html', {
+ return render_to_response('authopenid/delete.html', {
'form': form,
'msg': msg,
}, context_instance=RequestContext(request))
@@ -969,7 +967,10 @@ def deleteopenid_failure(request, message):
return HttpResponseRedirect(redirect_to)
def external_legacy_login_info(request):
- return render('authopenid/external_legacy_login_info.html', context_instance=RequestContext(request))
+ feedback_url = reverse('feedback')
+ return render_to_response('authopenid/external_legacy_login_info.html',
+ {'feedback_url':feedback_url},
+ context_instance=RequestContext(request))
def sendpw(request):
"""
@@ -1019,7 +1020,7 @@ def sendpw(request):
else:
form = EmailPasswordForm()
- return render('authopenid/sendpw.html', {
+ return render_to_response('authopenid/sendpw.html', {
'form': form,
'msg': msg
}, context_instance=RequestContext(request))
diff --git a/dos2unix.sh b/dos2unix.sh
index 96a51c9d..2864426a 100644
--- a/dos2unix.sh
+++ b/dos2unix.sh
@@ -1,3 +1,5 @@
+#please take care not to dos2unix anything in your .git directory
+#because that will probably break your repo
dos2unix `find . -name '*.py'`
dos2unix `find . -name '*.po'`
dos2unix `find . -name '*.js'`
diff --git a/drop-auth.sql b/drop-auth.sql
new file mode 100644
index 00000000..bc17dce3
--- /dev/null
+++ b/drop-auth.sql
@@ -0,0 +1,8 @@
+drop table auth_group;
+drop table auth_group_permissions;
+drop table auth_message;
+drop table auth_permission;
+drop table auth_user;
+drop table auth_user_groups;
+drop table auth_user_user_permissions;
+
diff --git a/forum/feed.py b/forum/feed.py
index ad1d5cbd..e4b929e9 100644
--- a/forum/feed.py
+++ b/forum/feed.py
@@ -13,16 +13,16 @@
from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
from django.utils.translation import ugettext as _
from models import Question
-import settings
+from django.conf import settings
class RssLastestQuestionsFeed(Feed):
title = settings.APP_TITLE + _(' - ')+ _('latest questions')
- link = settings.APP_URL + '/' + _('question/')
+ link = settings.APP_URL #+ '/' + _('question/')
description = settings.APP_DESCRIPTION
#ttl = 10
copyright = settings.APP_COPYRIGHT
def item_link(self, item):
- return self.link + '%s/' % item.id
+ return self.link + item.get_absolute_url()
def item_author_name(self, item):
return item.author.username
diff --git a/forum/forms.py b/forum/forms.py
index 2d2021b5..42becc11 100644
--- a/forum/forms.py
+++ b/forum/forms.py
@@ -4,8 +4,10 @@ from django import forms
from models import *
from const import *
from django.utils.translation import ugettext as _
-from django_authopenid.forms import NextUrlField, UserNameField
-import settings
+from utils.forms import NextUrlField, UserNameField
+from recaptcha_django import ReCaptchaField
+from django.conf import settings
+import logging
class TitleField(forms.CharField):
def __init__(self, *args, **kwargs):
@@ -109,6 +111,9 @@ class ModerateUserForm(forms.ModelForm):
model = User
fields = ('is_approved',)
+class NotARobotForm(forms.Form):
+ recaptcha = ReCaptchaField()
+
class FeedbackForm(forms.Form):
name = forms.CharField(label=_('Your name:'), required=False)
email = forms.EmailField(label=_('Email (not shared with anyone):'), required=False)
@@ -204,8 +209,8 @@ class EditUserForm(forms.Form):
def __init__(self, user, *args, **kwargs):
super(EditUserForm, self).__init__(*args, **kwargs)
- #self.fields['username'].initial = user.username
- #self.fields['username'].user_instance = user
+ self.fields['username'].initial = user.username
+ self.fields['username'].user_instance = user
self.fields['email'].initial = user.email
self.fields['realname'].initial = user.real_name
self.fields['website'].initial = user.website
@@ -299,14 +304,24 @@ class EditUserEmailFeedsForm(forms.Form):
self.initial = self.NO_EMAIL_INITIAL
return self
- def save(self,user):
+ def save(self,user,save_unbound=False):
+ """
+ with save_unbound==True will bypass form validation and save initial values
+ """
changed = False
for form_field, feed_type in self.FORM_TO_MODEL_MAP.items():
s, created = EmailFeedSetting.objects.get_or_create(subscriber=user,\
feed_type=feed_type)
- new_value = self.cleaned_data[form_field]
+ if save_unbound:
+ #just save initial values instead
+ if form_field in self.initial:
+ new_value = self.initial[form_field]
+ else:
+ new_value = self.fields[form_field].initial
+ else:
+ new_value = self.cleaned_data[form_field]
if s.frequency != new_value:
- s.frequency = self.cleaned_data[form_field]
+ s.frequency = new_value
s.save()
changed = True
else:
@@ -316,3 +331,22 @@ class EditUserEmailFeedsForm(forms.Form):
feed_type = ContentType.objects.get_for_model(Question)
user.followed_questions.clear()
return changed
+
+
+class SimpleEmailSubscribeForm(forms.Form):
+ SIMPLE_SUBSCRIBE_CHOICES = (
+ ('y',_('okay, let\'s try!')),
+ ('n',_('no OSQA community email please, thanks'))
+ )
+ subscribe = forms.ChoiceField(widget=forms.widgets.RadioSelect(), \
+ error_messages={'required':_('please choose one of the options above')},
+ choices=SIMPLE_SUBSCRIBE_CHOICES)
+
+ def save(self,user=None):
+ EFF = EditUserEmailFeedsForm
+ if self.cleaned_data['subscribe'] == 'y':
+ email_settings_form = EFF()
+ logging.debug('%s wants to subscribe' % user.username)
+ else:
+ email_settings_form = EFF(initial=EFF.NO_EMAIL_INITIAL)
+ email_settings_form.save(user,save_unbound=True)
diff --git a/forum/management/commands/message_to_everyone.py b/forum/management/commands/message_to_everyone.py
new file mode 100644
index 00000000..c020c178
--- /dev/null
+++ b/forum/management/commands/message_to_everyone.py
@@ -0,0 +1,12 @@
+from django.core.management.base import NoArgsCommand
+from django.contrib.auth.models import User
+import sys
+
+class Command(NoArgsCommand):
+ def handle_noargs(self, **options):
+ msg = None
+ if msg == None:
+ print 'to run this command, please first edit the file %s' % __file__
+ sys.exit(1)
+ for u in User.objects.all():
+ u.message_set.create(message = msg % u.username)
diff --git a/forum/management/commands/multi_award_badges.py b/forum/management/commands/multi_award_badges.py
index c6dbc250..6b330cf9 100644
--- a/forum/management/commands/multi_award_badges.py
+++ b/forum/management/commands/multi_award_badges.py
@@ -345,4 +345,4 @@ class Command(BaseCommand):
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
+ cursor.close()
diff --git a/forum/management/commands/send_email_alerts.py b/forum/management/commands/send_email_alerts.py
index f5974e6b..62f13d69 100644
--- a/forum/management/commands/send_email_alerts.py
+++ b/forum/management/commands/send_email_alerts.py
@@ -7,7 +7,7 @@ from django.core.mail import EmailMessage
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
import datetime
-import settings
+from django.conf import settings
import logging
from utils.odict import OrderedDict
@@ -58,10 +58,10 @@ class Command(NoArgsCommand):
q_ans.cutoff_time = cutoff_time
elif feed.feed_type == 'q_all':
if user.tag_filter_setting == 'ignored':
- ignored_tags = Tag.objects.filter(user_selections___reason='bad',user_selections__user=user)
+ ignored_tags = Tag.objects.filter(user_selections__reason='bad',user_selections__user=user)
q_all = Q_set.exclude( tags__in=ignored_tags )
else:
- selected_tags = Tag.objects.filter(user_selections___reason='good',user_selections__user=user)
+ selected_tags = Tag.objects.filter(user_selections__reason='good',user_selections__user=user)
q_all = Q_set.filter( tags__in=selected_tags )
q_all.cutoff_time = cutoff_time
#build list in this order
@@ -154,6 +154,7 @@ class Command(NoArgsCommand):
if num_q > 0:
url_prefix = settings.APP_URL
subject = _('email update message subject')
+ print 'have %d updated questions for %s' % (num_q, user.username)
text = ungettext('%(name)s, this is an update message header for a question',
'%(name)s, this is an update message header for %(num)d questions',num_q) \
% {'num':num_q, 'name':user.username}
diff --git a/forum/management/commands/subscribe_everyone.py b/forum/management/commands/subscribe_everyone.py
index f5cbf8eb..c79528f3 100644
--- a/forum/management/commands/subscribe_everyone.py
+++ b/forum/management/commands/subscribe_everyone.py
@@ -6,7 +6,7 @@ from django.core.mail import EmailMessage
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
import datetime
-import settings
+from django.conf import settings
class Command(NoArgsCommand):
def handle_noargs(self,**options):
diff --git a/forum/models.py b/forum/models.py
index c3b89ce9..a2988be4 100644
--- a/forum/models.py
+++ b/forum/models.py
@@ -15,7 +15,7 @@ from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
from django.contrib.sitemaps import ping_google
import django.dispatch
-import settings
+from django.conf import settings
import logging
if settings.USE_SPHINX_SEARCH == True:
@@ -831,7 +831,7 @@ def notify_award_message(instance, created, **kwargs):
"""
if created:
user = instance.user
- user.message_set.create(message=u"%s" % instance.badge.name)
+ user.message_set.create(message=u"Congratulations, you have received a badge '%s'" % instance.badge.name)
def record_answer_accepted(instance, created, **kwargs):
"""
diff --git a/forum/sitemap.py b/forum/sitemap.py
index dc97a009..c0c60b5e 100644
--- a/forum/sitemap.py
+++ b/forum/sitemap.py
@@ -9,3 +9,6 @@ class QuestionsSitemap(Sitemap):
def lastmod(self, obj):
return obj.last_activity_at
+
+ def location(self, obj):
+ return obj.get_absolute_url()
diff --git a/forum/urls.py b/forum/urls.py
index f7d6eba5..42746d44 100644
--- a/forum/urls.py
+++ b/forum/urls.py
@@ -54,7 +54,7 @@ urlpatterns = patterns('',
app.delete_comment, kwargs={'commented_object_type':'answer'}, \
name='delete_answer_comment'), \
#place general question item in the end of other operations
- url(r'^%s(?P<id>\d+)//*' % _('question/'), app.question, name='question'),
+ url(r'^%s(?P<id>\d+)/' % _('question/'), app.question, name='question'),
url(r'^%s$' % _('tags/'), app.tags, name='tags'),
url(r'^%s(?P<tag>[^/]+)/$' % _('tags/'), app.tag, name='tag_questions'),
diff --git a/forum/views.py b/forum/views.py
index c4514912..bad37693 100644
--- a/forum/views.py
+++ b/forum/views.py
@@ -33,7 +33,7 @@ from forum.auth import *
from forum.const import *
from forum.user import *
from forum import auth
-from django_authopenid.util import get_next_url
+from utils.forms import get_next_url
# used in index page
INDEX_PAGE_SIZE = 20
@@ -436,6 +436,21 @@ def question(request, id):
logging.debug('view_id=' + str(view_id))
question = get_object_or_404(Question, id=id)
+ try:
+ pattern = r'/%s%s%d/([\w-]+)' % (settings.FORUM_SCRIPT_ALIAS,_('question/'), question.id)
+ path_re = re.compile(pattern)
+ logging.debug(pattern)
+ logging.debug(request.path)
+ m = path_re.match(request.path)
+ if m:
+ slug = m.group(1)
+ logging.debug('have slug %s' % slug)
+ assert(slug == slugify(question.title))
+ else:
+ logging.debug('no match!')
+ except:
+ return HttpResponseRedirect(question.get_absolute_url())
+
if question.deleted and not can_view_deleted_post(request.user, question):
raise Http404
answer_form = AnswerForm(question,request.user)
diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo
index 16f3554d..ef3007a0 100644
--- a/locale/en/LC_MESSAGES/django.mo
+++ b/locale/en/LC_MESSAGES/django.mo
Binary files differ
diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po
index 15385964..ee40fa36 100644
--- a/locale/en/LC_MESSAGES/django.po
+++ b/locale/en/LC_MESSAGES/django.po
@@ -175,7 +175,7 @@ msgstr ""
#: django_authopenid/urls.py:15
msgid "external-login/"
-msgstr "using-nmr-wiki-login-and-password/"
+msgstr ""
#: django_authopenid/urls.py:16
msgid "register/"
diff --git a/mediawiki/PHPSerialize.py b/mediawiki/PHPSerialize.py
new file mode 100644
index 00000000..d25b71bd
--- /dev/null
+++ b/mediawiki/PHPSerialize.py
@@ -0,0 +1,149 @@
+import types, string
+
+"""
+Serialize class for the PHP serialization format.
+
+@version v0.4 BETA
+@author Scott Hurring; scott at hurring dot com
+@copyright Copyright (c) 2005 Scott Hurring
+@license http://opensource.org/licenses/gpl-license.php GNU Public License
+$Id: PHPSerialize.py,v 1.1 2006/01/08 21:53:19 shurring Exp $
+
+Most recent version can be found at:
+http://hurring.com/code/python/phpserialize/
+
+Usage:
+# Create an instance of the serialize engine
+s = PHPSerialize()
+# serialize some python data into a string
+serialized_string = s.serialize(string)
+# encode a session list (php's session_encode)
+serialized_string = s.session_encode(list)
+
+See README.txt for more information.
+"""
+
+class PHPSerialize(object):
+ """
+ Class to serialize data using the PHP Serialize format.
+
+ Usage:
+ serialized_string = PHPSerialize().serialize(data)
+ serialized_string = PHPSerialize().session_encode(list)
+ """
+
+ def __init__(self):
+ pass
+
+ def session_encode(self, session):
+ """Thanks to Ken Restivo for suggesting the addition
+ of session_encode
+ """
+ out = ""
+ for (k,v) in session.items():
+ out = out + "%s|%s" % (k, self.serialize(v))
+ return out
+
+ def serialize(self, data):
+ return self.serialize_value(data)
+
+ def is_int(self, data):
+ """
+ Determine if a string var looks like an integer
+ TODO: Make this do what PHP does, instead of a hack
+ """
+ try:
+ int(data)
+ return True
+ except:
+ return False
+
+ def serialize_key(self, data):
+ """
+ Serialize a key, which follows different rules than when
+ serializing values. Many thanks to Todd DeLuca for pointing
+ out that keys are serialized differently than values!
+
+ From http://us2.php.net/manual/en/language.types.array.php
+ A key may be either an integer or a string.
+ If a key is the standard representation of an integer, it will be
+ interpreted as such (i.e. "8" will be interpreted as int 8,
+ while "08" will be interpreted as "08").
+ Floats in key are truncated to integer.
+ """
+ # Integer, Long, Float, Boolean => integer
+ if type(data) is types.IntType or type(data) is types.LongType \
+ or type(data) is types.FloatType or type(data) is types.BooleanType:
+ return "i:%s;" % int(data)
+
+ # String => string or String => int (if string looks like int)
+ elif type(data) is types.StringType:
+ if self.is_int(data):
+ return "i:%s;" % int(data)
+ else:
+ return "s:%i:\"%s\";" % (len(data), data);
+
+ # None / NULL => empty string
+ elif type(data) is types.NoneType:
+ return "s:0:\"\";"
+
+ # I dont know how to serialize this
+ else:
+ raise Exception("Unknown / Unhandled key type (%s)!" % type(data))
+
+
+ def serialize_value(self, data):
+ """
+ Serialize a value.
+ """
+
+ # Integer => integer
+ if type(data) is types.IntType:
+ return "i:%s;" % data
+
+ # Float, Long => double
+ elif type(data) is types.FloatType or type(data) is types.LongType:
+ return "d:%s;" % data
+
+ # String => string or String => int (if string looks like int)
+ # Thanks to Todd DeLuca for noticing that PHP strings that
+ # look like integers are serialized as ints by PHP
+ elif type(data) is types.StringType:
+ if self.is_int(data):
+ return "i:%s;" % int(data)
+ else:
+ return "s:%i:\"%s\";" % (len(data), data);
+
+ # None / NULL
+ elif type(data) is types.NoneType:
+ return "N;";
+
+ # Tuple and List => array
+ # The 'a' array type is the only kind of list supported by PHP.
+ # array keys are automagically numbered up from 0
+ elif type(data) is types.ListType or type(data) is types.TupleType:
+ i = 0
+ out = []
+ # All arrays must have keys
+ for k in data:
+ out.append(self.serialize_key(i))
+ out.append(self.serialize_value(k))
+ i += 1
+ return "a:%i:{%s}" % (len(data), "".join(out))
+
+ # Dict => array
+ # Dict is the Python analogy of a PHP array
+ elif type(data) is types.DictType:
+ out = []
+ for k in data:
+ out.append(self.serialize_key(k))
+ out.append(self.serialize_value(data[k]))
+ return "a:%i:{%s}" % (len(data), "".join(out))
+
+ # Boolean => bool
+ elif type(data) is types.BooleanType:
+ return "b:%i;" % (data == 1)
+
+ # I dont know how to serialize this
+ else:
+ raise Exception("Unknown / Unhandled data type (%s)!" % type(data))
diff --git a/mediawiki/PHPUnserialize.py b/mediawiki/PHPUnserialize.py
new file mode 100644
index 00000000..b59c869c
--- /dev/null
+++ b/mediawiki/PHPUnserialize.py
@@ -0,0 +1,187 @@
+import types, string, re
+
+"""
+Unserialize class for the PHP serialization format.
+
+@version v0.4 BETA
+@author Scott Hurring; scott at hurring dot com
+@copyright Copyright (c) 2005 Scott Hurring
+@license http://opensource.org/licenses/gpl-license.php GNU Public License
+$Id: PHPUnserialize.py,v 1.1 2006/01/08 21:53:19 shurring Exp $
+
+Most recent version can be found at:
+http://hurring.com/code/python/phpserialize/
+
+Usage:
+# Create an instance of the unserialize engine
+u = PHPUnserialize()
+# unserialize some string into python data
+data = u.unserialize(serialized_string)
+
+Please see README.txt for more information.
+"""
+
+class PHPUnserialize(object):
+ """
+ Class to unserialize something from the PHP Serialize format.
+
+ Usage:
+ u = PHPUnserialize()
+ data = u.unserialize(serialized_string)
+ """
+
+ def __init__(self):
+ pass
+
+ def session_decode(self, data):
+ """Thanks to Ken Restivo for suggesting the addition
+ of session_encode
+ """
+ session = {}
+ while len(data) > 0:
+ m = re.match('^(\w+)\|', data)
+ if m:
+ key = m.group(1)
+ offset = len(key)+1
+ (dtype, dataoffset, value) = self._unserialize(data, offset)
+ offset = offset + dataoffset
+ data = data[offset:]
+ session[key] = value
+ else:
+ # No more stuff to decode
+ return session
+
+ return session
+
+ def unserialize(self, data):
+ return self._unserialize(data, 0)[2]
+
+ def _unserialize(self, data, offset=0):
+ """
+ Find the next token and unserialize it.
+ Recurse on array.
+
+ offset = raw offset from start of data
+
+ return (type, offset, value)
+ """
+
+ buf = []
+ dtype = string.lower(data[offset:offset+1])
+
+ #print "# dtype =", dtype
+
+ # 't:' = 2 chars
+ dataoffset = offset + 2
+ typeconvert = lambda x : x
+ chars = datalength = 0
+
+ # int => Integer
+ if dtype == 'i':
+ typeconvert = lambda x : int(x)
+ (chars, readdata) = self.read_until(data, dataoffset, ';')
+ # +1 for end semicolon
+ dataoffset += chars + 1
+
+ # bool => Boolean
+ elif dtype == 'b':
+ typeconvert = lambda x : (int(x) == 1)
+ (chars, readdata) = self.read_until(data, dataoffset, ';')
+ # +1 for end semicolon
+ dataoffset += chars + 1
+
+ # double => Floating Point
+ elif dtype == 'd':
+ typeconvert = lambda x : float(x)
+ (chars, readdata) = self.read_until(data, dataoffset, ';')
+ # +1 for end semicolon
+ dataoffset += chars + 1
+
+ # n => None
+ elif dtype == 'n':
+ readdata = None
+
+ # s => String
+ elif dtype == 's':
+ (chars, stringlength) = self.read_until(data, dataoffset, ':')
+ # +2 for colons around length field
+ dataoffset += chars + 2
+
+ # +1 for start quote
+ (chars, readdata) = self.read_chars(data, dataoffset+1, int(stringlength))
+ # +2 for endquote semicolon
+ dataoffset += chars + 2
+
+ if chars != int(stringlength) != int(readdata):
+ raise Exception("String length mismatch")
+
+ # array => Dict
+ # If you originally serialized a Tuple or List, it will
+ # be unserialized as a Dict. PHP doesn't have tuples or lists,
+ # only arrays - so everything has to get converted into an array
+ # when serializing and the original type of the array is lost
+ elif dtype == 'a':
+ readdata = {}
+
+ # How many keys does this list have?
+ (chars, keys) = self.read_until(data, dataoffset, ':')
+ # +2 for colons around length field
+ dataoffset += chars + 2
+
+ # Loop through and fetch this number of key/value pairs
+ for i in range(0, int(keys)):
+ # Read the key
+ (ktype, kchars, key) = self._unserialize(data, dataoffset)
+ dataoffset += kchars
+ #print "Key(%i) = (%s, %i, %s) %i" % (i, ktype, kchars, key, dataoffset)
+
+ # Read value of the key
+ (vtype, vchars, value) = self._unserialize(data, dataoffset)
+ dataoffset += vchars
+ #print "Value(%i) = (%s, %i, %s) %i" % (i, vtype, vchars, value, dataoffset)
+
+ # Set the list element
+ readdata[key] = value
+
+ # +1 for end semicolon
+ dataoffset += 1
+ #chars = int(dataoffset) - start
+
+ # I don't know how to unserialize this
+ else:
+ raise Exception("Unknown / Unhandled data type (%s)!" % dtype)
+
+
+ return (dtype, dataoffset-offset, typeconvert(readdata))
+
+ def read_until(self, data, offset, stopchar):
+ """
+ Read from data[offset] until you encounter some char 'stopchar'.
+ """
+ buf = []
+ char = data[offset:offset+1]
+ i = 2
+ while char != stopchar:
+ # Consumed all the characters and havent found ';'
+ if i+offset > len(data):
+ raise Exception("Invalid")
+ buf.append(char)
+ char = data[offset+(i-1):offset+i]
+ i += 1
+
+ # (chars_read, data)
+ return (len(buf), "".join(buf))
+
+ def read_chars(self, data, offset, length):
+ """
+ Read 'length' number of chars from data[offset].
+ """
+ buf = []
+ # Account for the starting quote char
+ #offset += 1
+ for i in range(0, length):
+ char = data[offset+(i-1):offset+i]
+ buf.append(char)
+
+ # (chars_read, data)
+ return (len(buf), "".join(buf))
diff --git a/mediawiki/README b/mediawiki/README
new file mode 100644
index 00000000..b664a8a7
--- /dev/null
+++ b/mediawiki/README
@@ -0,0 +1,106 @@
+This is a rough example of integrated mediawiki authentication
+originally written to work on a customized MW (some tables are different from standard)
+so to adapt this to your case you'll most likely need to tweak this
+
+Also keep in mind that probably a better solution would be to create a single signon site.
+
+Author: evgeny.fadeev@gmail.com (Evgeny)
+
+==Minimal directions==
+
+1) Add the following to your settings_local.py (with relevant modifications):
+
+USE_EXTERNAL_LEGACY_LOGIN = True #enable external legacy login
+EXTERNAL_LEGACY_LOGIN_AUTHENTICATION_BACKEND = 'mediawiki.auth.IncludeVirtualAuthenticationBackend'
+EXTERNAL_LEGACY_LOGIN_AUTHENTICATION_MIDDLEWARE = 'mediawiki.middleware.IncludeVirtualAuthenticationMiddleware'
+EXTERNAL_LEGACY_LOGIN_MODULE = 'mediawiki' #current module
+EXTERNAL_LEGACY_LOGIN_HOST = 'yoursite.org' #wiki domain
+EXTERNAL_LEGACY_LOGIN_PORT = 80 #port, probably 80
+EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME = 'My Wiki' #html allowed
+MEDIAWIKI_URL="http://yoursite.org/wiki/index.php"
+MEDIAWIKI_SALT_PASSWORD=True #or False - depending on your LocalSettings.php
+MEDIAWIKI_INDEX_PHP_URL='/wiki/index.php'
+MEDIAWIKI_COOKIE_DOMAIN='.yoursite.org' #for cross subdomain login
+MEDIAWIKI_SESSION_COOKIE_NAME = '' # probably '<dbname>_<dbtblprefix>_session'
+MEDIAWIKI_PHP_SESSION_PREFIX='sess_' #depends on your setup
+MEDIAWIKI_PHP_SESSION_PATH='/var/lib/php/session'
+SESSION_COOKIE_DOMAIN = '.yoursite.org' #use this notation for cross-subdomain login
+
+2) Configure apache to access forum "backend" via the wiki domain.
+Example configuration is in the end of the doc.
+
+3) Install two wiki extensions WsgiInjectableSpecialPage, UserRegister -
+the usual MW way. You might want to disable traditional wiki registration.
+
+4) grep files for 'yourwiki' and change that to your own taste - there are some
+hardcoded urls
+
+5) Templates are in templates/mediawiki - you probably will want to customize them,
+form js media: templates/content/js/mediawiki-login.js
+form css media: templates/content/style/mediawiki-login.css
+
+==Requirements==
+wiki and forum must live in the same mysql database for registration to work,
+however login will work even if this is not the case
+
+you must own both wiki and forum or there must be good trust relationship
+between the owners - because password is shared
+
+==Notes on how external login currently works.==
+password and login are entered in the login form.
+these are checked against mw api
+
+password is saved in the auth_user table (the django way)
+so if you at some point set USE_EXTERNAL_LEGACY_LOGIN = False
+wiki passwords and logins will still work on the forum
+
+login action is partially synchronized btw wiki and forum (from forum to wiki,
+but not the opposite way yet)
+
+when users first register - either on wiki or forum they are logged in on both
+
+on registration they receive a greeting email - you will want to customize messages
+
+technically, on the wiki registration form is injected via apache SSI
+- using include virtual call
+
+there is a possibility for cross-site scripting attack if wiki session is stolen
+
+==Apache setup example for the wiki==
+This assumes that wiki and forum facing the user are on different subdomains.
+Also this setup is just an example - you may do better :).
+Forum setup in apache is described in main osqa INSTALL document - that's extra.
+
+<VirtualHost your.ip:port>
+ ServerAdmin admin@yourwiki.org
+ DocumentRoot /path/to/wiki/root #dir containing wiki directory
+ ServerName yourwiki.org
+ AddOutputFilter INCLUDES .php
+ Alias /backend/content/ /path/to/forum/templates/content/
+ AliasMatch (content\/style\/[^/]*\.css) /path/to/forum/templates/$1
+ AliasMatch (content\/.*) /path/to/forum/templates/$1
+ <Directory /path/to/forum/templates/content>
+ Order deny,allow
+ Allow from all
+ </Directory>
+ WSGIDaemonProcess my-forum-wiki-side #use daemon mode so to avoid potential timezone messups
+ WSGIProcessGroup my-forum-wiki-side
+ WSGIScriptAlias /backend /path/to/forum/cnprog.wsgi
+ CustomLog /var/log/httpd/yourwiki/access_log common
+ ErrorLog /var/log/httpd/yourwiki/error_log
+ LogLevel debug
+ DirectoryIndex index.php index.html
+</VirtualHost>
+<Directory /path/to/wiki/root>
+ Options Includes
+ <IfModule sapi_apache2.c>
+ php_admin_flag engine on
+ php_admin_flag safe_mode off
+ </IfModule>
+ <IfModule mod_php5.c>
+ php_admin_flag engine on
+ php_admin_flag safe_mode off
+ php_admin_value open_basedir "/path/to/wiki/root:.:/tmp" #tmp used for sessions
+ </IfModule>
+</Directory>
+
diff --git a/mediawiki/UserRegister.alias.php b/mediawiki/UserRegister.alias.php
new file mode 100644
index 00000000..0d5c1523
--- /dev/null
+++ b/mediawiki/UserRegister.alias.php
@@ -0,0 +1,7 @@
+<?php
+$messages = array();
+
+/* *** English *** */
+$messages['en'] = array(
+ 'User Register' => array('User Register'),
+);
diff --git a/mediawiki/UserRegister.body.php b/mediawiki/UserRegister.body.php
new file mode 100644
index 00000000..f8c953a9
--- /dev/null
+++ b/mediawiki/UserRegister.body.php
@@ -0,0 +1,14 @@
+<?php
+class UserRegister extends WsgiInjectableSpecialPage {
+ function __construct() {
+ parent::__construct( 'UserRegister',
+ '/backend/account/nmr-wiki/signup/',
+ array(0=>'/backend/content/style/mediawiki-login.css'),
+ array(
+ 0=>'/backend/content/js/jquery-1.2.6.min.js',
+ 1=>'/backend/content/js/mediawiki-login.js'
+ )
+ );
+ }
+}
+
diff --git a/mediawiki/UserRegister.i18n.php b/mediawiki/UserRegister.i18n.php
new file mode 100644
index 00000000..2f8d41d0
--- /dev/null
+++ b/mediawiki/UserRegister.i18n.php
@@ -0,0 +1,6 @@
+<?php
+$messages = array();
+
+$messages['en'] = array(
+ 'userregister' => 'Join the Wiki',
+);
diff --git a/mediawiki/UserRegister.php b/mediawiki/UserRegister.php
new file mode 100644
index 00000000..cff0e69d
--- /dev/null
+++ b/mediawiki/UserRegister.php
@@ -0,0 +1,24 @@
+<?php
+# Alert the user that this is not a valid entry point to MediaWiki if they try to access the special pages file directly.
+if (!defined('MEDIAWIKI')) {
+ echo <<<EOT
+Not a valid entry point.
+EOT;
+ exit( 1 );
+}
+
+$wgExtensionCredits['specialpage'][] = array(
+ 'name' => 'User Registration',
+ 'author' => 'Evgeny Fadeev',
+ 'url' => 'none',
+ 'description' => 'Creates new user account for the Wiki and Q&A forum',
+ 'descriptionmsg' => 'people-page-desc',
+ 'version' => '0.0.0',
+);
+
+$dir = dirname(__FILE__) . '/';
+
+$wgAutoloadClasses['UserRegister'] = $dir . 'UserRegister.body.php'; # Tell MediaWiki to load the extension body.
+$wgExtensionMessagesFiles['UserRegister'] = $dir . 'UserRegister.i18n.php';
+$wgExtensionAliasesFiles['UserRegister'] = $dir . 'UserRegister.alias.php';
+$wgSpecialPages['UserRegister'] = 'UserRegister'; # Let MediaWiki know about your new special page.
diff --git a/mediawiki/WsgiInjectableSpecialPage.php b/mediawiki/WsgiInjectableSpecialPage.php
new file mode 100644
index 00000000..d23f22e8
--- /dev/null
+++ b/mediawiki/WsgiInjectableSpecialPage.php
@@ -0,0 +1,80 @@
+<?php
+
+class WsgiInjectableSpecialPage extends SpecialPage {
+ var $default_wsgi_command;
+ function __construct($page_name,$default_wsgi_command,$css='',$scripts='',$wsgi_prefix=''){
+ parent::__construct($page_name);
+ wfLoadExtensionMessages($page_name);
+ $this->default_wsgi_command = $default_wsgi_command;
+ $this->css = $css;
+ $this->scripts = $scripts;
+ $this->wsgi_prefix = $wsgi_prefix;
+ }
+ function execute($par){
+ global $wgWsgiScriptPath, $wgRequest, $wgOut, $wgHeader;
+ $wgWsgiScriptPath = '';
+ if ($this->wsgi_prefix != ''){
+ $wsgi_call = $this->wsgi_prefix;
+ }
+ else {
+ $wsgi_call = $wgWsgiScriptPath;
+ }
+
+ $this->setHeaders();
+
+ if ($this->css != ''){
+ if (is_array($this->css)){
+ foreach($this->css as $css){
+ $wgHeader->addCSS($css);
+ }
+ }
+ else{
+ $wgHeader->addCSS($this->css);
+ }
+ }
+ if ($this->scripts != ''){
+ if (is_array($this->scripts)){
+ foreach($this->scripts as $script){
+ $wgHeader->addScript($script);
+ }
+ }
+ else{
+ $wgHeader->addScript($this->css);
+ }
+ }
+
+ #add command
+ if (!is_null($wgRequest->getVal('command'))){
+ $wsgi_call .= $wgRequest->getVal('command');
+ }
+ else {
+ #why is this not working?
+ $wsgi_call .= $this->default_wsgi_command;
+ }
+ #add session key
+ $session_name = ini_get('session.name');#session_name();
+ $session = '';
+ if (array_key_exists($session_name, $_COOKIE)){
+ $session = $_COOKIE[$session_name];
+ }
+ $wsgi_call .= "?session=${session}";
+
+ #add posted request variables
+ if ($wgRequest->wasPosted()){
+ $data = $wgRequest->data;
+ foreach ($data as $key => $value){
+ if ($key != 'title'){
+ $wsgi_call .= "&${key}=${value}";
+ }
+ }
+ $wsgi_call .= '&was_posted=true';
+ }
+ else {
+ $wsgi_call .= '&was_posted=false';
+ }
+
+ #print the include statement called as GET request
+ $wgOut->addHTML("<!--#include virtual=\"${wsgi_call}\"-->");
+ #$wgOut->addHTML("<!-- ${wsgi_call} -->"); #print this only for debugging
+ }
+}
diff --git a/mediawiki/__init__.py b/mediawiki/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/mediawiki/__init__.py
diff --git a/django_authopenid/external_login.py b/mediawiki/api.py
index bd49c009..912de041 100644
--- a/django_authopenid/external_login.py
+++ b/mediawiki/api.py
@@ -1,6 +1,6 @@
#this file contains stub functions that can be extended to support
#connect legacy login with external site
-import settings
+from django.conf import settings
from django_authopenid.models import ExternalLoginData
import httplib
import urllib
@@ -9,6 +9,7 @@ import cookielib
from django import forms
import xml.dom.minidom as xml
import logging
+from models import User as MWUser
def login(request,user):
"""performs the additional external login operation
@@ -35,11 +36,18 @@ def set_login_cookies(response,user):
c[prefix + 'Token'] = token
c[prefix + '_session'] = sessionid
+ logging.debug('have cookies ' + str(c))
+
#custom code that copies cookies from external site
#not sure how to set paths and domain of cookies here
+ domain = settings.MEDIAWIKI_COOKIE_DOMAIN
for key in c:
if c[key]:
- response.set_cookie(str(key),value=str(c[key]))
+ response.set_cookie(str(key),\
+ value=str(c[key]),\
+ domain=domain)
+ for c in response.cookies.values():
+ logging.debug(c.output())
except ExternalLoginData.DoesNotExist:
#this must be an OpenID login
pass
@@ -62,6 +70,8 @@ def check_password(username,password):
port = settings.EXTERNAL_LEGACY_LOGIN_PORT
ext_site = httplib.HTTPConnection(host,port)
+ print 'connected to %s:%s' % (str(host),str(port))
+
#custom code. this one does authentication through
#MediaWiki API
params = urllib.urlencode({'action':'login','format':'xml',
@@ -76,6 +86,8 @@ def check_password(username,password):
data = response.read().strip()
ext_site.close()
+ print data
+
dom = xml.parseString(data)
login = dom.getElementsByTagName('login')[0]
result = login.getAttribute('result')
@@ -100,4 +112,27 @@ def createuser(username,email,password):
#retrieve email address
def get_email(username,password):
- return ''
+ try:
+ u = MWUser.objects.get(user_name=username)
+ return u.user_email
+ except MWUser.DoesNotExist:
+ return ''
+
+#try to get full name from mediawiki
+def get_screen_name(username,password):
+ try:
+ u = MWUser.objects.get(user_name=username)
+ full_name = u' '.join((u.user_first_name, u.user_last_name)).strip()
+ if full_name != u'':
+ return full_name
+ else:
+ return username
+ except MWUser.DoesNotExist:
+ return username
+
+def connect_local_user_to_external_user(user, login, password):
+ try:
+ u = MWUser.objects.get(user_name=login)
+ user.mediawiki_user = u
+ except MWUser.DoesNotExist:
+ pass
diff --git a/mediawiki/auth.py b/mediawiki/auth.py
new file mode 100644
index 00000000..ee367dc1
--- /dev/null
+++ b/mediawiki/auth.py
@@ -0,0 +1,59 @@
+from mediawiki.models import User as MWUser
+from django.contrib.auth.models import User
+from django_authopenid.models import ExternalLoginData
+from django.conf import settings
+import logging
+from PHPUnserialize import PHPUnserialize
+import os
+
+class php(object):
+ @staticmethod
+ def get_session_data(session):
+ prefix = settings.MEDIAWIKI_PHP_SESSION_PREFIX
+ path = settings.MEDIAWIKI_PHP_SESSION_PATH
+ file = os.path.join(path,prefix + session)
+ #file may not exist
+ data = open(file).read()
+ u = PHPUnserialize()
+ return u.session_decode(data)
+
+class IncludeVirtualAuthenticationBackend(object):
+ def authenticate(self,token=None):
+ logging.debug('authenticating session %s' % token)
+ try:
+ php_session = php.get_session_data(token)
+ #todo: report technical errors to root
+ except:
+ #Fail condition 1. Session data cannot be retrieved
+ logging.debug('session %s cannot be retrieved' % str(token))
+ return None
+ try:
+ name = php_session['wsUserName']
+ id = php_session['wsUserID']
+ except:
+ #Fail condition 2. Data misses keys
+ logging.debug('missing data in session table')
+ return None
+ try:
+ logging.debug('trying to find user %s id=%s in the MW database' % (name,id))
+ wu = MWUser.objects.get(user_name=name,user_id=id)
+ except MWUser.DoesNotExist:
+ #Fail condition 3. User does not match session data
+ logging.debug('could not find wiki user who owns session')
+ return None
+ try:
+ logging.debug('trying to get external login data for mw user %s' % name)
+ eld = ExternalLoginData.objects.get(external_username=name)
+ #update session data and save?
+ return eld.user #may be none!
+ except ExternalLoginData.DoesNotExist:
+ #Fail condition 4. no external login data - user never logged in through django
+ #using the wiki login and password
+ logging.debug('no association found for MW user %s with django user' % name)
+ return None
+
+ def get_user(self, user_id):
+ try:
+ return User.objects.get(pk=user_id)
+ except User.DoesNotExist:
+ return None
diff --git a/mediawiki/forms.py b/mediawiki/forms.py
new file mode 100644
index 00000000..bac39b5a
--- /dev/null
+++ b/mediawiki/forms.py
@@ -0,0 +1,164 @@
+from utils.forms import NextUrlField, UserNameField, UserEmailField, SetPasswordForm
+from django import forms
+from django.forms import ValidationError
+from models import User as MWUser
+from models import TITLE_CHOICES
+from django.contrib.auth.models import User
+from django.utils.translation import ugettext as _
+from django.utils.safestring import mark_safe
+from django.contrib.formtools.wizard import FormWizard
+from forum.forms import EditUserEmailFeedsForm, SimpleEmailSubscribeForm
+from django.forms import ValidationError
+from recaptcha_django import ReCaptchaField
+from utils.forms import StrippedNonEmptyCharField
+from forum.templatetags import extra_tags as forum_extra_tags
+
+#make better translations in your language django.po
+EMAIL_FEED_CHOICES = (
+ ('y',_('okay, let\'s try!')),
+ ('n',_('no OSQA community email please, thanks'))
+)
+
+wiki_account_taken_msg = _('Wiki site already has this account, if it is yours perhaps you can '
+ 'just try to log in with it?<br/>'
+ 'Otherwise, please pick another login name.')
+
+class RegisterForm(SetPasswordForm, SimpleEmailSubscribeForm):
+ login_name = UserNameField(label=_('Login name'), \
+ db_model=MWUser, \
+ db_field='user_name', \
+ error_messages={ \
+ 'required':_('Please enter login name above, it is required for the Wiki site'), \
+ 'taken': mark_safe(wiki_account_taken_msg) \
+ }
+ )
+ next = NextUrlField()
+ email = UserEmailField()
+ screen_name = UserNameField(label=mark_safe(_('Please type your nickname below')), \
+ skip_clean=True, \
+ required=False)
+ first_name = StrippedNonEmptyCharField(max_length=255,label=mark_safe(_('First name')),
+ error_messages={'required':_('First name is required')}
+ )
+ last_name = StrippedNonEmptyCharField(max_length=255,label=_('Last name'),
+ error_messages={'required':_('Last name is required')}
+ )
+ #cannot be just "title" because there would be a conflict with "title" field used for MW!!!
+ user_title = forms.ChoiceField(choices=TITLE_CHOICES, label=_('Title (optional)'))
+ use_separate_screen_name = forms.BooleanField(
+ label=mark_safe(_('I prefer (or have to) to use a separate forum screen name')),
+ required=False,
+ )
+ #subscribe = forms.ChoiceField(widget=forms.widgets.RadioSelect, \
+ # error_messages={'required':_('please choose one of the options above')},
+ # choices= EMAIL_FEED_CHOICES)
+ recaptcha = ReCaptchaField()
+
+ class Media:
+ css={'all':(forum_extra_tags.href('/content/style/mediawiki-login.css'),),}
+ js=(forum_extra_tags.href('/content/js/mediawiki-login.js'),)
+
+ def add_screen_name_error(self, err):
+ if 'screen_name' in self.cleaned_data:
+ del self.cleaned_data['screen_name']
+ error_list = self._errors.get('screen_name',forms.util.ErrorList([]))
+ if isinstance(err, forms.util.ErrorList):
+ error_list.extend(err)
+ else:
+ error_list.append(err)
+ self._errors['screen_name'] = error_list
+
+ def clean(self):
+ #this method cleans screen_name and use_separate_screen_name
+ screen_name = self.cleaned_data.get('screen_name', '')
+
+ if 'use_separate_screen_name' in self.cleaned_data \
+ and self.cleaned_data['use_separate_screen_name']:
+ if screen_name == '':
+ msg = _('please enter an alternative screen name or uncheck the box above')
+ self.add_screen_name_error(msg)
+ else:
+ try:
+ screen_name = self.fields['screen_name'].clean(screen_name)
+ self.final_clean_screen_name(screen_name)
+ except ValidationError, e:
+ self.add_screen_name_error(e)
+ else:
+ if screen_name != '':
+ self.add_screen_name_error(_('sorry, to use alternative screen name, please confirm it by checking the box above'))
+ else:
+ #build screen name from first and last names
+ first = self.cleaned_data.get('first_name',None)
+ last = self.cleaned_data.get('last_name',None)
+ if first and last:
+ screen_name = u'%s %s' % (first,last)
+ self.final_clean_screen_name(screen_name)
+ return self.cleaned_data
+
+ def final_clean_screen_name(self,name):
+ try:
+ u = User.objects.get(username=name)
+ msg = _('Screen name <strong>%(real_name)s</strong> is somehow already taken on the forum.<br/>'
+ 'Unfortunately you will have to pick a separate screen name, but of course '
+ 'there is no need to change the first name and last name entries.<br/>'
+ 'Please send us your feedback if you feel there might be a mistake. '
+ 'Sorry for the inconvenience.')\
+ % {'real_name':name}
+ self.add_screen_name_error(mark_safe(msg))
+ except:
+ self.cleaned_data['screen_name'] = name
+
+ #overridden validation for UserNameField
+ def clean_login_name(self):
+ try:
+ MWUser.objects.get(user_name=self.cleaned_data['login_name'])
+ del self.cleaned_data['login_name']
+ raise ValidationError(_('sorry this login name is already taken, please try another'))
+ except:
+ return self.cleaned_data['login_name']
+
+class RegisterFormWizard(FormWizard):
+ def done(self, request, form_list):
+ data = form_list[0].cleaned_data
+ login_name = data['login_name']
+ password = data['password']
+ first_name = data['first_name']
+ last_name = data['last_name']
+ screen_name = data['screen_name']
+ email = data['email']
+ subscribe = data['subscribe']
+ next = data['next']
+
+ #register mediawiki user
+ mwu = MWUser(
+ user_name=login_name,
+ user_password=password,
+ user_first_name = first_name,
+ user_last_name = last_name,
+ user_email = email
+ )
+ mwu.save()
+
+ #register local user
+ User.objects.create_user(screen_name, email, password)
+ u = authenticate(username=screen_name, password=password)
+ u.mediawiki_user = mwu
+ u.save()
+
+ #save email feed settings
+ EFF = EditUserEmailFeedsForm
+ if subscribe == 'y':
+ email_settings_form = EFF()
+ else:
+ email_settings_form = EFF(initial=EFF.NO_EMAIL_INITIAL)
+ email_settings_form.save(u)
+
+ #create welcome message
+ u.message_set.create(message=_('Welcome to Q&A forum!'))
+ return HttpResponseRedirect(next)
+
+ def get_template(self, step):
+ if step == 0:
+ return 'mediawiki/mediawiki_signup.html'
+ elif step == 1:
+ return 'notarobot.html'
diff --git a/mediawiki/junk.py b/mediawiki/junk.py
new file mode 100644
index 00000000..e67d492a
--- /dev/null
+++ b/mediawiki/junk.py
@@ -0,0 +1,2 @@
+def junk():
+ pass
diff --git a/mediawiki/middleware.py b/mediawiki/middleware.py
new file mode 100644
index 00000000..a46f486a
--- /dev/null
+++ b/mediawiki/middleware.py
@@ -0,0 +1,57 @@
+from django.contrib import auth
+from django.core.exceptions import ImproperlyConfigured
+from django.conf import settings
+import logging
+import traceback
+import sys
+
+class IncludeVirtualAuthenticationMiddleware(object):
+ def process_request(self,request):
+ """in this type of authentication the mw session token is passed via
+ "session" request parameter and authentication happens on every
+ request
+ """
+ logging.debug('trying include virtual milldeware')
+ if not hasattr(request,'user'):
+ raise ImproperlyConfigured(
+ "The include virtual mediawiki authentication middleware requires the"
+ " authentication middleware to be installed. Edit your"
+ " MIDDLEWARE_CLASSES setting to insert"
+ " 'django.contrib.auth.middleware.AuthenticationMiddleware'"
+ " before the IncludeVirtualAuthenticationMiddleware class."
+ )
+
+ session = None
+ request.is_include_virtual = False
+ if request.is_ajax():
+ logging.debug('have ajax request')
+ cookie_name = settings.MEDIAWIKI_SESSION_COOKIE_NAME
+ if cookie_name in request.COOKIES:
+ session = request.COOKIES[cookie_name]
+ logging.debug('ajax call has session %s' % session)
+ else:
+ logging.debug('dont have cookie')
+ else:
+ if request.REQUEST.has_key('session'):
+ session = request.REQUEST['session']
+ request.is_include_virtual = True
+ logging.debug('I am virtual')
+ if request.REQUEST.get('was_posted','false') == 'true':
+ data = request.GET.copy()
+ data['recaptcha_ip_field'] = request.META['REMOTE_ADDR']
+ request.GET = data
+ logging.debug('REQUEST is now %s' % str(request.GET))
+ user = auth.authenticate(token=session) #authenticate every time
+ if user:
+ request.user = user
+ auth.login(request,user)
+ #else I probably need to forbid access
+ #raise ImproperlyConfigured(
+ # "The include virtual mediawiki authentication middleware requires the"
+ # "'session' request parameter set in the including document"
+ #)
+
+ def process_exception(self,request,exception):
+ exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
+ logging.debug('\n'.join(traceback.format_tb(exceptionTraceback)))
+ logging.debug('have exception %s %s' % (exceptionType,exceptionValue))
diff --git a/mediawiki/models.py b/mediawiki/models.py
new file mode 100644
index 00000000..e37aec32
--- /dev/null
+++ b/mediawiki/models.py
@@ -0,0 +1,312 @@
+# This is an auto-generated Django model module.
+# You'll have to do the following manually to clean this up:
+# * Rearrange models' order
+# * Make sure each model has one field with primary_key=True
+# Feel free to rename the models, but don't rename db_table values or field names.
+#
+# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'
+# into your database.
+
+table_prefix = u'nmrwiki'
+from django.db import models
+import re
+from django.conf import settings
+import logging
+from django.contrib.auth.models import User as DjangoUser
+from django.utils.translation import ugettext as _
+import hashlib
+import time
+import random
+
+MW_TS = '%Y%m%d%H%M%S'
+
+TITLE_CHOICES = (
+ ('none',_('----')),
+ ('prof',_('Prof.')),
+ ('dr',_('Dr.')),
+)
+
+class User(models.Model):
+ user_id = models.IntegerField(primary_key=True,db_column='user_id')
+ user_name = models.CharField(max_length=765)
+ user_real_name = models.CharField(max_length=765)
+ user_password = models.TextField()
+ user_newpassword = models.TextField()
+ user_newpass_time = models.CharField(max_length=14, blank=True)
+ user_email = models.TextField()
+ user_options = models.TextField()
+ user_touched = models.CharField(max_length=14)
+ user_token = models.CharField(max_length=32)
+ user_email_authenticated = models.CharField(max_length=14, blank=True)
+ user_email_token = models.CharField(max_length=32, blank=True)
+ user_email_token_expires = models.CharField(max_length=14, blank=True)
+ user_registration = models.CharField(max_length=14, blank=True)
+ user_editcount = models.IntegerField(null=True, blank=True)
+ user_last_name = models.CharField(max_length=765, blank=True)
+ user_first_name = models.CharField(max_length=765, blank=True)
+ user_reason_to_join = models.CharField(max_length=765, blank=True)
+ user_title = models.CharField(max_length=16, blank=True, choices=TITLE_CHOICES)
+ class Meta:
+ db_table = table_prefix + u'user'
+ managed = False
+
+ def set_default_options(self):
+ default_options = {
+ 'quickbar':1,
+ 'underline':2,
+ 'cols':80,
+ 'rows':25,
+ 'searchlimit':20,
+ 'contextlines':5,
+ 'contextchars':50,
+ 'skin':'false',
+ 'math':1,
+ 'rcdays':7,
+ 'rclimit':50,
+ 'wllimit':250,
+ 'highlightbroken':1,
+ 'stubthreshold':0,
+ 'previewontop':1,
+ 'editsection':1,
+ 'editsectiononrightclick':0,
+ 'showtoc':1,
+ 'showtoolbar':1,
+ 'date':'default',
+ 'imagesize':2,
+ 'thumbsize':2,
+ 'rememberpassword':0,
+ 'enotifwatchlistpages':0,
+ 'enotifusertalkpages':1,
+ 'enotifminoredits':0,
+ 'enotifrevealaddr':0,
+ 'shownumberswatching':1,
+ 'fancysig':0,
+ 'externaleditor':0,
+ 'externaldiff':0,
+ 'showjumplinks':1,
+ 'numberheadings':0,
+ 'uselivepreview':0,
+ 'watchlistdays':3.0,
+ 'usenewrc':1,
+ }
+ self.user_options = '\n'.join(
+ map(lambda opt: '%s=%s' % (opt[0], str(opt[1])),
+ default_options.items())
+ )
+
+ def set_password_and_token(self,password):
+ p = hashlib.md5(password).hexdigest()
+ if hasattr(settings,'MEDIAWIKI_SALT_PASSWORD') and settings.MEDIAWIKI_SALT_PASSWORD == True:
+ p = hashlib.md5('%d-%s' % (self.user_id, p)).hexdigest()
+ self.user_password = p
+ self.user_token = hashlib.md5(p + str(time.time())).hexdigest()
+
+ def get_name(self):
+ if self.user_real_name:
+ if re.search(r'\S',self.user_real_name):
+ return self.user_real_name
+ return self.user_name + ' (nickname)'
+
+ def get_html(self):
+ return '<a href="%s">%s</a>' % (self.get_absolute_url(),self.get_name())
+
+ def get_absolute_url(self):
+ url = settings.MEDIAWIKI_URL + '?title=User:' + self.user_name
+ return url
+
+class UserProfile(models.Model):
+ nup_user_id = models.ForeignKey(User,primary_key=True,db_column='nup_user_id')
+ nup_about = models.CharField(max_length=765, blank=True)
+ nup_position_title = models.CharField(max_length=765, blank=True)
+ nup_position_type = models.CharField(max_length=765, blank=True)
+ nup_employer_division = models.CharField(max_length=765, blank=True)
+ nup_employer_company = models.CharField(max_length=765, blank=True)
+ nup_employer_type = models.CharField(max_length=765, blank=True)
+ nup_employment_status = models.CharField(max_length=45, blank=True)
+ nup_profession = models.CharField(max_length=765, blank=True)
+ nup_city = models.CharField(max_length=765, blank=True)
+ nup_state = models.CharField(max_length=765, blank=True)
+ nup_country = models.CharField(max_length=765, blank=True)
+ nup_lattitude = models.FloatField(null=True, blank=True)
+ nup_longitude = models.FloatField(null=True, blank=True)
+ nup_hiring = models.IntegerField(null=True, blank=True)
+ nup_hunting = models.IntegerField(null=True, blank=True)
+ nup_education = models.TextField(blank=True)
+ nup_websites = models.TextField(blank=True)
+ nup_interests = models.TextField(blank=True)
+ nup_job_ad = models.TextField(blank=True)
+ nup_job_ad_title = models.CharField(max_length=765, blank=True)
+ nup_job_ad_active = models.IntegerField(null=True, blank=True)
+ nup_expertise = models.TextField(blank=True)
+ nup_is_approved = models.BooleanField()
+ class Meta:
+ db_table = table_prefix + u'new_user_profile'
+ managed = False
+
+class RecentChanges(models.Model):
+ rc_id = models.AutoField(primary_key=True, db_column='rc_id')
+ rc_timestamp = models.CharField(max_length=14)
+ rc_cur_time = models.CharField(max_length=14)
+ rc_user = models.ForeignKey(User, db_column='rc_user')
+ rc_user_text = models.CharField(max_length=765)
+ rc_namespace = models.IntegerField()
+ rc_title = models.CharField(max_length=765)
+ rc_comment = models.CharField(max_length=765)
+ rc_minor = models.IntegerField()
+ rc_bot = models.IntegerField()
+ rc_new = models.IntegerField()
+ rc_cur_id = models.IntegerField()
+ rc_this_oldid = models.IntegerField()
+ rc_last_oldid = models.IntegerField()
+ rc_type = models.IntegerField()
+ rc_moved_to_ns = models.IntegerField()
+ rc_moved_to_title = models.CharField(max_length=765)
+ rc_patrolled = models.IntegerField()
+ rc_ip = models.CharField(max_length=40)
+ rc_old_len = models.IntegerField(null=True, blank=True)
+ rc_new_len = models.IntegerField(null=True, blank=True)
+ rc_deleted = models.IntegerField()
+ rc_logid = models.ForeignKey('Logging', db_column='rc_logid')
+ rc_log_type = models.CharField(max_length=255, blank=True)
+ rc_log_action = models.CharField(max_length=255, blank=True)
+ rc_params = models.TextField(blank=True)
+ class Meta:
+ db_table = table_prefix + u'recentchanges'
+ managed = False
+
+class Logging(models.Model):
+ log_id = models.AutoField(primary_key=True)
+ log_type = models.CharField(max_length=10)
+ log_action = models.CharField(max_length=10)
+ log_timestamp = models.CharField(max_length=14)
+ log_user = models.ForeignKey(User,db_column='log_user')
+ log_namespace = models.IntegerField()
+ log_title = models.CharField(max_length=765)
+ log_comment = models.CharField(max_length=765)
+ log_params = models.TextField()
+ log_deleted = models.IntegerField()
+ class Meta:
+ db_table = table_prefix + u'logging'
+ managed = False
+
+ def show_in_recent_changes(self, ip=None, rc_minor=False):
+ #to call this method self object must already exist in DB
+ if self.log_type == 'newusers' and self.log_action=='create':
+ rc = RecentChanges(
+ rc_ip=ip,
+ rc_minor=int(rc_minor),
+ rc_deleted=0,
+ rc_bot=0,
+ rc_new=0,
+ rc_moved_to_title='',
+ rc_moved_to_ns=0,
+ rc_this_oldid=0,
+ rc_last_oldid=0,
+ rc_patrolled=1,
+ rc_old_len=None,
+ rc_new_len=None,
+ rc_logid=self,
+ rc_user=self.log_user,
+ rc_user_text=self.log_user.user_name,
+ rc_log_type=self.log_type,
+ rc_log_action=self.log_action,
+ rc_timestamp = self.log_timestamp,
+ rc_cur_time = self.log_timestamp,
+ rc_title='Log/newusers',
+ rc_namespace=-1, #-1 special, 2 is User namespace
+ rc_params=self.log_params,
+ rc_comment=_('Welcome new user!'),
+ rc_type=3,#MW RCLOG constant from Defines.php
+ rc_cur_id=0,
+ )
+ rc.save()
+ else:
+ raise NotImplementedError()
+
+
+class Page(models.Model):
+ page_id = models.AutoField(primary_key=True)
+ page_namespace = models.IntegerField(unique=True)
+ page_title = models.CharField(max_length=765)
+ page_restrictions = models.TextField()
+ page_counter = models.IntegerField()
+ page_is_redirect = models.IntegerField()
+ page_is_new = models.IntegerField()
+ page_random = models.FloatField()
+ page_touched = models.CharField(max_length=14)
+ page_latest = models.IntegerField()
+ page_len = models.IntegerField()
+ class Meta:
+ db_table = table_prefix + u'page'
+ managed = False
+ def save(self):
+ raise Exception('WikiUser table is read-only in this application')
+
+class PageLinks(models.Model):
+ pl_from = models.ForeignKey(Page)
+ pl_namespace = models.IntegerField()
+ pl_title = models.CharField(max_length=765)
+ class Meta:
+ db_table = table_prefix + u'pagelinks'
+ managed = False
+ def save(self):
+ raise Exception('WikiUser table is read-only in this application')
+
+class Revision(models.Model):
+ rev_id = models.IntegerField(unique=True)
+ rev_page = models.IntegerField()
+ rev_text_id = models.IntegerField()
+ rev_comment = models.TextField()
+ rev_user = models.IntegerField()
+ rev_user_text = models.CharField(max_length=765)
+ rev_timestamp = models.CharField(max_length=14)
+ rev_minor_edit = models.IntegerField()
+ rev_deleted = models.IntegerField()
+ rev_len = models.IntegerField(null=True, blank=True)
+ rev_parent_id = models.IntegerField(null=True, blank=True)
+ class Meta:
+ db_table = table_prefix + u'revision'
+ managed = False
+
+class Text(models.Model):
+ old_id = models.IntegerField(primary_key=True)
+ old_text = models.TextField()
+ old_flags = models.TextField()
+ class Meta:
+ db_table = table_prefix + u'text'
+ managed = False
+
+#nmrwiki_stats table may be of interest
+
+class UserGroups(models.Model):
+ ug_user = models.ForeignKey(User,primary_key=True)
+ ug_group = models.CharField(max_length=16)
+ class Meta:
+ db_table = table_prefix + u'user_groups'
+ managed = False
+
+def user_get_absolute_url(user):
+ return user.mediawiki_user.get_absolute_url()
+
+def user_get_html(user):
+ return user.mediawiki_user.get_html()
+
+def user_has_valid_email(user):
+ if user.mediawiki_user.user_email_authenticated:
+ return True
+ else:
+ return False
+
+def user_get_description_for_admin(user):
+ out = user.get_html() + ' (%s)' % user.username
+ if user.has_valid_email():
+ out += ' - has valid email'
+ else:
+ out += ' - <em>no email!</em>'
+ return out
+
+DjangoUser.add_to_class('mediawiki_user',models.ForeignKey(User, null=True))
+DjangoUser.add_to_class('get_wiki_profile_url',user_get_absolute_url)
+DjangoUser.add_to_class('get_wiki_profile_url_html',user_get_html)
+DjangoUser.add_to_class('get_description_for_admin',user_get_description_for_admin)
+DjangoUser.add_to_class('has_valid_wiki_email',user_has_valid_email)
diff --git a/mediawiki/templatetags/__init__.py b/mediawiki/templatetags/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/mediawiki/templatetags/__init__.py
diff --git a/mediawiki/templatetags/mediawikitags.py b/mediawiki/templatetags/mediawikitags.py
new file mode 100644
index 00000000..b21789f4
--- /dev/null
+++ b/mediawiki/templatetags/mediawikitags.py
@@ -0,0 +1,62 @@
+from django import template
+from django.template.defaultfilters import stringfilter
+from django.conf import settings
+import logging
+
+register = template.Library()
+
+#template tags
+class MWPluginFormActionNode(template.Node):
+ def __init__(self, wiki_page, form_action):
+ self.form_action = ''.join(form_action[1:-1])
+ self.wiki_page = ''.join(wiki_page[1:-1])
+ def render(self, context):
+ out = ('<input type="hidden" name="title" value="%s"/>' \
+ + '<input type="hidden" name="command" value="%s"/>') \
+ % (self.wiki_page, self.form_action)
+ return out
+
+def curry_up_to_two_argument_tag(TagNodeClass):
+ def do_the_action_func(parser,token):
+ args = token.split_contents()
+ if len(args) > 3:
+ tagname = token.contents.split()[0]
+ raise template.TemplateSyntaxError, \
+ '%s tag requires two arguments or less' % tagname
+ if len(args) > 1:
+ argument1 = ''.join(args[1][1:-1])
+ else:
+ argument1 = None
+ if len(args) == 3:
+ argument2 = ''.join(args[2][1:-1])
+ else:
+ argument2 = None
+ return TagNodeClass(argument1, argument2)
+ return do_the_action_func
+
+def do_mw_plugin_form_action(parser,token):
+ args = token.split_contents()
+ if len(args) != 3:
+ tagname = token.contents.split()[0]
+ raise template.TemplateSyntaxError, \
+ '%s tag requires two arguments' % tagname
+ return MWPluginFormActionNode(args[1],args[2])
+
+class MediaWikiPluginUrlNode(template.Node):
+ """will return either wiki url, a particular page url
+ or a page with command argument to be interpreted by the plugin
+ """
+ def __init__(self,wiki_page=None,url=None):
+ self.url = url
+ self.wiki_page = wiki_page
+ def render(self,context):
+ title_token = '?title=%s' % self.wiki_page
+ cmd_token = '&command=%s' % self.url
+ if self.wiki_page == None:
+ return settings.MEDIAWIKI_URL
+ if self.url == None:
+ return settings.MEDIAWIKI_URL + title_token
+ return settings.MEDIAWIKI_URL + title_token + cmd_token
+
+register.tag('mw_plugin_form_action',do_mw_plugin_form_action)
+register.tag('mw_plugin_url',curry_up_to_two_argument_tag(MediaWikiPluginUrlNode))
diff --git a/mediawiki/views.py b/mediawiki/views.py
new file mode 100644
index 00000000..012d6f42
--- /dev/null
+++ b/mediawiki/views.py
@@ -0,0 +1,192 @@
+#this file contains stub functions that can be extended to support
+#connect legacy login with external site
+#from django import forms
+import time
+from models import User as MWUser
+from models import Logging
+from models import MW_TS
+import api
+from django.shortcuts import render_to_response
+from django.utils.translation import ugettext as _
+from django.template import RequestContext
+from django.http import HttpResponseRedirect
+from forms import RegisterForm
+from forum.forms import SimpleEmailSubscribeForm
+from forum.models import Question
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate, login
+from django.http import HttpResponseRedirect
+from django.db import transaction
+from django_authopenid.models import ExternalLoginData
+from django_authopenid.views import not_authenticated
+from django.template import loader
+from django.core.mail import send_mail
+from django.conf import settings
+from django.utils.safestring import mark_safe
+import hashlib
+import random
+
+#not a view, but uses request and templates
+def send_welcome_email(request, wiki_user, django_user):
+ random.seed()
+ confirmation_token = '%032x' % random.getrandbits(128)
+ wiki_user.user_email_token = hashlib.md5(confirmation_token).hexdigest()
+ wiki_user.user_email_token_expires = time.strftime(MW_TS,(time.gmtime(time.time() + 7*24*60*60)))
+ wiki_user.save()
+
+ link = 'http://' + settings.EXTERNAL_LEGACY_LOGIN_HOST \
+ + settings.MEDIAWIKI_INDEX_PHP_URL \
+ + '?title=Special:Confirmemail/' \
+ + confirmation_token
+
+ pw_link = 'http://' + settings.EXTERNAL_LEGACY_LOGIN_HOST \
+ + settings.MEDIAWIKI_INDEX_PHP_URL \
+ + '?title=Password_recovery'
+
+ if wiki_user.user_title == 'prof':
+ template_name = 'mediawiki/welcome_professor_email.txt'
+ else:
+ template_name = 'mediawiki/welcome_email.txt'
+ t = loader.get_template(template_name)
+
+ data = {
+ 'email_confirmation_url':mark_safe(link),
+ 'admin_email':settings.DEFAULT_FROM_EMAIL,
+ 'first_name':wiki_user.user_first_name,
+ 'last_name':wiki_user.user_last_name,
+ 'login_name':wiki_user.user_name,
+ 'title':wiki_user.user_title,
+ 'user_email':wiki_user.user_email,
+ 'forum_screen_name':django_user.username,
+ 'password_recovery_url':mark_safe(pw_link),
+ }
+ body = t.render(RequestContext(request,data))
+ if wiki_user.user_title in ('prof','dr'):
+ subject = _('%(title)s %(last_name)s, welcome to the OSQA online community!') \
+ % {'title':wiki_user.get_user_title_display(),'last_name':wiki_user.user_last_name }
+ else:
+ subject = _('%(first_name)s, welcome to the OSQA online community!') \
+ % {'first_name':wiki_user.user_first_name}
+ from_email = settings.DEFAULT_FROM_EMAIL
+ send_mail(subject,body,from_email,[wiki_user.user_email])
+
+@transaction.commit_manually
+def signup(request):
+ #this view works through forum and mediawiki (using apache include virtual injection)
+ if request.is_include_virtual and request.REQUEST.get('was_posted','false')=='true':
+ POST_INCLUDE_VIRTUAL = True
+ POST_DATA = request.GET
+ else:
+ POST_INCLUDE_VIRTUAL = False
+ if request.method == 'POST':
+ POST_DATA = request.POST
+ else:
+ POST_DATA = None
+
+ if POST_DATA:
+ form = RegisterForm(POST_DATA)
+ if form.is_valid():
+ data = form.cleaned_data
+ login_name = data['login_name']
+ password = data['password']
+ first_name = data['first_name']
+ last_name = data['last_name']
+ screen_name = data['screen_name']
+ user_title = data['user_title']
+ email = data['email']
+ next = data['next']
+
+ #register mediawiki user
+ user_real_name = u'%s %s' % (first_name,last_name)
+ mwu = MWUser(
+ user_name=login_name,
+ user_first_name = first_name,
+ user_last_name = last_name,
+ user_title = user_title,
+ user_email = email,
+ user_real_name=user_real_name
+ )
+ mwu.set_default_options()
+ mwu.save()
+ #password may need user id so reload it
+ mwu = MWUser.objects.get(user_name = login_name)
+ mwu.set_password_and_token(password)
+ mwu.save()
+
+ #create log message
+ mwu_creation_log = Logging(
+ log_type='newusers',
+ log_action='create',
+ log_timestamp=time.strftime(MW_TS),
+ log_params=str(mwu.user_id),
+ log_namespace=2,
+ log_user=mwu,
+ log_deleted=0,
+ )
+ mwu_creation_log.save()
+ mwu_creation_log.show_in_recent_changes(ip=request.META['REMOTE_ADDR'])
+ print 'creation log saved'
+
+ #register local user
+ User.objects.create_user(screen_name, email, password)
+ u = authenticate(username=screen_name, password=password)
+ login(request,u)
+ u.mediawiki_user = mwu
+ u.save()
+
+ #save email feed settings
+ subscribe = SimpleEmailSubscribeForm(POST_DATA)
+ if subscribe.is_valid():
+ subscribe.save(user=u)
+
+ #save external login data
+ eld = ExternalLoginData(external_username=login_name, user=u)
+ eld.save()
+
+ transaction.commit()#commit so that user becomes visible on the wiki side
+
+ #check password through API and load MW HTTP header session data
+ api.check_password(login_name,password)
+
+ print 'wiki login worked'
+
+ #create welcome message on the forum
+ u.message_set.create(message=_('Welcome to the OSQA community!'))
+ print 'about to send confirmation email'
+ send_welcome_email(request, mwu, u)
+
+ if POST_INCLUDE_VIRTUAL:
+ questions = Question.objects.exclude(deleted=True, closed=True, answer_accepted=True)
+ questions = questions.order_by('-last_activity_at')[:5]
+ response = render_to_response('mediawiki/thanks_for_joining.html', \
+ {
+ 'wiki_user':mwu,
+ 'user':u,
+ 'questions':questions,
+ },
+ context_instance = RequestContext(request))
+ api.set_login_cookies(response, u)
+ #call session middleware now to get the django login cookies
+ from django.contrib.sessions.middleware import SessionMiddleware
+ sm = SessionMiddleware()
+ response = sm.process_response(request,response)
+ cookies = response.cookies
+ for c in cookies.values():
+ response.write(c.js_output())
+ else:
+ response = HttpResponseRedirect(next)
+ api.set_login_cookies(response, u)
+
+ #set cookies so that user is logged in in the wiki too
+ transaction.commit()
+ return response
+ else:
+ form = RegisterForm()
+
+ transaction.commit()
+ if request.is_include_virtual:
+ template_name = 'mediawiki/mediawiki_signup_content.html'
+ else:
+ template_name = 'mediawiki/mediawiki_signup.html'
+ return render_to_response(template_name,{'form':form},\
+ context_instance=RequestContext(request))
diff --git a/middleware/anon_user.py b/middleware/anon_user.py
index 8422d89b..fa2686f0 100644
--- a/middleware/anon_user.py
+++ b/middleware/anon_user.py
@@ -1,8 +1,8 @@
from django.http import HttpResponseRedirect
-from django_authopenid.util import get_next_url
+from utils.forms import get_next_url
from django.utils.translation import ugettext as _
from user_messages import create_message, get_and_delete_messages
-import settings
+from django.conf import settings
import logging
class AnonymousMessageManager(object):
diff --git a/middleware/cancel.py b/middleware/cancel.py
index f03ff35e..51e1b253 100644
--- a/middleware/cancel.py
+++ b/middleware/cancel.py
@@ -1,5 +1,5 @@
from django.http import HttpResponseRedirect
-from django_authopenid.util import get_next_url
+from utils.forms import get_next_url
import logging
class CancelActionMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
diff --git a/settings.py b/settings.py
index e2e97cb1..e45d4780 100755
--- a/settings.py
+++ b/settings.py
@@ -14,19 +14,22 @@ TEMPLATE_LOADERS = (
# 'django.template.loaders.eggs.load_template_source',
)
-MIDDLEWARE_CLASSES = (
- 'django.middleware.gzip.GZipMiddleware',
+MIDDLEWARE_CLASSES = [
+ #'django.middleware.gzip.GZipMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
#'django.middleware.locale.LocaleMiddleware',
+ #'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
+ #'django.middleware.cache.FetchFromCacheMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.middleware.transaction.TransactionMiddleware',
#'django.middleware.sqlprint.SqlPrintingMiddleware',
'middleware.anon_user.ConnectToSessionMessagesMiddleware',
'middleware.pagesize.QuestionsPageSizeMiddleware',
'middleware.cancel.CancelActionMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
-)
+ 'recaptcha_django.middleware.ReCaptchaMiddleware',
+ 'django.middleware.transaction.TransactionMiddleware',
+]
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
@@ -52,7 +55,10 @@ ALLOW_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
# unit byte
ALLOW_MAX_FILE_SIZE = 1024 * 1024
-INSTALLED_APPS = (
+# User settings
+from settings_local import *
+
+INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
@@ -62,11 +68,27 @@ INSTALLED_APPS = (
'django.contrib.sitemaps',
'forum',
'django_authopenid',
- #'djangosphinx',
'debug_toolbar' ,
'user_messages',
- 'fbconnect',
-)
+]
-# User settings
-from settings_local import *
+AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend',]
+
+if check_local_setting('USE_SPHINX_SEARCH',True):
+ INSTALLED_APPS.append('djangosphinx')
+
+if check_local_setting('USE_FB_CONNECT',True):
+ INSTALLED_APPS.append('fbconnect')
+
+#load optional plugin module for external password login
+if 'USE_EXTERNAL_LEGACY_LOGIN' in locals() and USE_EXTERNAL_LEGACY_LOGIN:
+ INSTALLED_APPS.append(EXTERNAL_LEGACY_LOGIN_MODULE)
+
+ if 'EXTERNAL_LEGACY_LOGIN_AUTHENTICATION_BACKEND' in locals():
+ AUTHENTICATION_BACKENDS.append(EXTERNAL_LEGACY_LOGIN_AUTHENTICATION_BACKEND)
+ if 'EXTERNAL_LEGACY_LOGIN_AUTHENTICATION_MIDDLEWARE' in locals():
+ MIDDLEWARE_CLASSES.append(EXTERNAL_LEGACY_LOGIN_AUTHENTICATION_MIDDLEWARE)
+ def LOAD_EXTERNAL_LOGIN_APP():
+ return __import__(EXTERNAL_LEGACY_LOGIN_MODULE, [], [], ['api','forms','views'])
+else:
+ LOAD_EXTERNAL_LOGIN_APP = lambda: None
diff --git a/settings_local.py.dist b/settings_local.py.dist
index 443b73d1..f271a0b0 100755
--- a/settings_local.py.dist
+++ b/settings_local.py.dist
@@ -2,6 +2,13 @@
import os.path
from django.utils.translation import ugettext as _
+def check_local_setting(name, value):
+ local_vars = locals()
+ if name in local_vars and local_var[name] == value:
+ return True
+ else:
+ return False
+
SITE_SRC_ROOT = os.path.dirname(__file__)
LOG_FILENAME = 'django.osqa.log'
@@ -87,6 +94,11 @@ SPHINX_SEARCH_INDICES=('osqa',) #a tuple of index names remember about a comma a
SPHINX_SERVER='localhost'
SPHINX_PORT=3312
+#please get these at recaptcha.net
+RECAPTCHA_PRIVATE_KEY='...'
+RECAPTCHA_PUBLIC_KEY='...'
+
#Facebook settings
+USE_FB_CONNECT=True
FB_API_KEY='' #your api key from facebook
FB_SECRET='' #your application secret
diff --git a/sql_scripts/100108_upgrade_ef.sql b/sql_scripts/100108_upgrade_ef.sql
new file mode 100644
index 00000000..1c9a5c1c
--- /dev/null
+++ b/sql_scripts/100108_upgrade_ef.sql
@@ -0,0 +1,4 @@
+alter table auth_user add column hide_ignored_questions tinyint(1) not NULL;
+update auth_user set hide_ignored_questions=0;
+alter table auth_user add column tag_filter_setting varchar(16) not NULL;
+update auth_user set tag_filter_setting='ignored';
diff --git a/sql_scripts/badges.sql b/sql_scripts/badges.sql
new file mode 100644
index 00000000..f47e067a
--- /dev/null
+++ b/sql_scripts/badges.sql
@@ -0,0 +1,37 @@
+INSERT INTO `badge` ( `id`, `name`, `type`, `slug`, `description`, `multiple`, `awarded_count`) VALUES
+(1, 'Disciplined', 3, 'disciplined', 'Deleted own post with score of 3 or higher', 1, 0),
+(2, 'Peer Pressure', 3, 'peer-pressure', 'Deleted own post with score of -3 or lower', 1, 0),
+(3, 'Nice answer', 3, 'nice-answer', 'Answer voted up 10 times', 1, 0),
+(4, 'Nice Question', 3, 'nice-question', 'Question voted up 10 times', 1, 0),
+(5, 'Pundit', 3, 'pundit', 'Left 10 comments with score of 10 or more', 0, 0),
+(6, 'Popular Question', 3, 'popular-question', 'Asked a question with 1,000 views', 1, 0),
+(7, 'Citizen patrol', 3, 'citizen-patrol', 'First flagged post', 0, 0),
+(8, 'Cleanup', 3, 'cleanup', 'First rollback', 0, 0),
+(9, 'Critic', 3, 'critic', 'First down vote', 0, 0),
+(10, 'Editor', 3, 'editor', 'First edit', 0, 0),
+(11, 'Organizer', 3, 'organizer', 'First retag', 0, 0),
+(12, 'Scholar', 3, 'scholar', 'First accepted answer on your own question', 0, 0),
+(13, 'Student', 3, 'student', 'Asked first question with at least one up vote', 0, 0),
+(14, 'Supporter', 3, 'supporter', 'First up vote', 0, 0),
+(15, 'Teacher', 3, 'teacher', 'Answered first question with at least one up vote', 0, 0),
+(16, 'Autobiographer', 3, 'autobiographer', 'Completed all user profile fields', 0, 0),
+(17, 'Self-Learner', 3, 'self-learner', 'Answered your own question with at least 3 up votes', 1, 0),
+(18, 'Great Answer', 1, 'great-answer', 'Answer voted up 100 times', 1, 0),
+(19, 'Great Question', 1, 'great-question', 'Question voted up 100 times', 1, 0),
+(20, 'Stellar Question', 1, 'stellar-question', 'Question favorited by 100 users', 1, 0),
+(21, 'Famous question', 1, 'famous-question', 'Asked a question with 10,000 views', 1, 0),
+(22, 'Alpha', 2, 'alpha', 'Actively participated in the private alpha', 0, 0),
+(23, 'Good Answer', 2, 'good-answer', 'Answer voted up 25 times', 1, 0),
+(24, 'Good Question', 2, 'good-question', 'Question voted up 25 times', 1, 0),
+(25, 'Favorite Question', 2, 'favorite-question', 'Question favorited by 25 users', 1, 0),
+(26, 'Civic duty', 2, 'civic-duty', 'Voted 300 times', 0, 0),
+(27, 'Strunk & White', 2, 'strunk-and-white', 'Edited 100 entries', 0, 0),
+(28, 'Generalist', 2, 'generalist', 'Active in many different tags', 0, 0),
+(29, 'Expert', 2, 'export', 'Very active in one tag', 0, 0),
+(30, 'Yearling', 2, 'yearling', 'Active member for a year', 0, 0),
+(31, 'Notable Question', 2, 'notable-question', 'Asked a question with 2,500 views', 1, 0),
+(32, 'Enlightened', 2, 'enlightened', 'First answer was accepted with at least 10 up votes', 0, 0),
+(33, 'Beta', 2, 'beta', 'Actively participated in the private beta', 0, 0),
+(34, 'Guru', 2, 'guru', 'Accepted answer and voted up 40 times', 1, 0),
+(35, 'Necromancer', 2, 'necromancer', 'Answered a question more than 60 days later with at least 5 votes', 1, 0),
+(36, 'Taxonomist', 2, 'taxonomist', 'Created a tag used by 50 questions', 1, 0);
diff --git a/templates/about.html b/templates/about.html
index ec4b6a73..66dcc3fd 100644
--- a/templates/about.html
+++ b/templates/about.html
@@ -12,24 +12,25 @@
</div>
<div class="content">
- <p><strong>{{settings.APP_SHORT_NAME}}</strong> is a collaboratively edited question and answer site created with
- <a href="http://osqa.net">OSQA: The Open Source Q&A System</a>.</p>
+ <p class="strong">Please customize file templates/about.html</p>
<p>Here you can <strong>ask</strong> and <strong>answer</strong> questions, <strong>comment</strong>
and <strong>vote</strong> for the questions of others and their answers. Both questions and answers
<strong>can be revised</strong> and improved. Questions can be <strong>tagged</strong> with
- the relevant keywords to simplify future access and organize the accumulated material.</p>
+ the relevant keywords to simplify future access and organize the accumulated material.
+ </p>
- <p>This OSQA site is moderated by its members, hopefully - including yourself!
+ <p>This <span class="orange">Q&amp;A</span> site is moderated by its members, hopefully - including yourself!
Moderation rights are gradually assigned to the site users based on the accumulated <strong>"reputation"</strong>
points. These points are added to the users account when others vote for his/her questions or answers.
- These points (very) roughly reflect the level of trust of the community.</p>
-
- <p>No points are necessary to ask or answer the questions - so please - <strong><a href="{% url user_signin %}">join
- us!</a></strong></p>
-
- <p>If you would like to find out more about this site - please see <strong><a href="{% url faq %}">frequently
- asked questions</a></strong>.</p>
+ These points (very) roughly reflect the level of trust of the community.
+ </p>
+ <p>No points are necessary to ask or answer the questions - so please -
+ <strong><a href="{% url user_signin %}">join us!</a></strong>
+ </p>
+ <p>
+ If you would like to find out more about this site - please see <strong><a href="{% url faq %}">frequently asked questions</a></strong>.
+ </p>
</div>
{% endblock %}
<!-- end template about.html -->
diff --git a/templates/authopenid/complete.html b/templates/authopenid/complete.html
index 1606cfc5..c967e8e2 100644
--- a/templates/authopenid/complete.html
+++ b/templates/authopenid/complete.html
@@ -11,7 +11,7 @@ parameters:
* username (same as screen name or username in the models, and nickname in openid sreg)
* form1 - OpenidRegisterForm
* form2 - OpenidVerifyForm not clear what this form is supposed to do, not used for legacy
-* email_feeds_form forum.forms.EditUserEmailFeedsForm
+* email_feeds_form forum.forms.SimpleEmailSubscribeForm
* openid_username_exists
{% endcomment %}
{% load i18n %}
@@ -92,9 +92,11 @@ parameters:
{% endif %}
{{ form1.email }}
</div>
- <p class='nomargin'>{% trans "receive updates motivational blurb" %}</p>
- {% include "edit_user_email_feeds_form.html" %}
- <p class='nomargin'>{% trans "Tag filter tool will be your right panel, once you log in." %}</p>
+ <p>{% trans "receive updates motivational blurb" %}</p>
+ <div class='simple-subscribe-options'>
+ {{email_feeds_form.subscribe}}
+ </div>
+ <p class='space-above'>{% trans "Tag filter tool will be your right panel, once you log in." %}</p>
<div class="submit-row"><input type="submit" class="submit" name="bnewaccount" value="{% trans "create account" %}"/></div>
</form>
</div>
@@ -108,7 +110,9 @@ parameters:
<div class="form-row"><label for="id_username">{% trans "user name" %}</label><br/>{{ form2.username }}</div>
<div class="form-row"><label for="id_passwordl">{% trans "password" %}</label><br/>{{ form2.password }}</div>
<p><span class='big strong'>(Optional) receive updates by email</span> - only sent when there are any.</p>
- {% include "edit_user_email_feeds_form.html" %}
+ <div class='simple-subscribe-options'>
+ {{email_feeds_form.subscribe}}
+ </div>
<!--todo double check translation from chinese 确认 = "Register" -->
<div class="submit-row">
<input type="submit" class="submit" name="bverify" value="{% trans "Register" %}"/>
diff --git a/templates/authopenid/external_legacy_login_info.html b/templates/authopenid/external_legacy_login_info.html
index c200b29d..3318499c 100644
--- a/templates/authopenid/external_legacy_login_info.html
+++ b/templates/authopenid/external_legacy_login_info.html
@@ -9,7 +9,7 @@
{% spaceless %}
<div class="message">
<!--add info about your external login site here-->
-{% trans "how to login with password through external login website" %}
+{% blocktrans %}how to login with password through external login website or use {{feedback_url}}{% endblocktrans %}
</div>
{% endspaceless %}
{% endblock %}
diff --git a/templates/authopenid/signup.html b/templates/authopenid/signup.html
index 45dfb51b..d800eaf9 100644
--- a/templates/authopenid/signup.html
+++ b/templates/authopenid/signup.html
@@ -10,10 +10,18 @@
<p class="message">{% trans "Traditional signup info" %}</p>
<form action="{% url user_signup %}" method="post" accept-charset="utf-8">
<ul class="form-horizontal-rows">
- {{form.as_ul}}
+ <li><label for="usename_id">{{form.username.label}}</label>{{form.username}}{{form.username.errors}}</li>
+ <li><label for="email_id">{{form.email.label}}</label>{{form.email}}{{form.email.errors}}</li>
+ <li><label for="password1_id">{{form.password1.label}}</label>{{form.password1}}{{form.password1.errors}}</li>
+ <li><label for="password2_id">{{form.password2.label}}</label>{{form.password2}}{{form.password2.errors}}</li>
</ul>
- <p style="margin:10px 0px 0px 3px;">{% trans "receive updates motivational blurb" %}</p>
- {% include "edit_user_email_feeds_form.html" %}
+ <p class="signup_p">{% trans "receive updates motivational blurb" %}</p>
+ <p class="signup_p">{% trans "Please select your preferred email update schedule for the following groups of questions:" %}</p>
+ <div class='simple-subscribe-options'>
+ {{email_feeds_form.subscribe}}
+ </div>
+ <p class="signup_p">{% trans "Please read and type in the two words below to help us prevent automated account creation." %}</p>
+ {{form.recaptcha}}
<div class="submit-row"><input type="submit" class="submit" value="{% trans "Create Account" %}" />
<strong>{% trans "or" %}
<a href="{% url user_signin %}">{% trans "return to OpenID login" %}</a></strong></div>
diff --git a/templates/badge.html b/templates/badge.html
index 73cba4ba..af6aa2a2 100644
--- a/templates/badge.html
+++ b/templates/badge.html
@@ -28,7 +28,7 @@
</div>
<div id="award-list" style="clear:both;margin-left:20px;line-height:25px;">
{% for award in awards %}
- <p style="width:185px;float:left"><a href="{% url users %}{{ award.id }}/{{ award.name }}">{{ award.name }}</a> {% get_score_badge_by_details award.rep award.gold award.silver award.bronze %}</p>
+ <p style="width:180px;float:left"><a href="{% url users %}{{ award.id }}/{{ award.name }}">{{ award.name }}</a> {% get_score_badge_by_details award.rep award.gold award.silver award.bronze %}</p>
{% endfor %}
</div>
diff --git a/templates/badges.html b/templates/badges.html
index 1902a3b0..8de93df5 100644
--- a/templates/badges.html
+++ b/templates/badges.html
@@ -33,10 +33,10 @@
{% endifequal %}
{% endfor %}
</div>
- <div style="float:left;width:180px;">
+ <div style="float:left;width:230px;">
<a href="{{badge.get_absolute_url}}" title="{{ badge.get_type_display }} : {{ badge.description }}" class="medal"><span class="badge{{ badge.type }}">&#9679;</span>&nbsp;{{ badge.name }}</a><strong> &#215; {{ badge.awarded_count|intcomma }}</strong>
</div>
- <p style="float:left;width:350px;">
+ <p style="float:left;margin-top:8px;">
{{ badge.description }}
</p>
</div>
diff --git a/templates/base.html b/templates/base.html
index a33512cc..17a32ef2 100755
--- a/templates/base.html
+++ b/templates/base.html
@@ -11,7 +11,7 @@
{% endspaceless %}
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
{% if settings.GOOGLE_SITEMAP_CODE %}
- <meta name="verify-v1" content="{{settings.GOOGLE_SITEMAP_CODE}}" />
+ <meta name="google-site-verification" content="{{settings.GOOGLE_SITEMAP_CODE}}" />
{% endif %}
<link rel="shortcut icon" href="{% href "/content/images/favicon.ico" %}" />
<link href="{% href "/content/style/style.css" %}" rel="stylesheet" type="text/css" />
@@ -46,7 +46,7 @@
body { margin-top:2.4em; }
</style>
<script type="text/javascript">
- $().ready(function() {
+ $(document).ready(function() {
$('#validate_email_alert').click(function(){notify.close(true)})
notify.show();
});
diff --git a/templates/base_content.html b/templates/base_content.html
index 78e5fe38..eacdc6d0 100644
--- a/templates/base_content.html
+++ b/templates/base_content.html
@@ -7,7 +7,7 @@
<title>{% block title %}{% endblock %} - {{ settings.APP_TITLE }}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
{% if settings.GOOGLE_SITEMAP_CODE %}
- <meta name="verify-v1" content="{{ settings.GOOGLE_SITEMAP_CODE }}" />
+ <meta name="google-site-verification" content="{{ settings.GOOGLE_SITEMAP_CODE }}" />
{% endif %}
<link rel="shortcut icon" href="{% href "/content/images/favicon.ico" %}" />
<link href="{% href "/content/style/style.css" %}" rel="stylesheet" type="text/css" />
@@ -48,7 +48,7 @@
body { margin-top:2.4em; }
</style>
<script type="text/javascript">
- $().ready(function() {
+ $(document).ready(function() {
var element = $('#validate_email_alert')
element.click(function(){notify.close(true);setTimeout(function(){},1000)})
notify.show();
@@ -64,7 +64,7 @@
{% autoescape off %}
{% if user_messages %}
{% for message in user_messages %}
- <p class="darkred">{{ message }}<p>
+ <p class="darkred">{{ message }}</p>
{% endfor %}
{% endif %}
{% endautoescape %}
diff --git a/templates/content/images/logo.png b/templates/content/images/logo.png
deleted file mode 100644
index b53732e3..00000000
--- a/templates/content/images/logo.png
+++ /dev/null
Binary files differ
diff --git a/templates/content/jquery-openid/images/local-login.png b/templates/content/jquery-openid/images/local-login.png
deleted file mode 100644
index 258cedac..00000000
--- a/templates/content/jquery-openid/images/local-login.png
+++ /dev/null
Binary files differ
diff --git a/templates/content/jquery-openid/jquery.openid.js b/templates/content/jquery-openid/jquery.openid.js
index 763af2c6..8d1cd204 100644
--- a/templates/content/jquery-openid/jquery.openid.js
+++ b/templates/content/jquery-openid/jquery.openid.js
@@ -53,7 +53,7 @@ $.fn.openid = function() {
$localfs.fadeOut('slow');
$idfs.fadeOut('slow');
$id.val($this.find("li.highlight span").text());
- setTimeout(function(){$('#bsignin').click()},1000);
+ setTimeout(function(){$('#bsignin').click();},1000);
return false;
};
diff --git a/templates/content/js/com.cnprog.admin.js b/templates/content/js/com.cnprog.admin.js
index cb1c1b15..39dff48c 100644
--- a/templates/content/js/com.cnprog.admin.js
+++ b/templates/content/js/com.cnprog.admin.js
@@ -1,4 +1,4 @@
-$().ready( function(){
+$(document).ready( function(){
var options = {
success: function(a,b){$('.admin #action_status').html($.i18n._('changes saved'));},
dataType:'json',
diff --git a/templates/content/js/com.cnprog.editor.js b/templates/content/js/com.cnprog.editor.js
index 6cfa2c74..18cc5166 100644
--- a/templates/content/js/com.cnprog.editor.js
+++ b/templates/content/js/com.cnprog.editor.js
@@ -65,4 +65,4 @@ function ajaxFileUpload(imageUrl)
)
return false;
-} \ No newline at end of file
+}
diff --git a/templates/content/js/com.cnprog.i18n.js b/templates/content/js/com.cnprog.i18n.js
index 7562628b..da9bf396 100644
--- a/templates/content/js/com.cnprog.i18n.js
+++ b/templates/content/js/com.cnprog.i18n.js
@@ -1,3 +1,5 @@
+
+//var i18nLang;
var i18nZh = {
'insufficient privilege':'用户权限不在操作范围',
'cannot pick own answer as best':'不能设置自己的回答为最佳答案',
diff --git a/templates/content/js/com.cnprog.post.js b/templates/content/js/com.cnprog.post.js
index a073d20f..668c80fe 100644
--- a/templates/content/js/com.cnprog.post.js
+++ b/templates/content/js/com.cnprog.post.js
@@ -61,7 +61,7 @@ var Vote = function(){
var pleaseSeeFAQ = $.i18n._('please see') + "<a href='" + scriptUrl + $.i18n._("faq/") + "'>faq</a>";
- var favoriteAnonymousMessage = $.i18n._('anonymous users cannot select favorite questions')
+ var favoriteAnonymousMessage = $.i18n._('anonymous users cannot select favorite questions');
var voteAnonymousMessage = $.i18n._('anonymous users cannot vote') + pleaseLogin;
var upVoteRequiredScoreMessage = $.i18n._('>15 points requried to upvote') + pleaseSeeFAQ;
var downVoteRequiredScoreMessage = $.i18n._('>100 points required to downvote') + pleaseSeeFAQ;
@@ -242,7 +242,8 @@ var Vote = function(){
url: scriptUrl + $.i18n._("questions/") + questionId + "/" + $.i18n._("vote/"),
data: { "type": voteType, "postId": postId },
error: handleFail,
- success: function(data){callback(object, voteType, data)}});
+ success: function(data){callback(object, voteType, data);}
+ });
};
var handleFail = function(xhr, msg){
@@ -287,8 +288,9 @@ var Vote = function(){
object.attr("src", scriptUrl + "content/images/vote-favorite-off.png");
var fav = getFavoriteNumber();
fav.removeClass("my-favorite-number");
- if(data.count == 0)
+ if(data.count === 0){
data.count = '';
+ }
fav.text(data.count);
}
else if(data.success == "1"){
@@ -456,9 +458,12 @@ var Vote = function(){
submit($(object), voteType, callback_remove);
}
}
- }
+ };
} ();
+var questionComments = createComments('question');
+var answerComments = createComments('answer');
+var commentsFactory = {'question' : questionComments, 'answer' : answerComments};
// site comments
function createComments(type) {
@@ -482,12 +487,12 @@ function createComments(type) {
$(jDiv).css('background','none');
$(jDiv).css('padding-left',0);
if (canPostComments(id)) {
- if (jDiv.find("#" + formId).length == 0) {
+ if (jDiv.find("#" + formId).length === 0) {
var form = '<form id="' + formId + '" class="post-comments"><div>';
form += '<textarea name="comment" cols="60" rows="5" maxlength="300" onblur="'+ objectType +'Comments.updateTextCounter(this)" ';
form += 'onfocus="' + objectType + 'Comments.updateTextCounter(this)" onkeyup="'+ objectType +'Comments.updateTextCounter(this)"></textarea>';
- form += '<input type="submit" value="'
- + $.i18n._('add comment') + '" /><br><span class="text-counter"></span>';
+ form += '<input type="submit" value="' +
+ $.i18n._('add comment') + '" /><br><span class="text-counter"></span>';
form += '<span class="form-error"></span></div></form>';
jDiv.append(form);
@@ -499,20 +504,20 @@ function createComments(type) {
}
else {
var divId = "comments-rep-needed-" + objectType + '-' + id;
- if (jDiv.find("#" + divId).length == 0) {
- jDiv.append('<p id="' + divId + '" class="comment">'
- + $.i18n._('to comment, need') + ' ' +
- + repNeededForComments + ' ' + $.i18n._('community karma points')
- + '<a href="' + scriptUrl + $.i18n._('faq/') + '" class="comment-user">'
- + $.i18n._('please see') + 'faq</a></span></p>');
+ if (jDiv.find("#" + divId).length === 0) {
+ jDiv.append('<p id="' + divId + '" class="comment">' +
+ $.i18n._('to comment, need') + ' ' +
+ repNeededForComments + ' ' + $.i18n._('community karma points') +
+ '<a href="' + scriptUrl + $.i18n._('faq/') + '" class="comment-user">' +
+ $.i18n._('please see') + 'faq</a></span></p>');
}
}
};
var getComments = function(id, jDiv) {
//appendLoaderImg(id);
- $.getJSON(scriptUrl + objectType + "s/" + id + "/" + $.i18n._("comments/")
- , function(json) { showComments(id, json); });
+ $.getJSON(scriptUrl + objectType + "s/" + id + "/" + $.i18n._("comments/"),
+ function(json) { showComments(id, json); });
};
var showComments = function(id, json) {
@@ -523,8 +528,9 @@ function createComments(type) {
jDiv.children().remove();
removeLoader();
if (json && json.length > 0) {
- for (var i = 0; i < json.length; i++)
+ for (var i = 0; i < json.length; i++){
renderComment(jDiv, json[i]);
+ }
jDiv.children().show();
}
};
@@ -535,14 +541,14 @@ function createComments(type) {
var img = scriptUrl + "content/images/close-small.png";
var imgHover = scriptUrl + "content/images/close-small-hover.png";
html += '<img class="delete-icon" onclick="' + objectType + 'Comments.deleteComment($(this), ' + post_id + ', \'' + delete_url + '\')" src="' + img;
- html += '" onmouseover="$(this).attr(\'src\', \'' + imgHover + '\')" onmouseout="$(this).attr(\'src\', \'' + img
+ html += '" onmouseover="$(this).attr(\'src\', \'' + imgHover + '\')" onmouseout="$(this).attr(\'src\', \'' + img;
html += '\')" title="' + $.i18n._('delete this comment') + '" />';
return html;
}
else{
return '';
}
- }
+ };
// {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
var renderComment = function(jDiv, json) {
@@ -604,8 +610,8 @@ function createComments(type) {
$(this).children().each(
function(i){
var comment_id = $(this).attr('id').replace('comment-','');
- var delete_url = scriptUrl + objectType + 's/' + post_id + '/'
- + $.i18n._('comments/') + comment_id + '/' + $.i18n._('delete/');
+ var delete_url = scriptUrl + objectType + 's/' + post_id + '/' +
+ $.i18n._('comments/') + comment_id + '/' + $.i18n._('delete/');
var html = $(this).html();
var CommentsClass;
if (objectType == 'question'){
@@ -638,14 +644,14 @@ function createComments(type) {
jDiv.show();
var link = $('#comments-link-' + objectType + '-' + id);
- if (canPostComments(id)) link.parent().find("textarea").get(0).focus();
+ if (canPostComments(id)) { link.parent().find("textarea").get(0).focus(); }
link.remove();
},
hide: function(id) {
var jDiv = jDivInit(id);
var len = jDiv.children("div.comments").children().length;
- var anchorText = len == 0 ? $.i18n._('add a comment') : $.i18n._('comments') + ' (<b>' + len + "</b>)";
+ var anchorText = len === 0 ? $.i18n._('add a comment') : $.i18n._('comments') + ' (<b>' + len + "</b>)";
jDiv.hide();
jDiv.siblings("a").unbind("click").click(function() { commentsFactory[objectType].show(id); }).html(anchorText);
@@ -666,23 +672,18 @@ function createComments(type) {
var length = textarea.value ? textarea.value.length : 0;
var color = length > 270 ? "#f00" : length > 200 ? "#f60" : "#999";
var jSpan = $(textarea).siblings("span.text-counter");
- jSpan.html($.i18n._('can write')
- + (300 - length) + ' '
- + $.i18n._('characters')).css("color", color);
+ jSpan.html($.i18n._('can write') +
+ (300 - length) + ' ' +
+ $.i18n._('characters')).css("color", color);
}
};
}
-var questionComments = createComments('question');
-var answerComments = createComments('answer');
-
-$().ready(function() {
+$(document).ready(function() {
questionComments.init();
answerComments.init();
});
-var commentsFactory = {'question' : questionComments, 'answer' : answerComments};
-
/*
Prettify
http://www.apache.org/licenses/LICENSE-2.0
diff --git a/templates/content/js/com.cnprog.tag_selector.js b/templates/content/js/com.cnprog.tag_selector.js
index f6c16c9c..06aefcfc 100644
--- a/templates/content/js/com.cnprog.tag_selector.js
+++ b/templates/content/js/com.cnprog.tag_selector.js
@@ -1,7 +1,8 @@
+//var scriptUrl, interestingTags, ignoredTags, tags, $;
function pickedTags(){
var sendAjax = function(tagname, reason, action, callback){
- url = scriptUrl;
+ var url = scriptUrl;
if (action == 'add'){
url += $.i18n._('mark-tag/');
if (reason == 'good'){
@@ -16,15 +17,15 @@ function pickedTags(){
}
url = url + tagname + '/';
- call_settings = {
+ var call_settings = {
type:'POST',
url:url
- }
- if (callback != false){
- call_settings['success'] = callback;
+ };
+ if (callback !== false){
+ call_settings.success = callback;
}
$.ajax(call_settings);
- }
+ };
var unpickTag = function(from_target ,tagname, reason, send_ajax){
@@ -32,7 +33,7 @@ function pickedTags(){
var deleteTagLocally = function(){
from_target[tagname].remove();
delete from_target[tagname];
- }
+ };
if (send_ajax){
sendAjax(tagname,reason,'remove',deleteTagLocally);
}
@@ -40,7 +41,7 @@ function pickedTags(){
deleteTagLocally();
}
- }
+ };
var setupTagDeleteEvents = function(obj,tag_store,tagname,reason,send_ajax){
obj.unbind('mouseover').bind('mouseover', function(){
@@ -52,12 +53,13 @@ function pickedTags(){
obj.click( function(){
unpickTag(tag_store,tagname,reason,send_ajax);
});
- }
+ };
var handlePickedTag = function(obj,reason){
var tagname = $.trim($(obj).prev().attr('value'));
- to_target = interestingTags;
- from_target = ignoredTags;
+ var to_target = interestingTags;
+ var from_target = ignoredTags;
+ var to_tag_container;
if (reason == 'bad'){
to_target = ignoredTags;
from_target = interestingTags;
@@ -78,13 +80,13 @@ function pickedTags(){
//send ajax request to pick this tag
sendAjax(tagname,reason,'add',function(){
- new_tag = $('<span></span>');
+ var new_tag = $('<span></span>');
new_tag.addClass('deletable-tag');
- tag_link = $('<a></a>');
+ var tag_link = $('<a></a>');
tag_link.attr('rel','tag');
tag_link.attr('href', scriptUrl + $.i18n._('tags/') + tagname);
tag_link.html(tagname);
- del_link = $('<img></img>');
+ var del_link = $('<img></img>');
del_link.addClass('delete-icon');
del_link.attr('src', scriptUrl + 'content/images/close-small-dark.png');
@@ -97,7 +99,7 @@ function pickedTags(){
to_target[tagname] = new_tag;
});
}
- }
+ };
var collectPickedTags = function(){
var good_prefix = 'interesting-tag-';
@@ -108,7 +110,8 @@ function pickedTags(){
ignoredTags = {};
$('.deletable-tag').each(
function(i,item){
- item_id = $(item).attr('id')
+ var item_id = $(item).attr('id');
+ var tag_name, tag_store;
if (good_re.test(item_id)){
tag_name = item_id.replace(good_prefix,'');
tag_store = interestingTags;
@@ -123,10 +126,10 @@ function pickedTags(){
return;
}
tag_store[tag_name] = $(item);
- setupTagDeleteEvents($(item).find('img'),tag_store,tag_name,reason,true)
+ setupTagDeleteEvents($(item).find('img'),tag_store,tag_name,reason,true);
}
);
- }
+ };
var setupHideIgnoredQuestionsControl = function(){
$('#hideIgnoredTagsCb').unbind('click').click(function(){
@@ -138,7 +141,7 @@ function pickedTags(){
data: {command:'toggle-ignored-questions'}
});
});
- }
+ };
return {
init: function(){
collectPickedTags();
@@ -157,8 +160,8 @@ function pickedTags(){
}
});
- $("#interestingTagAdd").click(function(){handlePickedTag(this,'good')});
- $("#ignoredTagAdd").click(function(){handlePickedTag(this,'bad')});
+ $("#interestingTagAdd").click(function(){handlePickedTag(this,'good');});
+ $("#ignoredTagAdd").click(function(){handlePickedTag(this,'bad');});
}
};
}
diff --git a/templates/content/js/com.cnprog.utils.js b/templates/content/js/com.cnprog.utils.js
index b19b6773..4c3aafba 100644
--- a/templates/content/js/com.cnprog.utils.js
+++ b/templates/content/js/com.cnprog.utils.js
@@ -1,6 +1,7 @@
+//var $, scriptUrl;
var showMessage = function(object, msg) {
- var div = $('<div class="vote-notification"><h3>' + msg + '</h3>('
- + $.i18n._('click to close') + ')</div>');
+ var div = $('<div class="vote-notification"><h3>' + msg + '</h3>(' +
+ $.i18n._('click to close') + ')</div>');
div.click(function(event) {
$(".vote-notification").fadeOut("fast", function() { $(this).remove(); });
@@ -35,18 +36,30 @@ var notify = function() {
} ();
function appendLoader(containerSelector) {
- $(containerSelector).append('<img class="ajax-loader" '
- +'src="' + scriptUrl + 'content/images/indicator.gif" title="'
- +$.i18n._('loading...')
- +'" alt="'
- +$.i18n._('loading...')
- +'" />');
+ $(containerSelector).append('<img class="ajax-loader" ' +
+ 'src="' + scriptUrl + 'content/images/indicator.gif" title="' +
+ $.i18n._('loading...') +
+ '" alt="' +
+ $.i18n._('loading...') +
+ '" />');
}
function removeLoader() {
$("img.ajax-loader").remove();
}
+function setSubmitButtonDisabled(formSelector, isDisabled) {
+ $(formSelector).find("input[type='submit']").attr("disabled", isDisabled ? "true" : "");
+}
+
+function enableSubmitButton(formSelector) {
+ setSubmitButtonDisabled(formSelector, false);
+}
+
+function disableSubmitButton(formSelector) {
+ setSubmitButtonDisabled(formSelector, true);
+}
+
function setupFormValidation(formSelector, validationRules, validationMessages, onSubmitCallback) {
enableSubmitButton(formSelector);
$(formSelector).validate({
@@ -56,7 +69,7 @@ function setupFormValidation(formSelector, validationRules, validationMessages,
errorClass: "form-error",
errorPlacement: function(error, element) {
var span = element.next().find("span.form-error");
- if (span.length == 0) {
+ if (span.length === 0) {
span = element.parent().find("span.form-error");
}
span.replaceWith(error);
@@ -64,24 +77,16 @@ function setupFormValidation(formSelector, validationRules, validationMessages,
submitHandler: function(form) {
disableSubmitButton(formSelector);
- if (onSubmitCallback)
+ if (onSubmitCallback){
onSubmitCallback();
- else
+ }
+ 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(){
@@ -108,11 +113,11 @@ var CPValidator = function(){
},
text: {
required: " " + $.i18n._('content cannot be empty'),
- minlength: jQuery.format(' ' + $.i18n._('content minchars'))
+ minlength: $.format(' ' + $.i18n._('content minchars'))
},
title: {
required: " " + $.i18n._('please enter title'),
- minlength: jQuery.format(' ' + $.i18n._('title minchars'))
+ minlength: $.format(' ' + $.i18n._('title minchars'))
}
};
}
diff --git a/templates/content/js/mediawiki-login.js b/templates/content/js/mediawiki-login.js
new file mode 100644
index 00000000..f1805f88
--- /dev/null
+++ b/templates/content/js/mediawiki-login.js
@@ -0,0 +1,29 @@
+function toggleScreenNameInput(par1,par2){
+ if ($(this).is(':checked')){
+ $('.optional-screen-name').show();
+ }
+ else {
+ $('#id_screen_name').val('');
+ $('.optional-screen-name').hide();
+ }
+}
+
+function toggleScreenNameErrorMessage(e){
+ var screen_name = $('#id_screen_name').val();
+ if (screen_name != ''){
+ $('.screen-name-error').hide();
+ }
+ else{
+ $('.screen-name-error').show();
+ }
+}
+
+$(document).ready( function(){
+ var screen_name = $('#id_screen_name').val();
+ var use_screen_name = $('#id_use_separate_screen_name').is(':checked');
+ if (screen_name == '' && !use_screen_name){
+ $('.optional-screen-name').hide();
+ }
+ $('#id_use_separate_screen_name').unbind('click').click(toggleScreenNameInput);
+ $('#id_screen_name').unbind('keyup').keyup(toggleScreenNameErrorMessage);
+});
diff --git a/templates/content/style/mediawiki-login.css b/templates/content/style/mediawiki-login.css
new file mode 100644
index 00000000..58813c7c
--- /dev/null
+++ b/templates/content/style/mediawiki-login.css
@@ -0,0 +1,63 @@
+#mediawiki-login legend {
+ font-weight:bold;
+ font-size:14px;
+}
+
+#mediawiki-login fieldset {
+ border:none;
+}
+
+#mediawiki-login ul {
+ list-style: none;
+ list-style-position: outside;
+ padding: 0px;
+ margin: 10px 0 0 0;
+}
+
+#mediawiki-login p {
+ margin:0;
+}
+
+#mediawiki-login div.login-information label {
+ width: 180px;
+ display: inline-block;
+}
+
+#mediawiki-login legend {
+ padding: 0px;
+}
+
+#mediawiki-login h2 {
+ margin:10px 0 0 0;
+ padding:3px 0 3px 0;
+ border:none;
+ font-family:sans-serif;
+ font-size:16.8px;
+ font-weight:bold;
+}
+
+#mediawiki-login tr {
+ vertical-align: top;
+}
+
+#id_screen_name {
+ margin-left:25px;
+}
+
+#mediawiki-login input {
+ height: 20px;
+}
+
+#mediawiki-login input.submit {
+ margin-top:5px;
+ margin-left:5px;
+ display:block;
+ clear:both;
+ font-weight: bold;
+ font-size:14.4px;
+ height:33px;
+ /*padding: 4px 6px 4px 6px;*/
+ text-align: center;
+ border: 1px solid #777777;
+ background: #D4D0C8;
+}
diff --git a/templates/content/style/style.css b/templates/content/style/style.css
index 47b4dc00..aba67eee 100644
--- a/templates/content/style/style.css
+++ b/templates/content/style/style.css
@@ -686,7 +686,12 @@ table.form-as-table th {
/*.form-row li label {
display: inline
}*/
-.submit-row{line-height:30px;padding-top:10px;}
+.submit-row{
+ line-height:30px;
+ padding-top:10px;
+ display: block;
+ clear: both;
+}
.errors{line-height:20px;color:red;}
.error{
color:darkred;
@@ -1158,7 +1163,7 @@ ul.bulleta li {background:url(../images/bullet_green.gif) no-repeat 0px 2px; pad
.message p {
margin-bottom:0px;
}
-.message p.space-above {
+p.space-above {
margin-top:10px;
}
@@ -1446,3 +1451,16 @@ ul.form-horizontal-rows li input {
#hideIgnoredTagsCb {
margin: 0 2px 0 1px;
}
+#recaptcha_widget_div {
+ width:318px;
+ float:left;
+ clear:both;
+}
+p.signup_p {
+ margin: 20px 0px 0px 0px;
+}
+.simple-subscribe-options ul {
+ list-style:none;
+ list-style-position:outside;
+ margin:0;
+}
diff --git a/templates/footer.html b/templates/footer.html
index 9d19b41e..66feff8a 100644
--- a/templates/footer.html
+++ b/templates/footer.html
@@ -7,8 +7,6 @@
<div class="footerLinks" >
<a href="{% url about %}">{% trans "about" %}</a><span class="link-separator"> |</span>
<a href="{% url faq %}">{% trans "faq" %}</a><span class="link-separator"> |</span>
- <!--<a href="{{ blog_url }}">{% trans "blog" %}</a><span class="link-separator"> |</span>-->
- <!--<a href="{{ webmaster_email }}">{% trans "contact us" %}</a><span class="link-separator"> |</span>-->
<a href="{% url privacy %}">{% trans "privacy policy" %}</a><span class="link-separator"> |</span>
{% spaceless %}
<a href=
@@ -25,7 +23,6 @@
<p>
<a href="http://github.com/cnprog/CNPROG/network" target="_blank">
powered by cnprog platform
- <!--<img src="{% href "/content/images/djangomade124x25_grey.gif" %}" border="0" alt="Made with Django." title="Made with Django." >-->
</a>
</p>
</div>
@@ -36,14 +33,16 @@
</div>
</div>
<!-- 页面底部结束: -->
- <script type="text/javascript">
- var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
- document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
- </script>
- <script type="text/javascript">
- try {
- var pageTracker = _gat._getTracker('{{ settings.GOOGLE_ANALYTICS_KEY }}');
- pageTracker._trackPageview();
- } catch(err) {}
+ {% if settings.GOOGLE_ANALYTICS_KEY %}
+ <script type="text/javascript">
+ var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+ document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
+ <script type="text/javascript">
+ try {
+ var pageTracker = _gat._getTracker('{{ settings.GOOGLE_ANALYTICS_KEY }}');
+ pageTracker._trackPageview();
+ } catch(err) {}
+ </script>
+ {% endif %}
<!-- end template footer.html -->
diff --git a/templates/header.html b/templates/header.html
index 42074763..466659de 100644
--- a/templates/header.html
+++ b/templates/header.html
@@ -32,12 +32,6 @@
{% endif %}
<a id="nav_badges" href="{% url badges %}">{% trans "badges" %}</a>
<a id="nav_unanswered" href="{% url unanswered %}">{% trans "unanswered questions" %}</a>
-
- {% comment %}<!-- i think this needs to be removed -e.f. -->
- {% if request.user.is_authenticated %}
- <a id="nav_profile" href="{% url user %}{{ request.user.id }}/{{ request.user.username }}/">{% trans "my profile" %}</a>
- {% endif %}
- {% endcomment %}
<div class="focus">
<a id="nav_ask" href="{% url ask %}" class="special">{% trans "ask a question" %}</a>
</div>
diff --git a/templates/mediawiki/mediawiki_signup.html b/templates/mediawiki/mediawiki_signup.html
new file mode 100644
index 00000000..d1ecb96c
--- /dev/null
+++ b/templates/mediawiki/mediawiki_signup.html
@@ -0,0 +1,9 @@
+{% extends "base_content.html" %}
+{% load i18n %}
+{% block forejs %}
+ {{form.media}}
+{% endblock %}
+{% block title %}{% spaceless %}{% trans "MediaWiki User Registration" %}{% endspaceless %}{% endblock %}
+{% block content %}
+{% include "mediawiki/mediawiki_signup_content.html" %}
+{% endblock %}
diff --git a/templates/mediawiki/mediawiki_signup_content.html b/templates/mediawiki/mediawiki_signup_content.html
new file mode 100644
index 00000000..a09de107
--- /dev/null
+++ b/templates/mediawiki/mediawiki_signup_content.html
@@ -0,0 +1,110 @@
+{% load smart_if %}
+{% load i18n %}
+{% if request.is_include_virtual == False %}
+<div class="headNormal">
+ {% trans "MediaWiki User Registration" %}
+</div>
+{% endif %}
+<div id="mediawiki-login">
+{% if request.is_include_virtual %}
+<form name="fregister" action="/wiki/index.php" accept-charset="UTF-8" method="post">
+ <input type="hidden" name="command" value="/backend/account/yourwiki/signup/"/>
+ <input type="hidden" name="title" value="Special:UserRegister"/>
+{% else %}
+<form name="fregister" action="." accept-charset="UTF-8" method="post">
+{% endif %}
+ {% with form as f %}
+ {{ f.next }}
+ <h2>{% trans "Basic information" %}</h2>
+ <div class="login-information">
+ <p>
+ <label for="id_login_name">{{f.login_name.label}}</label>
+ {{f.login_name}}
+ </p>
+ {% if f.login_name.errors %}
+ <p class="error">{{ f.login_name.errors|join:", " }}</p>
+ {% endif %}
+ <p>
+ <label for="id_password1">{{f.password1.label}}</label>
+ {{f.password1}}
+ {% if f.password1.errors %}
+ <span class="error">{{ f.password1.errors|join:", " }}</span>
+ {% endif %}
+ </p>
+ <p>
+ <label for="id_password2">{{f.password2.label}}</label>
+ {{f.password2}}
+ {% if f.password2.errors %}
+ <span class="error">{{ f.password2.errors|join:", " }}</span>
+ {% endif %}
+ </p>
+ <p>
+ <label for="id_email">{{f.email.label}}</label>
+ {{f.email}}
+ {% if f.email.errors %}
+ <span class="error">{{ f.email.errors|join:", " }}</span>
+ {% endif %}
+ </p>
+ </div>
+ <h2>{% trans "Your Name" %}</h2>
+ <p>{% trans "<strong>1) Real name</strong> - required for the Wiki, but not shown on the forum by default" %}</p>
+ <table>
+ <tr>
+ <td><label for="id_first_name">{{f.first_name.label}}</label></td>
+ <td><label for="id_screen_name">{{f.last_name.label}}</label></td>
+ <td><label for="id_user_title">{{f.user_title.label}}</label></td>
+ </tr>
+ <tr>
+ <td>
+ {{f.first_name}}
+ {% if f.first_name.errors %}
+ <p class="error">{{ f.first_name.errors|join:", " }}</p>
+ {% endif %}
+ </td>
+ <td>
+ {{f.last_name}}
+ {% if f.last_name.errors %}
+ <p class="error">{{ f.last_name.errors|join:", " }}</p>
+ {% endif %}
+ </td>
+ <td>
+ {{f.user_title}}
+ {% if f.user_title.errors %}
+ <p class="error">{{ f.user_title.errors|join:", " }}</p>
+ {% endif %}
+ </td>
+ </tr>
+ </table>
+ <p>{% trans "<strong>2) Forum screen name</strong>" %}</p>
+ <p>{% trans "Just skip this to use your full name at the forum, otherwise please check below" %}</p>
+ <p>
+ {{f.use_separate_screen_name}}
+ <label for="id_use_separate_screen_name">
+ {{f.use_separate_screen_name.label}}
+ </label>
+ </p>
+ <p class="optional-screen-name">
+ {{f.screen_name}}
+ </p>
+ {% if f.screen_name.errors %}
+ <p class="error screen-name-error">{{ f.screen_name.errors|join:", " }}</p>
+ {% endif %}
+ <p class="optional-screen-name">{% trans "Please remember that forum screen name is not your login name.<br/>Screen name allows you stay anonymous at the forum - you can change it at any time too. Login name cannot be changed." %}</p>
+ <h2>{% trans "Update subscription" %}</h2>
+ <p>{% trans "receive updates motivational blurb" %}</p>
+ {{f.subscribe}}
+ {% if f.subscribe.errors %}
+ <p class="error">{{ f.subscribe.errors|join:", " }}</p>
+ {% endif %}
+ <h2>{% trans "Almost there..." %}</h2>
+ <p>{% trans "recaptcha explained" %}</p>
+ <p>{{f.recaptcha.errors|join:", "}}
+ <div style="clear:both; display:block;">{{f.recaptcha}}</div>
+ {% endwith %}
+ <input type="submit" value="{% trans "Create account" %}" class="submit"/>
+ {% comment %}<!-- this stuff was used for the wizard that fails with recaptcha so commented out-->
+ <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
+ {{ previous_fields|safe }}
+ {% endcomment %}
+ </form>
+</div>
diff --git a/templates/mediawiki/thanks_for_joining.html b/templates/mediawiki/thanks_for_joining.html
new file mode 100644
index 00000000..9695ba05
--- /dev/null
+++ b/templates/mediawiki/thanks_for_joining.html
@@ -0,0 +1,76 @@
+{% spaceless %}
+{% load smart_if %}
+{% with wiki_user.user_name as user_name %}
+<script type="text/javascript">
+$wgUserName = '{{user_name}}';
+$(document).ready(function(){
+$('#p-personal div').html('\
+<ul>\
+<li id="pt-0"><a href="/wiki/index.php?title=User:{{user_name}}" class="new">{{user_name}}</a></li>\
+<li id="pt-1"><a href="/wiki/index.php?title=UserWiki:{{user_name}}" class="new">my page</a></li>\
+<li id="pt-mytalk"><a href="/wiki/index.php?title=User_talk:{{user_name}}" title="My talk page [n]" accesskey="n" class="new">My talk</a></li>\
+<li id="pt-preferences"><a href="/wiki/index.php?title=Special:Preferences" title="My preferences">My preferences</a></li>\
+<li id="pt-watchlist"><a href="/wiki/index.php?title=Special:Watchlist" title="The list of pages you\'re monitoring for changes [l]" accesskey="l">My watchlist</a></li>\
+<li id="pt-mycontris"><a href="/wiki/index.php?title=Special:Contributions/{{user_name}}" title="List of my contributions [y]" accesskey="y">My contributions</a></li>\
+<li id="pt-logout"><a href="/wiki/index.php?title=Special:Userlogout&amp;returnto=Special:UserRegister" title="Log out">Log out</a></li>\
+</ul>\
+');
+});
+</script>
+{% endwith %}
+{% if wiki_user.user_title == 'prof' %}
+<script type="text/javascript">
+ var documentTitle = 'Professor {{wiki_user.user_last_name}}, Welcome to Wiki!';
+</script>
+{% else %}
+ {% if wiki_user.title == 'dr' %}
+ <script type="text/javascript">
+ var documentTitle = 'Dr. {{wiki_user.user_last_name}}, Welcome to Wiki!';
+ </script>
+ {% else %}
+ <script type="text/javascript">
+ var documentTitle = '{{wiki_user.user_first_name}}, Welcome to Wiki!';
+ </script>
+ {% endif %}
+{% endif %}
+<script type="text/javascript">
+ $(document).ready( function(){
+ document.title = documentTitle;
+ $('h1.firstHeading').html(documentTitle);
+ });
+</script>
+{% if wiki_user.user_title == 'prof' %}
+<p>Dear Professor {{wiki_user.user_last_name}},
+{% else %}
+ {% if wiki_user.title == 'dr' %}
+ <p>Dear Dr. {{wiki_user.user_last_name}},
+ {% else %}
+ <p>Dear {{wiki_user.user_first_name}},
+ {% endif %}
+{% endif %}
+thanks joining Wiki!</p>
+<p><strong>Could you help our community right now?</strong><br/>
+Please answer some of the questions from our Q&amp;A forum:</p>
+<ul>
+ {% for q in questions %}
+ <li><a href="http://yourwiki.org/question/{{q.id}}/{{q.title|slugify}}">{{q.title}}</a></li>
+ {% endfor %}
+</ul>
+<p>Your answers will be <strong>indispensable</strong>.<br/>
+Please feel free to ask something too! Hopefully you will like this forum and the wiki and invite your coworkers and friends to join.
+</p>
+<p>Might you consider <strong>sharing some of the digital documentation and pulse sequences</strong> that
+perhaps had accumulated in your lab?<br/> It's very easy to upload
+files to the wiki as it is to edit the pages directly.
+</p>
+<p>Best wishes,<br/>
+Wiki Server Admin.
+</p>
+<p>P.S. An email with the confirmation code has been sent to <strong>{{wiki_user.user_email}}</strong>.
+Please follow the included link to confirm your email address.
+{% if wiki_user.user_title == 'prof' %}
+<br/>
+Also, you are always welcome to <strong>advertise open positions</strong> in your laboratory on the wiki. </p>
+{% endif %}
+</p>
+{% endspaceless %}
diff --git a/templates/mediawiki/welcome_email.txt b/templates/mediawiki/welcome_email.txt
new file mode 100644
index 00000000..c282d9e5
--- /dev/null
+++ b/templates/mediawiki/welcome_email.txt
@@ -0,0 +1,28 @@
+{% spaceless %}
+{% load i18n %}
+{% load smart_if %}
+{% if title == 'prof' %}
+{% blocktrans %}Dear Professor {{last_name}},{% endblocktrans %}
+{% endif %}
+{% if title == 'dr' %}
+{% blocktrans %}Dear Dr. {{last_name}},{% endblocktrans %}
+{% endif %}
+{% if title == 'none' %}
+{% blocktrans %}Dear {{first_name}},{% endblocktrans %}
+{% endif %}
+{% endspaceless %}
+
+{% trans "Thank you for joining OSQA online community!" %}
+
+{% trans "A very brief introduction to OSQA community follows this technical information, included for your record:" %}
+{% blocktrans %}* please visit {{email_confirmation_url}} to confirm your email for the OSQA wiki
+* your OSQA login name is {{login_name}}, email address {{user_email}}.
+* password recovery information can be always found here: {{password_recovery_url}}{% endblocktrans %}
+
+{% trans "A brief introduction to the OSQA online community for the new user." %}
+
+{% blocktrans %}Sincerely,
+Adminstrator of the OSQA website.{% endblocktrans %}
+
+{% blocktrans %}P.S. If you believe that this message was sent in error please tell us
+about it by email at {{admin_email}}.{% endblocktrans %}
diff --git a/templates/mediawiki/welcome_professor_email.txt b/templates/mediawiki/welcome_professor_email.txt
new file mode 100644
index 00000000..6b05889d
--- /dev/null
+++ b/templates/mediawiki/welcome_professor_email.txt
@@ -0,0 +1,19 @@
+{% spaceless %}
+{% load i18n %}
+{% blocktrans %}Dear Professor {{last_name}},{% endblocktrans %}
+{% endspaceless %}
+
+{% trans "Thanks a lot for joining OSQA online community!" %}
+
+{% trans "A very brief introduction to OSQA community follows this technical information, included for your record:" %}
+{% blocktrans %}* please visit {{email_confirmation_url}} to confirm your email for the OSQA wiki
+* your OSQA login name is {{login_name}}, email address {{user_email}}.
+* password recovery information can be always found here: {{password_recovery_url}}{% endblocktrans %}
+
+{% trans "A brief introduction to the OSQA online community for the new professor user." %}
+
+{% blocktrans %}Sincerely,
+Adminstrator of the OSQA website.{% endblocktrans %}
+
+{% blocktrans %}P.S. If you believe that this message was sent in error please tell us
+about it by email at {{admin_email}}.{% endblocktrans %}
diff --git a/templates/notarobot.html b/templates/notarobot.html
new file mode 100644
index 00000000..698c5696
--- /dev/null
+++ b/templates/notarobot.html
@@ -0,0 +1,15 @@
+{% extends "base_content.html" %}
+{% load i18n %}
+{% block title %}{% spaceless %}{% trans "Please prove that you are a Human Being" %}{% endspaceless %}{% endblock %}
+{% block content %}
+{% comment %} this form is set up to be used in wizards {% endcomment %}
+<form name="notarobot" action="." method="POST">
+ <div>
+ {{form}}
+ </div>
+ <input type="submit" value="{% trans "I am a Human Being" %}" class="submit" style="float:left"/>
+ <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
+ {{ previous_fields|safe }}
+ </form>
+</form>
+{% endblock %}
diff --git a/templates/tag_selector.html b/templates/tag_selector.html
index 6edc5cc8..94d23f3c 100644
--- a/templates/tag_selector.html
+++ b/templates/tag_selector.html
@@ -37,6 +37,6 @@
<input id="ignoredTagAdd" type="submit" value="{% trans "Add" %}"/>
<p id="hideIgnoredTagsControl">
<input id="hideIgnoredTagsCb" type="checkbox" {% if request.user.hide_ignored_questions %}checked="checked"{% endif %} />
- <label id="hideIgnoredTagsLabel" for="hideIgnoredTags">{% trans "keep ingored questions hidden" %}</label>
+ <label id="hideIgnoredTagsLabel" for="hideIgnoredTagsCb">{% trans "keep ingored questions hidden" %}</label>
<p>
</div>
diff --git a/urls.py b/urls.py
index b27193be..3ebc19e8 100644
--- a/urls.py
+++ b/urls.py
@@ -1,6 +1,6 @@
from django.conf.urls.defaults import *
from django.utils.translation import ugettext as _
-import settings
+from django.conf import settings
from django.contrib import admin
admin.autodiscover()
diff --git a/utils/forms.py b/utils/forms.py
new file mode 100644
index 00000000..c54056ca
--- /dev/null
+++ b/utils/forms.py
@@ -0,0 +1,151 @@
+from django import forms
+import re
+from django.utils.translation import ugettext as _
+from django.utils.safestring import mark_safe
+from django.conf import settings
+from django.http import str_to_unicode
+from django.contrib.auth.models import User
+import urllib
+
+DEFAULT_NEXT = '/' + getattr(settings, 'FORUM_SCRIPT_ALIAS')
+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
+
+def get_next_url(request):
+ return clean_next(request.REQUEST.get('next'))
+
+class StrippedNonEmptyCharField(forms.CharField):
+ def clean(self,value):
+ value = value.strip()
+ if self.required and value == '':
+ raise forms.ValidationError(_('this field is required'))
+ return value
+
+class NextUrlField(forms.CharField):
+ def __init__(self):
+ super(NextUrlField,self).__init__(max_length = 255,widget = forms.HiddenInput(),required = False)
+ def clean(self,value):
+ return clean_next(value)
+
+login_form_widget_attrs = { 'class': 'required login' }
+username_re = re.compile(r'^[\w ]+$')
+
+class UserNameField(StrippedNonEmptyCharField):
+ RESERVED_NAMES = (u'fuck', u'shit', u'ass', u'sex', u'add',
+ u'edit', u'save', u'delete', u'manage', u'update', 'remove', 'new')
+ def __init__(self,db_model=User, db_field='username', must_exist=False,skip_clean=False,label=_('choose a username'),**kw):
+ self.must_exist = must_exist
+ self.skip_clean = skip_clean
+ self.db_model = db_model
+ self.db_field = db_field
+ error_messages={'required':_('user name is required'),
+ 'taken':_('sorry, this name is taken, please choose another'),
+ 'forbidden':_('sorry, this name is not allowed, please choose another'),
+ 'missing':_('sorry, there is no user with this name'),
+ 'multiple-taken':_('sorry, we have a serious error - user name is taken by several users'),
+ 'invalid':_('user name can only consist of letters, empty space and underscore'),
+ }
+ if 'error_messages' in kw:
+ error_messages.update(kw['error_messages'])
+ del kw['error_messages']
+ super(UserNameField,self).__init__(max_length=30,
+ widget=forms.TextInput(attrs=login_form_widget_attrs),
+ label=label,
+ error_messages=error_messages,
+ **kw
+ )
+
+ def clean(self,username):
+ """ validate username """
+ if self.skip_clean == True:
+ return username
+ if hasattr(self, 'user_instance') and isinstance(self.user_instance, User):
+ if username == self.user_instance.username:
+ return username
+ try:
+ username = super(UserNameField, self).clean(username)
+ except forms.ValidationError:
+ raise forms.ValidationError(self.error_messages['required'])
+ if self.required and not username_re.search(username):
+ raise forms.ValidationError(self.error_messages['invalid'])
+ if username in self.RESERVED_NAMES:
+ raise forms.ValidationError(self.error_messages['forbidden'])
+ try:
+ user = self.db_model.objects.get(
+ **{'%s' % self.db_field : username}
+ )
+ if user:
+ if self.must_exist:
+ return username
+ else:
+ raise forms.ValidationError(self.error_messages['taken'])
+ except self.db_model.DoesNotExist:
+ if self.must_exist:
+ raise forms.ValidationError(self.error_messages['missing'])
+ else:
+ return username
+ except self.db_model.MultipleObjectsReturned:
+ raise forms.ValidationError(self.error_messages['multiple-taken'])
+
+class UserEmailField(forms.EmailField):
+ def __init__(self,skip_clean=False,**kw):
+ self.skip_clean = skip_clean
+ super(UserEmailField,self).__init__(widget=forms.TextInput(attrs=dict(login_form_widget_attrs,
+ maxlength=200)), label=mark_safe(_('your email address')),
+ error_messages={'required':_('email address is required'),
+ 'invalid':_('please enter a valid email address'),
+ 'taken':_('this email is already used by someone else, please choose another'),
+ },
+ **kw
+ )
+
+ def clean(self,email):
+ """ validate if email exist in database
+ from legacy register
+ return: raise error if it exist """
+ email = super(UserEmailField,self).clean(email.strip())
+ if self.skip_clean:
+ return email
+ if settings.EMAIL_UNIQUE == True:
+ try:
+ user = User.objects.get(email = email)
+ raise forms.ValidationError(self.error_messsages['taken'])
+ except User.DoesNotExist:
+ return email
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(self.error_messages['taken'])
+ else:
+ return email
+
+class SetPasswordForm(forms.Form):
+ password1 = forms.CharField(widget=forms.PasswordInput(attrs=login_form_widget_attrs),
+ label=_('choose password'),
+ error_messages={'required':_('password is required')},
+ )
+ password2 = forms.CharField(widget=forms.PasswordInput(attrs=login_form_widget_attrs),
+ label=mark_safe(_('retype password')),
+ error_messages={'required':_('please, retype your password'),
+ 'nomatch':_('sorry, entered passwords did not match, please try again')},
+ )
+ def clean_password2(self):
+ """
+ Validates that the two password inputs match.
+
+ """
+ if 'password1' in self.cleaned_data:
+ if self.cleaned_data['password1'] == self.cleaned_data['password2']:
+ self.password = self.cleaned_data['password2']
+ self.cleaned_data['password'] = self.cleaned_data['password2']
+ return self.cleaned_data['password2']
+ else:
+ del self.cleaned_data['password2']
+ raise forms.ValidationError(self.fields['password2'].error_messages['nomatch'])
+ else:
+ return self.cleaned_data['password2']
+