summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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']
+