summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.project23
-rw-r--r--.pydevproject10
-rw-r--r--HOW_TO_DEBUG39
-rw-r--r--INSTALL13
-rw-r--r--README6
-rw-r--r--TODO6
-rw-r--r--cnprog.wsgi8
-rw-r--r--context.py1
-rw-r--r--cron/send_email_alerts6
-rw-r--r--django_authopenid/external_login.py103
-rw-r--r--django_authopenid/forms.py184
-rw-r--r--django_authopenid/middleware.py4
-rw-r--r--django_authopenid/models.py4
-rwxr-xr-x[-rw-r--r--]django_authopenid/urls.py33
-rw-r--r--django_authopenid/util.py18
-rwxr-xr-x[-rw-r--r--]django_authopenid/views.py309
-rw-r--r--dos2unix.sh12
-rw-r--r--drop-all.sql39
-rwxr-xr-xfbconnect/__init__.py0
-rwxr-xr-xfbconnect/fb.py89
-rwxr-xr-xfbconnect/forms.py8
-rwxr-xr-xfbconnect/models.py6
-rwxr-xr-xfbconnect/pjson.py313
-rwxr-xr-xfbconnect/tests.py23
-rwxr-xr-xfbconnect/urls.py16
-rwxr-xr-xfbconnect/views.py97
-rw-r--r--forum/feed.py6
-rw-r--r--forum/forms.py54
-rw-r--r--forum/forms.py.orig352
-rw-r--r--forum/management/commands/clean_award_badges.py7
-rw-r--r--forum/management/commands/message_to_everyone.py12
-rw-r--r--forum/management/commands/multi_award_badges.py47
-rw-r--r--forum/management/commands/once_award_badges.py23
-rw-r--r--forum/management/commands/send_email_alerts.py106
-rw-r--r--forum/management/commands/subscribe_everyone.py9
-rw-r--r--forum/managers.py15
-rw-r--r--forum/models.py19
-rw-r--r--forum/sitemap.py3
-rw-r--r--forum/templatetags/extra_tags.py7
-rw-r--r--forum/templatetags/smart_if.py802
-rw-r--r--forum/urls.py3
-rw-r--r--forum/views.py384
-rw-r--r--locale/en/LC_MESSAGES/django.po2
-rw-r--r--middleware/anon_user.py4
-rw-r--r--middleware/cancel.py2
-rw-r--r--migration7
-rw-r--r--osqa.wsgi.dist7
-rw-r--r--pgfulltext/__init__.py0
-rw-r--r--pgfulltext/management.py23
-rwxr-xr-x[-rw-r--r--]settings.py54
-rwxr-xr-x[-rw-r--r--]settings_local.py.dist114
-rw-r--r--sql_scripts/100108_upgrade_ef.sql4
-rw-r--r--sql_scripts/badges.sql37
-rw-r--r--sql_scripts/drop-all-tables.sh (renamed from drop-all-tables.sh)0
-rw-r--r--sql_scripts/drop-auth.sql8
-rw-r--r--sql_scripts/pg_fts_install.sql38
-rwxr-xr-xsql_scripts/update_2010_01_23.sql9
-rw-r--r--tables.sql440
-rw-r--r--templates/about.html9
-rw-r--r--templates/authopenid/complete.html24
-rw-r--r--templates/authopenid/external_legacy_login_info.html8
-rwxr-xr-x[-rw-r--r--]templates/authopenid/signin.html359
-rw-r--r--templates/authopenid/signup.html14
-rw-r--r--templates/badge.html2
-rw-r--r--templates/badges.html4
-rwxr-xr-x[-rw-r--r--]templates/base.html8
-rw-r--r--templates/base_content.html6
-rw-r--r--templates/content/images/logo.pngbin1902 -> 2081 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/jquery-openid/openid.css6
-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.js150
-rw-r--r--templates/content/js/com.cnprog.tag_selector.js45
-rw-r--r--templates/content/js/com.cnprog.utils.js59
-rw-r--r--templates/content/style/style.css592
-rwxr-xr-xtemplates/fbconnect/xd_receiver.html1
-rw-r--r--templates/footer.html23
-rw-r--r--templates/header.html6
-rw-r--r--templates/notarobot.html15
-rw-r--r--templates/question.html513
-rw-r--r--templates/questions.html238
-rw-r--r--templates/tag_selector.html2
-rw-r--r--templates/user_edit.html190
-rw-r--r--urls.py6
-rw-r--r--utils/forms.py151
88 files changed, 3913 insertions, 2494 deletions
diff --git a/.project b/.project
new file mode 100644
index 00000000..8e56b007
--- /dev/null
+++ b/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>osqa</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.python.pydev.PyDevBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.python.pydev.pythonNature</nature>
+ <nature>org.eclipse.wst.jsdt.core.jsNature</nature>
+ </natures>
+</projectDescription>
diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 00000000..f7f3fd1a
--- /dev/null
+++ b/.pydevproject
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?eclipse-pydev version="1.0"?>
+
+<pydev_project>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
+<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
+<path>/osqa</path>
+</pydev_pathproperty>
+</pydev_project>
diff --git a/HOW_TO_DEBUG b/HOW_TO_DEBUG
new file mode 100644
index 00000000..ba36198a
--- /dev/null
+++ b/HOW_TO_DEBUG
@@ -0,0 +1,39 @@
+1) LOGGING
+Please remember that log files may contain plaintext passwords, etc.
+
+Please do not add print statements - at least do not commit them to git
+because in some environments printing to stdout causes errors
+
+Instead use python logging this way:
+--------------------------------
+#somewere on top of file
+import logging
+
+#anywhere below
+logging.debug('this maybe works')
+logging.error('have big error!')
+#or even
+logging.debug('') #this will add time, line number, function and file record
+#sometimes useful record for call tracing on its own
+#etc - take a look at http://docs.python.org/library/logging.html
+-------------------------------
+
+in OSQA logging is currently set up in settings_local.py.dist
+please update it if you need - in older revs logging strings have less info
+
+messages of interest can be grepped out of the log file by module/file/function name
+e.g. to take out all django_authopenid logs run:
+>grep 'osqa\/django_authopenid' log/django.osqa.log | sed 's/^.*MSG: //'
+in the example above 'sed' call truncates out a long prefix
+and makes output look more meaningful
+
+2) DJANGO DEBUG TOOLBAR
+osqa works with django debug toolbar
+if debugging under apache server, check
+that debug toolbar media is loaded correctly
+if toolbar is enabled but you do not see it, possibly some Alias statement
+in apache config is wrong in your VirtualHost or elsewhere
+
+3) If you discover new debugging techniques, please add here.
+Possible areas to improve - at this point there is no SQL query logging,
+as well as request data and http header.
diff --git a/INSTALL b/INSTALL
index 7de10871..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
@@ -203,6 +212,10 @@ WSGIPythonEggs /var/python/eggs #must be readable and writable by apache
adjust other settings that have SPHINX_* prefix accordingly
remember that there must be trailing comma in parentheses for
SHPINX_SEARCH_INDICES tuple - particlarly with just one item!
+
+ in settings.py look for INSTALLED_APPS
+ and uncomment #'djangosphinx',
+
6. Email subscriptions
diff --git a/README b/README
new file mode 100644
index 00000000..2a209b7a
--- /dev/null
+++ b/README
@@ -0,0 +1,6 @@
+This is OSQA project - open source Q&A system
+
+Demo site is http://osqa.net
+
+OSQA is based on code of CNPROG, originally created by Mike Chen and Sailing Cai.
+
diff --git a/TODO b/TODO
index 372e714f..7c37071d 100644
--- a/TODO
+++ b/TODO
@@ -1,3 +1,3 @@
-* per-tag email subscriptions
-* make sorting tabs work in question search
-* allow multiple logins to the same account
+* per-tag email subscriptions
+* make sorting tabs work in question search - done
+* allow multiple logins to the same account
diff --git a/cnprog.wsgi b/cnprog.wsgi
deleted file mode 100644
index bd3745ee..00000000
--- a/cnprog.wsgi
+++ /dev/null
@@ -1,8 +0,0 @@
-#example wsgi setup script
-import os
-import sys
-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/context.py b/context.py
index 26d326a7..ca6ca163 100644
--- a/context.py
+++ b/context.py
@@ -2,6 +2,7 @@ from django.conf import settings
def application_settings(context):
my_settings = {
'APP_TITLE' : settings.APP_TITLE,
+ 'APP_SHORT_NAME' : settings.APP_SHORT_NAME,
'APP_URL' : settings.APP_URL,
'APP_KEYWORDS' : settings.APP_KEYWORDS,
'APP_DESCRIPTION' : settings.APP_DESCRIPTION,
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/external_login.py b/django_authopenid/external_login.py
deleted file mode 100644
index bd49c009..00000000
--- a/django_authopenid/external_login.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#this file contains stub functions that can be extended to support
-#connect legacy login with external site
-import settings
-from django_authopenid.models import ExternalLoginData
-import httplib
-import urllib
-import Cookie
-import cookielib
-from django import forms
-import xml.dom.minidom as xml
-import logging
-
-def login(request,user):
- """performs the additional external login operation
- """
- pass
-
-def set_login_cookies(response,user):
- #should be unique value by design
- try:
- eld = ExternalLoginData.objects.get(user=user)
-
- data = eld.external_session_data
- dom = xml.parseString(data)
- login_response = dom.getElementsByTagName('login')[0]
- userid = login_response.getAttribute('lguserid')
- username = login_response.getAttribute('lgusername')
- token = login_response.getAttribute('lgtoken')
- prefix = login_response.getAttribute('cookieprefix').decode('utf-8')
- sessionid = login_response.getAttribute('sessionid')
-
- c = {}
- c[prefix + 'UserName'] = username
- c[prefix + 'UserID'] = userid
- c[prefix + 'Token'] = token
- c[prefix + '_session'] = sessionid
-
- #custom code that copies cookies from external site
- #not sure how to set paths and domain of cookies here
- for key in c:
- if c[key]:
- response.set_cookie(str(key),value=str(c[key]))
- except ExternalLoginData.DoesNotExist:
- #this must be an OpenID login
- pass
-
-#function to perform external logout, if needed
-def logout(request):
- pass
-
-#should raise User.DoesNotExist or pass
-def clean_username(username):
- return username
-
-def check_password(username,password):
- """connects to external site and submits username/password pair
- return True or False depending on correctness of login
- saves remote unique id and remote session data in table ExternalLoginData
- may raise forms.ValidationError
- """
- host = settings.EXTERNAL_LEGACY_LOGIN_HOST
- port = settings.EXTERNAL_LEGACY_LOGIN_PORT
- ext_site = httplib.HTTPConnection(host,port)
-
- #custom code. this one does authentication through
- #MediaWiki API
- params = urllib.urlencode({'action':'login','format':'xml',
- 'lgname':username,'lgpassword':password})
- headers = {"Content-type": "application/x-www-form-urlencoded",
- 'User-Agent':"User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7",
- "Accept": "text/xml"}
- ext_site.request("POST","/wiki/api.php",params,headers)
- response = ext_site.getresponse()
- if response.status != 200:
- raise forms.ValidationError('error ' + response.status + ' ' + response.reason)
- data = response.read().strip()
- ext_site.close()
-
- dom = xml.parseString(data)
- login = dom.getElementsByTagName('login')[0]
- result = login.getAttribute('result')
-
- if result == 'Success':
- username = login.getAttribute('lgusername')
- try:
- eld = ExternalLoginData.objects.get(external_username=username)
- except ExternalLoginData.DoesNotExist:
- eld = ExternalLoginData()
- eld.external_username = username
- eld.external_session_data = data
- eld.save()
- return True
- else:
- error = login.getAttribute('details')
- raise forms.ValidationError(error)
- return False
-
-def createuser(username,email,password):
- pass
-
-#retrieve email address
-def get_email(username,password):
- return ''
diff --git a/django_authopenid/forms.py b/django_authopenid/forms.py
index d4482751..5ec21c1c 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,99 +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 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}))
@@ -168,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):
@@ -176,17 +85,24 @@ 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):
"""
- this clean function actuall cleans username and password
+ this clean function actually cleans username and password
test if password is valid for this username
this is really the "authenticate" function
@@ -201,11 +117,10 @@ class ClassicLoginForm(forms.Form):
self.user_cache = None
if username and password:
-
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:
@@ -271,7 +186,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):
@@ -299,53 +214,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:
@@ -359,17 +240,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 """
@@ -413,8 +283,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/middleware.py b/django_authopenid/middleware.py
index 2900d54c..2be8da90 100644
--- a/django_authopenid/middleware.py
+++ b/django_authopenid/middleware.py
@@ -2,6 +2,8 @@
from django_authopenid import mimeparse
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
+from django.conf import settings
+import logging
__all__ = ["OpenIDMiddleware"]
@@ -12,6 +14,7 @@ class OpenIDMiddleware(object):
"""
def process_request(self, request):
request.openid = request.session.get('openid', None)
+ logging.debug('openid in session is: %s' % str(request.openid))
def process_response(self, request, response):
if response.status_code != 200 or len(response.content) < 200:
@@ -20,5 +23,6 @@ class OpenIDMiddleware(object):
if path == "/" and request.META.has_key('HTTP_ACCEPT') and \
mimeparse.best_match(['text/html', 'application/xrds+xml'],
request.META['HTTP_ACCEPT']) == 'application/xrds+xml':
+ logging.debug('redirecting to yadis_xrdf:%s' % reverse('yadis_xrdf'))
return HttpResponseRedirect(reverse('yadis_xrdf'))
return response
diff --git a/django_authopenid/models.py b/django_authopenid/models.py
index 7b2e1c02..a12c2fec 100644
--- a/django_authopenid/models.py
+++ b/django_authopenid/models.py
@@ -71,6 +71,10 @@ class UserPasswordQueue(models.Model):
return self.user.username
class ExternalLoginData(models.Model):
+ """this class was added by Evgeny to associate
+ external authentication user with django user
+ probably it just does not belong here... (EF)
+ """
external_username = models.CharField(max_length=40, unique=True, null=False)
external_session_data = models.TextField()
user = models.ForeignKey(User, null=True)
diff --git a/django_authopenid/urls.py b/django_authopenid/urls.py
index 112cbbe1..e1986d19 100644..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 2b9d44a2..c7c9d8f3 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
@@ -15,28 +14,17 @@ try:
except:
from yadis import xri
-import time, base64, hashlib, operator
-import urllib
+import time, base64, hashlib, operator, logging
+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('/') and len(next)>1:
- 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):
+ logging.debug('init janrain openid object')
self.openid = openid_
self.issued = issued
self.attrs = attrs or {}
diff --git a/django_authopenid/views.py b/django_authopenid/views.py
index feb6b58f..16a78864 100644..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
@@ -60,37 +60,37 @@ except ImportError:
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
#2) login and get new session key
_login(request,user)
#3) send signal with old session key as argument
+ logging.debug('logged in user %s with session key %s' % (user.username, session_key))
user_logged_in.send(user=user,session_key=session_key,sender=None)
def logout(request):
from django.contrib.auth import logout as _logout#for login I've added wrapper below - called login
_logout(request)
if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- external_login.logout(request)
+ EXTERNAL_LOGIN_APP.api.logout(request)
def get_url_host(request):
if request.is_secure():
@@ -116,17 +116,22 @@ def ask_openid(request, openid_url, redirect_to, on_failure=None,
settings, 'OPENID_DISALLOW_INAMES', False
):
msg = _("i-names are not supported")
+ logging.debug('openid failed becaise i-names are not supported')
return on_failure(request, msg)
consumer = Consumer(request.session, DjangoOpenIDStore())
try:
auth_request = consumer.begin(openid_url)
except DiscoveryFailure:
msg = _(u"OpenID %(openid_url)s is invalid" % {'openid_url':openid_url})
+ logging.debug(msg)
return on_failure(request, msg)
+ logging.debug('openid seemed to work')
if sreg_request:
+ logging.debug('adding sreg_request - wtf it is?')
auth_request.addExtension(sreg_request)
redirect_url = auth_request.redirectURL(trust_root, redirect_to)
+ logging.debug('redirecting to %s' % redirect_url)
return HttpResponseRedirect(redirect_url)
def complete(request, on_success=None, on_failure=None, return_to=None):
@@ -134,31 +139,41 @@ def complete(request, on_success=None, on_failure=None, return_to=None):
on_success = on_success or default_on_success
on_failure = on_failure or default_on_failure
+ logging.debug('in django_authopenid.complete')
+
consumer = Consumer(request.session, DjangoOpenIDStore())
# make sure params are encoded in utf8
params = dict((k,smart_unicode(v)) for k, v in request.GET.items())
openid_response = consumer.complete(params, return_to)
if openid_response.status == SUCCESS:
+ logging.debug('SUCCESS')
return on_success(request, openid_response.identity_url,
openid_response)
elif openid_response.status == CANCEL:
+ logging.debug('CANCEL')
return on_failure(request, 'The request was canceled')
elif openid_response.status == FAILURE:
+ logging.debug('FAILURE')
return on_failure(request, openid_response.message)
elif openid_response.status == SETUP_NEEDED:
+ logging.debug('SETUP NEEDED')
return on_failure(request, 'Setup needed')
else:
+ logging.debug('BAD OPENID STATUS')
assert False, "Bad openid status: %s" % openid_response.status
def default_on_success(request, identity_url, openid_response):
""" default action on openid signin success """
+ logging.debug('')
request.session['openid'] = from_openid_response(openid_response)
+ logging.debug('performing default action on openid success %s' % get_next_url(request))
return HttpResponseRedirect(get_next_url(request))
def default_on_failure(request, message):
""" default failure action on signin """
- return render('openid_failure.html', {
+ logging.debug('default openid failure action')
+ return render_to_response('openid_failure.html', {
'message': message
})
@@ -177,21 +192,23 @@ def signin(request,newquestion=False,newanswer=False):
"""
signin page. It manages the legacy authentification (user/password)
and openid authentification
-
+
url: /signin/
template : authopenid/signin.htm
"""
+ logging.debug('in signin view')
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})
- if request.POST:
+ if request.method == 'POST':
#'blogin' - password login
if 'blogin' in request.POST.keys():
+ logging.debug('processing classic login form submission')
form_auth = ClassicLoginForm(request.POST)
if form_auth.is_valid():
#have login and password and need to login through external website
@@ -201,52 +218,49 @@ def signin(request,newquestion=False,newanswer=False):
next = form_auth.cleaned_data['next']
if form_auth.get_user() == None:
#need to create internal user
-
+
#1) save login and password temporarily in session
request.session['external_username'] = username
request.session['external_password'] = password
-
- #2) see if username clashes with some existing user
+
+ #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
user = form_auth.get_user()
login(request, user)
return HttpResponseRedirect(get_next_url(request))
-
+
elif 'bnewaccount' in request.POST.keys():
+ logging.debug('processing classic (login/password) create account form submission')
#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 +268,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 +280,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,14 +297,16 @@ 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
-
+
elif 'bsignin' in request.POST.keys() or 'openid_username' in request.POST.keys():
+ logging.debug('processing signin with openid submission')
form_signin = OpenidSigninForm(request.POST)
if form_signin.is_valid():
+ logging.debug('OpenidSigninForm is valid')
next = form_signin.cleaned_data['next']
sreg_req = sreg.SRegRequest(optional=['nickname', 'email'])
redirect_to = "%s%s?%s" % (
@@ -301,13 +319,17 @@ def signin(request,newquestion=False,newanswer=False):
redirect_to,
on_failure=signin_failure,
sreg_request=sreg_req)
-
-
+ else:
+ logging.debug('OpenidSigninForm is NOT valid! -> redisplay login view')
+
#if request is GET
+ if request.method == 'GET':
+ logging.debug('request method was GET')
question = None
if newquestion == True:
from forum.models import AnonymousQuestion as AQ
session_key = request.session.session_key
+ logging.debug('retrieving anonymously posted question associated with session %s' % session_key)
qlist = AQ.objects.filter(session_key=session_key).order_by('-added_at')
if len(qlist) > 0:
question = qlist[0]
@@ -315,47 +337,59 @@ def signin(request,newquestion=False,newanswer=False):
if newanswer == True:
from forum.models import AnonymousAnswer as AA
session_key = request.session.session_key
+ logging.debug('retrieving posted answer associated with session %s' % session_key)
alist = AA.objects.filter(session_key=session_key).order_by('-added_at')
if len(alist) > 0:
answer = alist[0]
-
- return render('authopenid/signin.html', {
+
+ logging.debug('showing signin view')
+ return render_to_response('authopenid/signin.html', {
'question':question,
'answer':answer,
'form1': form_auth,
'form2': form_signin,
'msg': request.GET.get('msg',''),
'sendpw_url': reverse('user_sendpw'),
+ 'fb_api_key': settings.FB_API_KEY,
}, context_instance=RequestContext(request))
def complete_signin(request):
""" in case of complete signin with openid """
+ logging.debug('')#blank log just for the trace
return complete(request, signin_success, signin_failure,
get_url_host(request) + reverse('user_complete_signin'))
def signin_success(request, identity_url, openid_response):
"""
openid signin success.
-
+
If the openid is already registered, the user is redirected to
url set par next or in settings with OPENID_REDIRECT_NEXT variable.
If none of these urls are set user is redirectd to /.
-
+
if openid isn't registered user is redirected to register page.
"""
+ logging.debug('')
openid_ = from_openid_response(openid_response) #create janrain OpenID object
request.session['openid'] = openid_
try:
+ logging.debug('trying to get user associated with this openid...')
rel = UserAssociation.objects.get(openid_url__exact = str(openid_))
+ logging.debug('success')
except:
+ logging.debug('failed --> try to register brand new user')
# try to register this new user
return register(request)
user_ = rel.user
if user_.is_active:
user_.backend = "django.contrib.auth.backends.ModelBackend"
+ logging.debug('user is active --> attached django auth ModelBackend --> calling login')
login(request, user_)
-
+ logging.debug('success')
+ else:
+ logging.debug('user is inactive, do not log them in')
+ logging.debug('redirecting to %s' % get_next_url(request))
return HttpResponseRedirect(get_next_url(request))
def is_association_exist(openid_url):
@@ -365,29 +399,32 @@ def is_association_exist(openid_url):
uassoc = UserAssociation.objects.get(openid_url__exact = openid_url)
except:
is_exist = False
+ logging.debug(str(is_exist))
return is_exist
@not_authenticated
def register(request):
"""
register an openid.
-
+
If user is already a member he can associate its openid with
its account.
-
+
A new account could also be created and automaticaly associated
to the openid.
-
+
url : /complete/
-
+
template : authopenid/complete.html
"""
-
+
+ logging.debug('')
openid_ = request.session.get('openid', None)
next = get_next_url(request)
if not openid_:
+ logging.debug('oops, no openid in session --> go back to signin')
return HttpResponseRedirect(reverse('user_signin') + '?next=%s' % next)
-
+
nickname = openid_.sreg.get('nickname', '')
email = openid_.sreg.get('email', '')
form1 = OpenidRegisterForm(initial={
@@ -399,56 +436,74 @@ def register(request):
'next': next,
'username': nickname,
})
- email_feeds_form = EditUserEmailFeedsForm()
-
+ email_feeds_form = SimpleEmailSubscribeForm()
+
user_ = None
is_redirect = False
- if request.POST:
+ logging.debug('request method is %s' % request.method)
+ if request.method == 'POST':
if 'bnewaccount' in request.POST.keys():
+ logging.debug('trying to create new account associated with openid')
form1 = OpenidRegisterForm(request.POST)
- email_feeds_form = EditUserEmailFeedsForm(request.POST)
- if form1.is_valid() and email_feeds_form.is_valid():
+ email_feeds_form = SimpleEmailSubscribeForm(request.POST)
+ if not form1.is_valid():
+ logging.debug('OpenidRegisterForm is INVALID')
+ elif not email_feeds_form.is_valid():
+ logging.debug('SimpleEmailSubscribeForm is INVALID')
+ else:
+ logging.debug('OpenidRegisterForm and SimpleEmailSubscribeForm are valid')
next = form1.cleaned_data['next']
is_redirect = True
+ logging.debug('creatng new django user %s ...' % form1.cleaned_data['username'])
tmp_pwd = User.objects.make_random_password()
user_ = User.objects.create_user(form1.cleaned_data['username'],
form1.cleaned_data['email'], tmp_pwd)
-
+
user_.set_unusable_password()
# make association with openid
- uassoc = UserAssociation(openid_url=str(openid_),
- user_id=user_.id)
+ logging.debug('creating new openid user association %s <--> %s' \
+ % (user_.username, str(openid_)))
+ uassoc = UserAssociation(openid_url=str(openid_), user_id=user_.id)
uassoc.save()
-
+
# login
user_.backend = "django.contrib.auth.backends.ModelBackend"
+ logging.debug('logging the user in')
login(request, user_)
+ logging.debug('saving email feed settings')
email_feeds_form.save(user_)
elif 'bverify' in request.POST.keys():
+ logging.debug('processing OpenidVerify form')
form2 = OpenidVerifyForm(request.POST)
if form2.is_valid():
+ logging.debug('form is valid')
is_redirect = True
next = form2.cleaned_data['next']
user_ = form2.get_user()
-
+ logging.debug('creating new openid user association %s <--> %s' \
+ % (user_.username, str(openid_)))
uassoc = UserAssociation(openid_url=str(openid_),
user_id=user_.id)
uassoc.save()
+ logging.debug('logging the user in')
login(request, user_)
-
+
#check if we need to post a question that was added anonymously
#this needs to be a function call becase this is also done
#if user just logged in and did not need to create the new account
if user_ != None:
if settings.EMAIL_VALIDATION == 'on':
+ logging.debug('sending email validation')
send_new_email_key(user_,nomessage=True)
output = validation_email_sent(request)
set_email_validation_message(user_) #message set after generating view
return output
if user_.is_authenticated():
+ logging.debug('success, send user to main page')
return HttpResponseRedirect(reverse('index'))
else:
+ logging.debug('have really strange error')
raise Exception('openid login failed')#should not ever get here
openid_str = str(openid_)
@@ -456,7 +511,7 @@ def register(request):
base_url = bits[2] #assume this is base url
url_bits = base_url.split('.')
provider_name = url_bits[-2].lower()
-
+
providers = {'yahoo':'<font color="purple">Yahoo!</font>',
'flickr':'<font color="#0063dc">flick</font><font color="#ff0084">r</font>&trade;',
'google':'Google&trade;',
@@ -465,10 +520,12 @@ def register(request):
}
if provider_name not in providers:
provider_logo = provider_name
+ logging.error('openid provider named "%s" has no pretty customized logo' % provider_name)
else:
provider_logo = providers[provider_name]
-
- return render('authopenid/complete.html', {
+
+ logging.debug('printing authopenid/complete.html output')
+ return render_to_response('authopenid/complete.html', {
'form1': form1,
'form2': form2,
'email_feeds_form': email_feeds_form,
@@ -485,11 +542,12 @@ def signin_failure(request, message):
template : "authopenid/signin.html"
"""
+ logging.debug('')
next = get_next_url(request)
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,
@@ -499,18 +557,21 @@ def signin_failure(request, message):
def signup(request):
"""
signup page. Create a legacy account
-
+
url : /signup/"
-
+
templates: authopenid/signup.html, authopenid/confirm_email.txt
"""
+ logging.debug('')
if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
+ logging.debug('handling external legacy login registration')
+ return HttpResponseRedirect(reverse('user_external_legacy_login_signup'))
next = get_next_url(request)
- if request.POST:
+ logging.debug('request method was %s' % request.method)
+ if request.method == '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()
form2_is_valid = email_feeds_form.is_valid()
@@ -519,11 +580,11 @@ def signup(request):
username = form.cleaned_data['username']
password = form.cleaned_data['password1']
email = form.cleaned_data['email']
-
+
user_ = User.objects.create_user( username,email,password )
if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- external_login.create_user(username,email,password)
-
+ EXTERNAL_LOGIN_APP.api.create_user(username,email,password)
+
user_.backend = "django.contrib.auth.backends.ModelBackend"
login(request, user_)
email_feeds_form.save(user_)
@@ -541,11 +602,15 @@ def signup(request):
message = message_template.render(message_context)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
[user_.email])
+ logging.debug('new user with login and password created!')
return HttpResponseRedirect(next)
+ else:
+ logging.debug('create classic account forms were invalid')
else:
form = ClassicRegisterForm(initial={'next':next})
- email_feeds_form = EditUserEmailFeedsForm()
- return render('authopenid/signup.html', {
+ email_feeds_form = SimpleEmailSubscribeForm()
+ logging.debug('printing legacy signup form')
+ return render_to_response('authopenid/signup.html', {
'form': form,
'email_feeds_form': email_feeds_form
}, context_instance=RequestContext(request))
@@ -558,19 +623,24 @@ def signout(request):
url : /signout/"
"""
+ logging.debug('')
try:
+ logging.debug('deleting openid session var')
del request.session['openid']
except KeyError:
+ logging.debug('failed')
pass
logout(request)
+ logging.debug('user logged out')
return HttpResponseRedirect(get_next_url(request))
def xrdf(request):
url_host = get_url_host(request)
+ logging.debug('what does this do??')
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))
@@ -587,6 +657,7 @@ def account_settings(request):
template : authopenid/settings.html
"""
+ logging.debug('')
msg = request.GET.get('msg', '')
is_openid = True
@@ -598,7 +669,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))
@@ -611,6 +682,7 @@ def changepw(request):
url : /changepw/
template: authopenid/changepw.html
"""
+ logging.debug('')
user_ = request.user
if user_.has_usable_password():
@@ -632,7 +704,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):
@@ -696,7 +768,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')},
@@ -711,7 +783,8 @@ 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',
+ logging.debug('')
+ return render_to_response('authopenid/changeemail.html',
{ 'email': request.user.email,
'change_email_url': reverse('user_changeemail'),
'action_type': 'validate', },
@@ -722,6 +795,7 @@ def verifyemail(request,id=None,key=None):
view that is shown when user clicks email validation link
url = /email/verify/{{user.id}}/{{user.email_key}}/
"""
+ logging.debug('')
if settings.EMAIL_VALIDATION != 'off':
user = User.objects.get(id=id)
if user:
@@ -729,9 +803,11 @@ 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))
+ else:
+ logging.error('hmm, no user found for email validation message - foul play?')
raise Http404
@login_required
@@ -743,6 +819,7 @@ def changeemail(request, action='change'):
template : authopenid/changeemail.html
"""
+ logging.debug('')
msg = request.GET.get('msg', None)
extension_args = {}
user_ = request.user
@@ -772,7 +849,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,
@@ -787,6 +864,7 @@ def changeemail(request, action='change'):
return output
def emailopenid_success(request, identity_url, openid_response):
+ logging.debug('')
openid_ = from_openid_response(openid_response)
user_ = request.user
@@ -816,6 +894,7 @@ def emailopenid_success(request, identity_url, openid_response):
def emailopenid_failure(request, message):
+ logging.debug('')
redirect_to = "%s?msg=%s" % (
reverse('user_changeemail'), urlquote_plus(message))
return HttpResponseRedirect(redirect_to)
@@ -830,6 +909,7 @@ def changeopenid(request):
template: authopenid/changeopenid.html
"""
+ logging.error('change openid view - never tested it yet!!!')
extension_args = {}
openid_url = ''
@@ -856,13 +936,14 @@ 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
}, context_instance=RequestContext(request))
def changeopenid_success(request, identity_url, openid_response):
+ logging.error('never tested this worflow')
openid_ = from_openid_response(openid_response)
is_exist = True
try:
@@ -896,6 +977,7 @@ def changeopenid_success(request, identity_url, openid_response):
def changeopenid_failure(request, message):
+ logging.error('never tested this workflow')
redirect_to = "%s?msg=%s" % (
reverse('user_changeopenid'),
urlquote_plus(message))
@@ -906,16 +988,17 @@ def delete(request):
"""
delete view. Allow user to delete its account. Password/openid are required to
confirm it. He should also check the confirm checkbox.
-
+
url : /delete
-
+
template : authopenid/delete.html
"""
-
+ logging.error('deleting account - never tested this')
+
extension_args = {}
user_ = request.user
-
+
redirect_to = get_url_host(request) + reverse('user_delete')
if request.POST:
form = DeleteForm(request.POST, user=user_)
@@ -931,14 +1014,15 @@ def delete(request):
redirect_to)
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))
def deleteopenid_success(request, identity_url, openid_response):
+ logging.error('never tested this')
openid_ = from_openid_response(openid_response)
user_ = request.user
@@ -964,29 +1048,38 @@ def deleteopenid_success(request, identity_url, openid_response):
def deleteopenid_failure(request, message):
+ logging.error('never tested this')
redirect_to = "%s?msg=%s" % (reverse('user_delete'), urlquote_plus(message))
return HttpResponseRedirect(redirect_to)
def external_legacy_login_info(request):
- return render('authopenid/external_legacy_login_info.html', context_instance=RequestContext(request))
+ logging.debug('maybe this view does not belong in this library')
+ 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):
"""
send a new password to the user. It return a mail with
a new pasword and a confirm link in. To activate the
new password, the user should click on confirm link.
-
+
url : /sendpw/
-
+
templates : authopenid/sendpw_email.txt, authopenid/sendpw.html
"""
+ logging.debug('')
if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
+ logging.debug('delegating to view dealing with external password recovery')
return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
-
+
msg = request.GET.get('msg','')
- if request.POST:
+ logging.debug('request method is %s' % request.method)
+ if request.method == 'POST':
form = EmailPasswordForm(request.POST)
if form.is_valid():
+ logging.debug('EmailPasswordForm is valid')
new_pw = User.objects.make_random_password()
confirm_key = UserPasswordQueue.objects.get_new_confirm_key()
try:
@@ -1005,6 +1098,7 @@ def sendpw(request):
message_template = loader.get_template(
'authopenid/sendpw_email.txt')
key_link = settings.APP_URL + reverse('user_confirmchangepw') + '?key=' + confirm_key
+ logging.debug('emailing new password for %s' % form.user_cache.username)
message_context = Context({
'site_url': settings.APP_URL + reverse('index'),
'key_link': key_link,
@@ -1017,13 +1111,13 @@ def sendpw(request):
msg = _("A new password and the activation link were sent to your email address.")
else:
form = EmailPasswordForm()
-
- return render('authopenid/sendpw.html', {
+
+ logging.debug('showing reset password form')
+ return render_to_response('authopenid/sendpw.html', {
'form': form,
'msg': msg
}, context_instance=RequestContext(request))
-
def confirmchangepw(request):
"""
view to set new password when the user click on confirm link
@@ -1031,14 +1125,16 @@ def confirmchangepw(request):
replace old password with new password and remove confirm
ley from the queue. Then it redirect the user to signin
page.
-
+
url : /sendpw/confirm/?key
-
+
"""
+ logging.debug('')
confirm_key = request.GET.get('key', '')
if not confirm_key:
+ logging.error('someone called confirm password without a key!')
return HttpResponseRedirect(reverse('index'))
-
+
try:
uqueue = UserPasswordQueue.objects.get(
confirm_key__exact=confirm_key
@@ -1046,25 +1142,28 @@ def confirmchangepw(request):
except:
msg = _("Could not change password. Confirmation key '%s'\
is not registered." % confirm_key)
+ logging.error(msg)
redirect = "%s?msg=%s" % (
reverse('user_sendpw'), urlquote_plus(msg))
return HttpResponseRedirect(redirect)
-
+
try:
user_ = User.objects.get(id=uqueue.user.id)
except:
msg = _("Can not change password. User don't exist anymore \
in our database.")
+ logging.error(msg)
redirect = "%s?msg=%s" % (reverse('user_sendpw'),
urlquote_plus(msg))
return HttpResponseRedirect(redirect)
-
+
user_.set_password(uqueue.new_password)
user_.save()
uqueue.delete()
msg = _("Password changed for %s. You may now sign in." %
user_.username)
+ logging.debug(msg)
redirect = "%s?msg=%s" % (reverse('user_signin'),
urlquote_plus(msg))
-
+
return HttpResponseRedirect(redirect)
diff --git a/dos2unix.sh b/dos2unix.sh
new file mode 100644
index 00000000..2864426a
--- /dev/null
+++ b/dos2unix.sh
@@ -0,0 +1,12 @@
+#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'`
+dos2unix `find . -name '*.css'`
+dos2unix `find . -name '*.txt'`
+dos2unix `find ./sphinx -type f`
+dos2unix `find ./cron -type f`
+dos2unix settings_local.py.dist
+dos2unix README
+dos2unix INSTALL
diff --git a/drop-all.sql b/drop-all.sql
deleted file mode 100644
index 52feb337..00000000
--- a/drop-all.sql
+++ /dev/null
@@ -1,39 +0,0 @@
-DROP TABLE IF EXISTS `activity`;
-DROP TABLE IF EXISTS `answer`;
-DROP TABLE IF EXISTS `answer_revision`;
-DROP TABLE IF EXISTS `auth_group`;
-DROP TABLE IF EXISTS `auth_group_permissions`;
-DROP TABLE IF EXISTS `auth_message`;
-DROP TABLE IF EXISTS `auth_permission`;
-DROP TABLE IF EXISTS `auth_user`;
-DROP TABLE IF EXISTS `auth_user_groups`;
-DROP TABLE IF EXISTS `auth_user_user_permissions`;
-DROP TABLE IF EXISTS `award`;
-DROP TABLE IF EXISTS `badge`;
-DROP TABLE IF EXISTS `book`;
-DROP TABLE IF EXISTS `book_author_info`;
-DROP TABLE IF EXISTS `book_author_rss`;
-DROP TABLE IF EXISTS `book_question`;
-DROP TABLE IF EXISTS `comment`;
-DROP TABLE IF EXISTS `django_admin_log`;
-DROP TABLE IF EXISTS `django_authopenid_association`;
-DROP TABLE IF EXISTS `django_authopenid_externallogindata`;
-DROP TABLE IF EXISTS `django_authopenid_nonce`;
-DROP TABLE IF EXISTS `django_authopenid_userassociation`;
-DROP TABLE IF EXISTS `django_authopenid_userpasswordqueue`;
-DROP TABLE IF EXISTS `django_content_type`;
-DROP TABLE IF EXISTS `django_session`;
-DROP TABLE IF EXISTS `django_site`;
-DROP TABLE IF EXISTS `favorite_question`;
-DROP TABLE IF EXISTS `flagged_item`;
-DROP TABLE IF EXISTS `forum_anonymousanswer`;
-DROP TABLE IF EXISTS `forum_anonymousemail`;
-DROP TABLE IF EXISTS `forum_anonymousquestion`;
-DROP TABLE IF EXISTS `forum_emailfeed`;
-DROP TABLE IF EXISTS `forum_emailfeedsetting`;
-DROP TABLE IF EXISTS `question`;
-DROP TABLE IF EXISTS `question_revision`;
-DROP TABLE IF EXISTS `question_tags`;
-DROP TABLE IF EXISTS `repute`;
-DROP TABLE IF EXISTS `tag`;
-DROP TABLE IF EXISTS `vote`;
diff --git a/fbconnect/__init__.py b/fbconnect/__init__.py
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/fbconnect/__init__.py
diff --git a/fbconnect/fb.py b/fbconnect/fb.py
new file mode 100755
index 00000000..99bc0b79
--- /dev/null
+++ b/fbconnect/fb.py
@@ -0,0 +1,89 @@
+from django.conf import settings
+from time import time
+from datetime import datetime
+from urllib import urlopen, urlencode
+
+try:
+ from json import load as load_json
+except:
+ from pjson import fread as load_json
+
+from models import FBAssociation
+import hashlib
+import logging
+
+REST_SERVER = 'http://api.facebook.com/restserver.php'
+
+def generate_sig(values):
+ keys = []
+
+ for key in sorted(values.keys()):
+ keys.append(key)
+
+ signature = ''.join(['%s=%s' % (key, values[key]) for key in keys]) + settings.FB_SECRET
+ return hashlib.md5(signature).hexdigest()
+
+def check_cookies_signature(cookies):
+ API_KEY = settings.FB_API_KEY
+
+ values = {}
+
+ for key in cookies.keys():
+ if (key.startswith(API_KEY + '_')):
+ values[key.replace(API_KEY + '_', '')] = cookies[key]
+
+ return generate_sig(values) == cookies[API_KEY]
+
+def get_user_data(cookies):
+ request_data = {
+ 'method': 'Users.getInfo',
+ 'api_key': settings.FB_API_KEY,
+ 'call_id': time(),
+ 'v': '1.0',
+ 'uids': cookies[settings.FB_API_KEY + '_user'],
+ 'fields': 'name,first_name,last_name',
+ 'format': 'json',
+ }
+
+ request_data['sig'] = generate_sig(request_data)
+ fb_response = urlopen(REST_SERVER, urlencode(request_data))
+ #print(fb_response)
+ return load_json(fb_response)[0]
+
+
+def delete_cookies(response):
+ API_KEY = settings.FB_API_KEY
+
+ response.delete_cookie(API_KEY + '_user')
+ response.delete_cookie(API_KEY + '_session_key')
+ response.delete_cookie(API_KEY + '_expires')
+ response.delete_cookie(API_KEY + '_ss')
+ response.delete_cookie(API_KEY)
+ response.delete_cookie('fbsetting_' + API_KEY)
+
+def check_session_expiry(cookies):
+ return datetime.fromtimestamp(float(cookies[settings.FB_API_KEY+'_expires'])) > datetime.now()
+
+STATES = {
+ 'FIRSTTIMER': 1,
+ 'SESSIONEXPIRED': 2,
+ 'RETURNINGUSER': 3,
+ 'INVALIDSTATE': 4,
+}
+
+def get_user_state(request):
+ API_KEY = settings.FB_API_KEY
+
+ if API_KEY in request.COOKIES:
+ if check_cookies_signature(request.COOKIES):
+ if check_session_expiry(request.COOKIES):
+ try:
+ uassoc = FBAssociation.objects.get(fbuid=request.COOKIES[API_KEY + '_user'])
+ return (STATES['RETURNINGUSER'], uassoc.user)
+ except:
+ return (STATES['FIRSTTIMER'], get_user_data(request.COOKIES))
+ else:
+ return (STATES['SESSIONEXPIRED'], None)
+
+ return (STATES['INVALIDSTATE'], None)
+
diff --git a/fbconnect/forms.py b/fbconnect/forms.py
new file mode 100755
index 00000000..94f86816
--- /dev/null
+++ b/fbconnect/forms.py
@@ -0,0 +1,8 @@
+from django_authopenid.forms import NextUrlField, UserNameField, UserEmailField
+
+from django import forms
+
+class FBConnectRegisterForm(forms.Form):
+ next = NextUrlField()
+ username = UserNameField()
+ email = UserEmailField()
diff --git a/fbconnect/models.py b/fbconnect/models.py
new file mode 100755
index 00000000..2172217d
--- /dev/null
+++ b/fbconnect/models.py
@@ -0,0 +1,6 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+class FBAssociation(models.Model):
+ user = models.ForeignKey(User)
+ fbuid = models.CharField(max_length=12, unique=True)
diff --git a/fbconnect/pjson.py b/fbconnect/pjson.py
new file mode 100755
index 00000000..6a395b97
--- /dev/null
+++ b/fbconnect/pjson.py
@@ -0,0 +1,313 @@
+import string
+import types
+
+## json.py implements a JSON (http://json.org) reader and writer.
+## Copyright (C) 2005 Patrick D. Logan
+## Contact mailto:patrickdlogan@stardecisions.com
+##
+## This library is free software; you can redistribute it and/or
+## modify it under the terms of the GNU Lesser General Public
+## License as published by the Free Software Foundation; either
+## version 2.1 of the License, or (at your option) any later version.
+##
+## This library is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public
+## License along with this library; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+class _StringGenerator(object):
+ def __init__(self, string):
+ self.string = string
+ self.index = -1
+ def peek(self):
+ i = self.index + 1
+ if i < len(self.string):
+ return self.string[i]
+ else:
+ return None
+ def next(self):
+ self.index += 1
+ if self.index < len(self.string):
+ return self.string[self.index]
+ else:
+ raise StopIteration
+ def all(self):
+ return self.string
+
+class WriteException(Exception):
+ pass
+
+class ReadException(Exception):
+ pass
+
+class JsonReader(object):
+ hex_digits = {'A': 10,'B': 11,'C': 12,'D': 13,'E': 14,'F':15}
+ escapes = {'t':'\t','n':'\n','f':'\f','r':'\r','b':'\b'}
+
+ def read(self, s):
+ self._generator = _StringGenerator(s)
+ result = self._read()
+ return result
+
+ def _read(self):
+ self._eatWhitespace()
+ peek = self._peek()
+ if peek is None:
+ raise ReadException, "Nothing to read: '%s'" % self._generator.all()
+ if peek == '{':
+ return self._readObject()
+ elif peek == '[':
+ return self._readArray()
+ elif peek == '"':
+ return self._readString()
+ elif peek == '-' or peek.isdigit():
+ return self._readNumber()
+ elif peek == 't':
+ return self._readTrue()
+ elif peek == 'f':
+ return self._readFalse()
+ elif peek == 'n':
+ return self._readNull()
+ elif peek == '/':
+ self._readComment()
+ return self._read()
+ else:
+ raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all()
+
+ def _readTrue(self):
+ self._assertNext('t', "true")
+ self._assertNext('r', "true")
+ self._assertNext('u', "true")
+ self._assertNext('e', "true")
+ return True
+
+ def _readFalse(self):
+ self._assertNext('f', "false")
+ self._assertNext('a', "false")
+ self._assertNext('l', "false")
+ self._assertNext('s', "false")
+ self._assertNext('e', "false")
+ return False
+
+ def _readNull(self):
+ self._assertNext('n', "null")
+ self._assertNext('u', "null")
+ self._assertNext('l', "null")
+ self._assertNext('l', "null")
+ return None
+
+ def _assertNext(self, ch, target):
+ if self._next() != ch:
+ raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all())
+
+ def _readNumber(self):
+ isfloat = False
+ result = self._next()
+ peek = self._peek()
+ while peek is not None and (peek.isdigit() or peek == "."):
+ isfloat = isfloat or peek == "."
+ result = result + self._next()
+ peek = self._peek()
+ try:
+ if isfloat:
+ return float(result)
+ else:
+ return int(result)
+ except ValueError:
+ raise ReadException, "Not a valid JSON number: '%s'" % result
+
+ def _readString(self):
+ result = ""
+ assert self._next() == '"'
+ try:
+ while self._peek() != '"':
+ ch = self._next()
+ if ch == "\\":
+ ch = self._next()
+ if ch in 'brnft':
+ ch = self.escapes[ch]
+ elif ch == "u":
+ ch4096 = self._next()
+ ch256 = self._next()
+ ch16 = self._next()
+ ch1 = self._next()
+ n = 4096 * self._hexDigitToInt(ch4096)
+ n += 256 * self._hexDigitToInt(ch256)
+ n += 16 * self._hexDigitToInt(ch16)
+ n += self._hexDigitToInt(ch1)
+ ch = unichr(n)
+ elif ch not in '"/\\':
+ raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all())
+ result = result + ch
+ except StopIteration:
+ raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all()
+ assert self._next() == '"'
+ return result
+
+ def _hexDigitToInt(self, ch):
+ try:
+ result = self.hex_digits[ch.upper()]
+ except KeyError:
+ try:
+ result = int(ch)
+ except ValueError:
+ raise ReadException, "The character %s is not a hex digit." % ch
+ return result
+
+ def _readComment(self):
+ assert self._next() == "/"
+ second = self._next()
+ if second == "/":
+ self._readDoubleSolidusComment()
+ elif second == '*':
+ self._readCStyleComment()
+ else:
+ raise ReadException, "Not a valid JSON comment: %s" % self._generator.all()
+
+ def _readCStyleComment(self):
+ try:
+ done = False
+ while not done:
+ ch = self._next()
+ done = (ch == "*" and self._peek() == "/")
+ if not done and ch == "/" and self._peek() == "*":
+ raise ReadException, "Not a valid JSON comment: %s, '/*' cannot be embedded in the comment." % self._generator.all()
+ self._next()
+ except StopIteration:
+ raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all()
+
+ def _readDoubleSolidusComment(self):
+ try:
+ ch = self._next()
+ while ch != "\r" and ch != "\n":
+ ch = self._next()
+ except StopIteration:
+ pass
+
+ def _readArray(self):
+ result = []
+ assert self._next() == '['
+ done = self._peek() == ']'
+ while not done:
+ item = self._read()
+ result.append(item)
+ self._eatWhitespace()
+ done = self._peek() == ']'
+ if not done:
+ ch = self._next()
+ if ch != ",":
+ raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
+ assert ']' == self._next()
+ return result
+
+ def _readObject(self):
+ result = {}
+ assert self._next() == '{'
+ done = self._peek() == '}'
+ while not done:
+ key = self._read()
+ if type(key) is not types.StringType:
+ raise ReadException, "Not a valid JSON object key (should be a string): %s" % key
+ self._eatWhitespace()
+ ch = self._next()
+ if ch != ":":
+ raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch)
+ self._eatWhitespace()
+ val = self._read()
+ result[key] = val
+ self._eatWhitespace()
+ done = self._peek() == '}'
+ if not done:
+ ch = self._next()
+ if ch != ",":
+ raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
+ assert self._next() == "}"
+ return result
+
+ def _eatWhitespace(self):
+ p = self._peek()
+ while p is not None and p in string.whitespace or p == '/':
+ if p == '/':
+ self._readComment()
+ else:
+ self._next()
+ p = self._peek()
+
+ def _peek(self):
+ return self._generator.peek()
+
+ def _next(self):
+ return self._generator.next()
+
+class JsonWriter(object):
+
+ def _append(self, s):
+ self._results.append(s)
+
+ def write(self, obj, escaped_forward_slash=False):
+ self._escaped_forward_slash = escaped_forward_slash
+ self._results = []
+ self._write(obj)
+ return "".join(self._results)
+
+ def _write(self, obj):
+ ty = type(obj)
+ if ty is types.DictType:
+ n = len(obj)
+ self._append("{")
+ for k, v in obj.items():
+ self._write(k)
+ self._append(":")
+ self._write(v)
+ n = n - 1
+ if n > 0:
+ self._append(",")
+ self._append("}")
+ elif ty is types.ListType or ty is types.TupleType:
+ n = len(obj)
+ self._append("[")
+ for item in obj:
+ self._write(item)
+ n = n - 1
+ if n > 0:
+ self._append(",")
+ self._append("]")
+ elif ty is types.StringType or ty is types.UnicodeType:
+ self._append('"')
+ obj = obj.replace('\\', r'\\')
+ if self._escaped_forward_slash:
+ obj = obj.replace('/', r'\/')
+ obj = obj.replace('"', r'\"')
+ obj = obj.replace('\b', r'\b')
+ obj = obj.replace('\f', r'\f')
+ obj = obj.replace('\n', r'\n')
+ obj = obj.replace('\r', r'\r')
+ obj = obj.replace('\t', r'\t')
+ self._append(obj)
+ self._append('"')
+ elif ty is types.IntType or ty is types.LongType:
+ self._append(str(obj))
+ elif ty is types.FloatType:
+ self._append("%f" % obj)
+ elif obj is True:
+ self._append("true")
+ elif obj is False:
+ self._append("false")
+ elif obj is None:
+ self._append("null")
+ else:
+ raise WriteException, "Cannot write in JSON: %s" % repr(obj)
+
+def write(obj, escaped_forward_slash=False):
+ return JsonWriter().write(obj, escaped_forward_slash)
+
+def read(s):
+ return JsonReader().read(s)
+
+def fread(f):
+ return read(f.read())
diff --git a/fbconnect/tests.py b/fbconnect/tests.py
new file mode 100755
index 00000000..2247054b
--- /dev/null
+++ b/fbconnect/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/fbconnect/urls.py b/fbconnect/urls.py
new file mode 100755
index 00000000..9b4ff0c9
--- /dev/null
+++ b/fbconnect/urls.py
@@ -0,0 +1,16 @@
+from django.conf.urls.defaults import *
+from django.utils.translation import ugettext as _
+from django.views.generic.simple import direct_to_template
+from views import signin, register
+
+urlpatterns = patterns('',
+ url(r'^xd_receiver$', direct_to_template, {'template': 'fbconnect/xd_receiver.html'}, name='xd_receiver'),
+
+ url(r'^%s$' % _('signin/'), signin, name="fb_signin"),
+ url(r'^%s%s$' % (_('signin/'), _('newquestion/')), signin, {'newquestion': True}, name="fb_signin_new_question"),
+ url(r'^%s%s$' % (_('signin/'), _('newanswer/')), signin, {'newanswer': True}, name="fb_signin_new_answer"),
+
+ url(r'^%s$' % _('register/'), register, name="fb_user_register"),
+ url(r'^%s%s$' % (_('register/'), _('newquestion/')), register, {'newquestion': True}, name="fb_user_register_new_question"),
+ url(r'^%s%s$' % (_('register/'), _('newanswer/')), register, {'newanswer': True}, name="fb_user_register_new_answer"),
+)
diff --git a/fbconnect/views.py b/fbconnect/views.py
new file mode 100755
index 00000000..5c308e45
--- /dev/null
+++ b/fbconnect/views.py
@@ -0,0 +1,97 @@
+from django.shortcuts import render_to_response as render
+from django.template import RequestContext
+from django.http import HttpResponseRedirect
+from django.utils.safestring import mark_safe
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
+from django.contrib.auth import login, logout
+from models import FBAssociation
+from forum.forms import EditUserEmailFeedsForm
+from django.conf import settings
+
+import fb
+import forms
+
+import logging
+
+def signin(request, newquestion = False, newanswer = False):
+ state, context = fb.get_user_state(request)
+
+ if state == fb.STATES['FIRSTTIMER']:
+ if newquestion:
+ register_url = 'fb_user_register_new_question'
+ elif newanswer:
+ register_url = 'fb_user_register_new_answer'
+ else:
+ register_url = 'fb_user_register'
+ return HttpResponseRedirect(reverse(register_url))
+ elif state == fb.STATES['RETURNINGUSER']:
+ return login_and_forward(request, context, newquestion, newanswer)
+ elif state == fb.STATES['SESSIONEXPIRED']:
+ response = logout(request, next_page=reverse('index'))
+ fb.delete_cookies(response)
+ return response
+
+ return HttpResponseRedirect(reverse('index'))
+
+def register(request, newquestion = False, newanswer = False):
+ state, context = fb.get_user_state(request)
+
+ if state == fb.STATES['FIRSTTIMER']:
+
+ if 'bnewaccount' in request.POST.keys():
+ form1 = forms.FBConnectRegisterForm(request.POST)
+ email_feeds_form = EditUserEmailFeedsForm(request.POST)
+
+ if (form1.is_valid() and email_feeds_form.is_valid()):
+ tmp_pwd = User.objects.make_random_password()
+ user_ = User.objects.create_user(form1.cleaned_data['username'],
+ form1.cleaned_data['email'], tmp_pwd)
+
+ user_.set_unusable_password()
+
+ uassoc = FBAssociation(user=user_, fbuid=context['uid'])
+ uassoc.save()
+
+ email_feeds_form.save(user_)
+
+ return login_and_forward(request, user_, newquestion, newanswer)
+ else:
+ form1 = forms.FBConnectRegisterForm(initial={
+ 'next': '/',
+ 'username': context['name'],
+ 'email': '',
+ })
+
+ email_feeds_form = EditUserEmailFeedsForm()
+
+ return render('authopenid/complete.html', {
+ 'form1': form1,
+ 'email_feeds_form': email_feeds_form,
+ 'provider':mark_safe('facebook'),
+ 'login_type':'facebook',
+ 'gravatar_faq_url':reverse('faq') + '#gravatar',
+ }, context_instance=RequestContext(request))
+ else:
+ return HttpResponseRedirect(reverse('index'))
+
+def login_and_forward(request, user, newquestion = False, newanswer = False):
+ old_session = request.session.session_key
+ user.backend = "django.contrib.auth.backends.ModelBackend"
+ login(request, user)
+
+ from forum.models import user_logged_in
+ user_logged_in.send(user=user,session_key=old_session,sender=None)
+
+ if (newquestion):
+ from forum.models import Question
+ question = Question.objects.filter(author=user).order_by('-added_at')[0]
+ return HttpResponseRedirect(question.get_absolute_url())
+
+ if (newanswer):
+ from forum.models import Answer
+ answer = Answer.objects.filter(author=user).order_by('-added_at')[0]
+ return HttpResponseRedirect(answer.get_absolute_url())
+
+ return HttpResponseRedirect('/')
+
diff --git a/forum/feed.py b/forum/feed.py
index 59983161..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 settings.APP_URL + '%s' % item.get_absolute_url()
+ 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 ad8a676a..22799622 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)
@@ -195,8 +200,9 @@ class EditAnswerForm(forms.Form):
self.fields['text'].initial = revision.text
class EditUserForm(forms.Form):
- email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- username = UserNameField(label=_('Screen name'))
+ email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=True, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ if settings.EDITABLE_SCREEN_NAME:
+ username = UserNameField(label=_('Screen name'))
realname = forms.CharField(label=_('Real name'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
website = forms.URLField(label=_('Website'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
city = forms.CharField(label=_('Location'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
@@ -205,7 +211,10 @@ class EditUserForm(forms.Form):
def __init__(self, user, *args, **kwargs):
super(EditUserForm, self).__init__(*args, **kwargs)
- self.fields['username'].initial = user.username
+ logging.debug('initializing the form')
+ if settings.EDITABLE_SCREEN_NAME:
+ 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 +308,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 +335,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/forms.py.orig b/forum/forms.py.orig
new file mode 100644
index 00000000..42becc11
--- /dev/null
+++ b/forum/forms.py.orig
@@ -0,0 +1,352 @@
+import re
+from datetime import date
+from django import forms
+from models import *
+from const import *
+from django.utils.translation import ugettext as _
+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):
+ super(TitleField, self).__init__(*args, **kwargs)
+ self.required = True
+ self.widget = forms.TextInput(attrs={'size' : 70, 'autocomplete' : 'off'})
+ self.max_length = 255
+ self.label = _('title')
+ self.help_text = _('please enter a descriptive title for your question')
+ self.initial = ''
+
+ def clean(self, value):
+ if len(value) < 10:
+ raise forms.ValidationError(_('title must be > 10 characters'))
+
+ return value
+
+class EditorField(forms.CharField):
+ def __init__(self, *args, **kwargs):
+ super(EditorField, self).__init__(*args, **kwargs)
+ self.required = True
+ self.widget = forms.Textarea(attrs={'id':'editor'})
+ self.label = _('content')
+ self.help_text = u''
+ self.initial = ''
+
+ def clean(self, value):
+ if len(value) < 10:
+ raise forms.ValidationError(_('question content must be > 10 characters'))
+
+ return value
+
+class TagNamesField(forms.CharField):
+ def __init__(self, *args, **kwargs):
+ super(TagNamesField, self).__init__(*args, **kwargs)
+ self.required = True
+ self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'})
+ self.max_length = 255
+ self.label = _('tags')
+ #self.help_text = _('please use space to separate tags (this enables autocomplete feature)')
+ self.help_text = _('Tags are short keywords, with no spaces within. Up to five tags can be used.')
+ self.initial = ''
+
+ def clean(self, value):
+ value = super(TagNamesField, self).clean(value)
+ data = value.strip()
+ if len(data) < 1:
+ raise forms.ValidationError(_('tags are required'))
+
+ split_re = re.compile(r'[ ,]+')
+ list = split_re.split(data)
+ list_temp = []
+ if len(list) > 5:
+ raise forms.ValidationError(_('please use 5 tags or less'))
+ for tag in list:
+ if len(tag) > 20:
+ raise forms.ValidationError(_('tags must be shorter than 20 characters'))
+ #take tag regex from settings
+ tagname_re = re.compile(r'[a-z0-9]+')
+ if not tagname_re.match(tag):
+ raise forms.ValidationError(_('please use following characters in tags: letters \'a-z\', numbers, and characters \'.-_#\''))
+ # only keep one same tag
+ if tag not in list_temp and len(tag.strip()) > 0:
+ list_temp.append(tag)
+ return u' '.join(list_temp)
+
+class WikiField(forms.BooleanField):
+ def __init__(self, *args, **kwargs):
+ super(WikiField, self).__init__(*args, **kwargs)
+ self.required = False
+ self.label = _('community wiki')
+ self.help_text = _('if you choose community wiki option, the question and answer do not generate points and name of author will not be shown')
+ def clean(self,value):
+ return value and settings.WIKI_ON
+
+class EmailNotifyField(forms.BooleanField):
+ def __init__(self, *args, **kwargs):
+ super(EmailNotifyField, self).__init__(*args, **kwargs)
+ self.required = False
+ self.widget.attrs['class'] = 'nomargin'
+
+class SummaryField(forms.CharField):
+ def __init__(self, *args, **kwargs):
+ super(SummaryField, self).__init__(*args, **kwargs)
+ self.required = False
+ self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'})
+ self.max_length = 300
+ self.label = _('update summary:')
+ self.help_text = _('enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)')
+
+class ModerateUserForm(forms.ModelForm):
+ is_approved = forms.BooleanField(label=_("Automatically accept user's contributions for the email updates"),
+ required=False)
+
+ def clean_is_approved(self):
+ if 'is_approved' not in self.cleaned_data:
+ self.cleaned_data['is_approved'] = False
+ return self.cleaned_data['is_approved']
+
+ class Meta:
+ model = User
+ fields = ('is_approved',)
+
+class 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)
+ message = forms.CharField(label=_('Your message:'), max_length=800,widget=forms.Textarea(attrs={'cols':60}))
+ next = NextUrlField()
+
+class AskForm(forms.Form):
+ title = TitleField()
+ text = EditorField()
+ tags = TagNamesField()
+ wiki = WikiField()
+
+ openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
+ user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+
+class AnswerForm(forms.Form):
+ text = EditorField()
+ wiki = WikiField()
+ openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
+ user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ email_notify = EmailNotifyField()
+ def __init__(self, question, user, *args, **kwargs):
+ super(AnswerForm, self).__init__(*args, **kwargs)
+ self.fields['email_notify'].widget.attrs['id'] = 'question-subscribe-updates';
+ if question.wiki and settings.WIKI_ON:
+ self.fields['wiki'].initial = True
+ if user.is_authenticated():
+ if user in question.followed_by.all():
+ self.fields['email_notify'].initial = True
+ return
+ self.fields['email_notify'].initial = False
+
+
+class CloseForm(forms.Form):
+ reason = forms.ChoiceField(choices=CLOSE_REASONS)
+
+class RetagQuestionForm(forms.Form):
+ tags = TagNamesField()
+ # initialize the default values
+ def __init__(self, question, *args, **kwargs):
+ super(RetagQuestionForm, self).__init__(*args, **kwargs)
+ self.fields['tags'].initial = question.tagnames
+
+class RevisionForm(forms.Form):
+ """
+ Lists revisions of a Question or Answer
+ """
+ revision = forms.ChoiceField(widget=forms.Select(attrs={'style' : 'width:520px'}))
+
+ def __init__(self, post, latest_revision, *args, **kwargs):
+ super(RevisionForm, self).__init__(*args, **kwargs)
+ revisions = post.revisions.all().values_list(
+ 'revision', 'author__username', 'revised_at', 'summary')
+ date_format = '%c'
+ self.fields['revision'].choices = [
+ (r[0], u'%s - %s (%s) %s' % (r[0], r[1], r[2].strftime(date_format), r[3]))
+ for r in revisions]
+ self.fields['revision'].initial = latest_revision.revision
+
+class EditQuestionForm(forms.Form):
+ title = TitleField()
+ text = EditorField()
+ tags = TagNamesField()
+ summary = SummaryField()
+
+ def __init__(self, question, revision, *args, **kwargs):
+ super(EditQuestionForm, self).__init__(*args, **kwargs)
+ self.fields['title'].initial = revision.title
+ self.fields['text'].initial = revision.text
+ self.fields['tags'].initial = revision.tagnames
+ # Once wiki mode is enabled, it can't be disabled
+ if not question.wiki:
+ self.fields['wiki'] = WikiField()
+
+class EditAnswerForm(forms.Form):
+ text = EditorField()
+ summary = SummaryField()
+
+ def __init__(self, answer, revision, *args, **kwargs):
+ super(EditAnswerForm, self).__init__(*args, **kwargs)
+ self.fields['text'].initial = revision.text
+
+class EditUserForm(forms.Form):
+ email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=True, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ #username = UserNameField(label=_('Screen name'))
+ realname = forms.CharField(label=_('Real name'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ website = forms.URLField(label=_('Website'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ city = forms.CharField(label=_('Location'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ birthday = forms.DateField(label=_('Date of birth'), help_text=_('will not be shown, used to calculate age, format: YYYY-MM-DD'), required=False, widget=forms.TextInput(attrs={'size' : 35}))
+ about = forms.CharField(label=_('Profile'), required=False, widget=forms.Textarea(attrs={'cols' : 60}))
+
+ def __init__(self, user, *args, **kwargs):
+ super(EditUserForm, self).__init__(*args, **kwargs)
+ self.fields['username'].initial = user.username
+ self.fields['username'].user_instance = user
+ self.fields['email'].initial = user.email
+ self.fields['realname'].initial = user.real_name
+ self.fields['website'].initial = user.website
+ self.fields['city'].initial = user.location
+
+ if user.date_of_birth is not None:
+ self.fields['birthday'].initial = user.date_of_birth
+ else:
+ self.fields['birthday'].initial = '1990-01-01'
+ self.fields['about'].initial = user.about
+ self.user = user
+
+ def clean_email(self):
+ """For security reason one unique email in database"""
+ if self.user.email != self.cleaned_data['email']:
+ #todo dry it, there is a similar thing in openidauth
+ if settings.EMAIL_UNIQUE == True:
+ if 'email' in self.cleaned_data:
+ try:
+ user = User.objects.get(email = self.cleaned_data['email'])
+ except User.DoesNotExist:
+ return self.cleaned_data['email']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(_('this email has already been registered, please use another one'))
+ raise forms.ValidationError(_('this email has already been registered, please use another one'))
+ return self.cleaned_data['email']
+
+class TagFilterSelectionForm(forms.ModelForm):
+ tag_filter_setting = forms.ChoiceField(choices=TAG_EMAIL_FILTER_CHOICES, #imported from forum/const.py
+ initial='ignored',
+ label=_('Choose email tag filter'),
+ widget=forms.RadioSelect)
+ class Meta:
+ model = User
+ fields = ('tag_filter_setting',)
+
+ def save(self):
+ before = self.instance.tag_filter_setting
+ super(TagFilterSelectionForm, self).save()
+ after = self.instance.tag_filter_setting #User.objects.get(pk=self.instance.id).tag_filter_setting
+ if before != after:
+ return True
+ return False
+
+class EditUserEmailFeedsForm(forms.Form):
+ WN = (('w',_('weekly')),('n',_('no email')))
+ DWN = (('d',_('daily')),('w',_('weekly')),('n',_('no email')))
+ FORM_TO_MODEL_MAP = {
+ 'all_questions':'q_all',
+ 'asked_by_me':'q_ask',
+ 'answered_by_me':'q_ans',
+ 'individually_selected':'q_sel',
+ }
+ NO_EMAIL_INITIAL = {
+ 'all_questions':'n',
+ 'asked_by_me':'n',
+ 'answered_by_me':'n',
+ 'individually_selected':'n',
+ }
+ asked_by_me = forms.ChoiceField(choices=DWN,initial='w',
+ widget=forms.RadioSelect,
+ label=_('Asked by me'))
+ answered_by_me = forms.ChoiceField(choices=DWN,initial='w',
+ widget=forms.RadioSelect,
+ label=_('Answered by me'))
+ individually_selected = forms.ChoiceField(choices=DWN,initial='w',
+ widget=forms.RadioSelect,
+ label=_('Individually selected'))
+ all_questions = forms.ChoiceField(choices=DWN,initial='w',
+ widget=forms.RadioSelect,
+ label=_('Entire forum (tag filtered)'),)
+
+ def set_initial_values(self,user=None):
+ KEY_MAP = dict([(v,k) for k,v in self.FORM_TO_MODEL_MAP.iteritems()])
+ if user != None:
+ settings = EmailFeedSetting.objects.filter(subscriber=user)
+ initial_values = {}
+ for setting in settings:
+ feed_type = setting.feed_type
+ form_field = KEY_MAP[feed_type]
+ frequency = setting.frequency
+ initial_values[form_field] = frequency
+ self.initial = initial_values
+ return self
+
+ def reset(self):
+ self.cleaned_data['all_questions'] = 'n'
+ self.cleaned_data['asked_by_me'] = 'n'
+ self.cleaned_data['answered_by_me'] = 'n'
+ self.cleaned_data['individually_selected'] = 'n'
+ self.initial = self.NO_EMAIL_INITIAL
+ return self
+
+ def save(self,user,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)
+ 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 = new_value
+ s.save()
+ changed = True
+ else:
+ if created:
+ s.save()
+ if form_field == 'individually_selected':
+ feed_type = ContentType.objects.get_for_model(Question)
+ user.followed_questions.clear()
+ return changed
+
+
+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/clean_award_badges.py b/forum/management/commands/clean_award_badges.py
index df3d2917..117e3a5f 100644
--- a/forum/management/commands/clean_award_badges.py
+++ b/forum/management/commands/clean_award_badges.py
@@ -21,9 +21,10 @@ from forum.models import *
class Command(NoArgsCommand):
def handle_noargs(self, **options):
try:
- self.clean_awards()
- except Exception, e:
- print e
+ try:
+ self.clean_awards()
+ except Exception, e:
+ print e
finally:
connection.close()
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 723a8cec..6b330cf9 100644
--- a/forum/management/commands/multi_award_badges.py
+++ b/forum/management/commands/multi_award_badges.py
@@ -82,27 +82,28 @@ TYPE_ACTIVITY_USER_FULL_UPDATED = 17
class Command(BaseCommand):
def handle_noargs(self, **options):
try:
- self.delete_question_be_voted_up_3()
- self.delete_answer_be_voted_up_3()
- self.delete_question_be_vote_down_3()
- self.delete_answer_be_voted_down_3()
- self.answer_be_voted_up_10()
- self.question_be_voted_up_10()
- self.question_view_1000()
- self.answer_self_question_be_voted_up_3()
- self.answer_be_voted_up_100()
- self.question_be_voted_up_100()
- self.question_be_favorited_100()
- self.question_view_10000()
- self.answer_be_voted_up_25()
- self.question_be_voted_up_25()
- self.question_be_favorited_25()
- self.question_view_2500()
- self.answer_be_accepted_and_voted_up_40()
- self.question_be_answered_after_60_days_and_be_voted_up_5()
- self.created_tag_be_used_in_question_50()
- except Exception, e:
- print e
+ try:
+ self.delete_question_be_voted_up_3()
+ self.delete_answer_be_voted_up_3()
+ self.delete_question_be_vote_down_3()
+ self.delete_answer_be_voted_down_3()
+ self.answer_be_voted_up_10()
+ self.question_be_voted_up_10()
+ self.question_view_1000()
+ self.answer_self_question_be_voted_up_3()
+ self.answer_be_voted_up_100()
+ self.question_be_voted_up_100()
+ self.question_be_favorited_100()
+ self.question_view_10000()
+ self.answer_be_voted_up_25()
+ self.question_be_voted_up_25()
+ self.question_be_favorited_25()
+ self.question_view_2500()
+ self.answer_be_accepted_and_voted_up_40()
+ self.question_be_answered_after_60_days_and_be_voted_up_5()
+ self.created_tag_be_used_in_question_50()
+ except Exception, e:
+ print e
finally:
connection.close()
@@ -317,7 +318,7 @@ class Command(BaseCommand):
object_id = row[2]
user = get_object_or_404(User, id=user_id)
- award = Award(user=user, badge=badge, content_type=content_type, object_id=objet_id)
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
award.save()
if update_auditted:
@@ -344,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/once_award_badges.py b/forum/management/commands/once_award_badges.py
index 03982c79..8c913348 100644
--- a/forum/management/commands/once_award_badges.py
+++ b/forum/management/commands/once_award_badges.py
@@ -94,17 +94,18 @@ BADGE_AWARD_TYPE_FIRST = {
class Command(BaseCommand):
def handle_noargs(self, **options):
try:
- self.alpha_user()
- self.beta_user()
- self.first_type_award()
- self.first_ask_be_voted()
- self.first_answer_be_voted()
- self.first_answer_be_voted_10()
- self.vote_count_300()
- self.edit_count_100()
- self.comment_count_10()
- except Exception, e:
- print e
+ try:
+ self.alpha_user()
+ self.beta_user()
+ self.first_type_award()
+ self.first_ask_be_voted()
+ self.first_answer_be_voted()
+ self.first_answer_be_voted_10()
+ self.vote_count_300()
+ self.edit_count_100()
+ self.comment_count_10()
+ except Exception, e:
+ print e
finally:
connection.close()
diff --git a/forum/management/commands/send_email_alerts.py b/forum/management/commands/send_email_alerts.py
index 283d5683..5e1eb3d0 100644
--- a/forum/management/commands/send_email_alerts.py
+++ b/forum/management/commands/send_email_alerts.py
@@ -2,42 +2,30 @@ from django.core.management.base import NoArgsCommand
from django.db import connection
from django.db.models import Q, F
from forum.models import *
-<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
-=======
from forum import const
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
from django.core.mail import EmailMessage
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
import datetime
-import settings
-<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
-=======
+from django.conf import settings
import logging
from utils.odict import OrderedDict
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
class Command(NoArgsCommand):
def handle_noargs(self,**options):
try:
- self.send_email_alerts()
- except Exception, e:
- print e
+ try:
+ self.send_email_alerts()
+ except Exception, e:
+ print e
finally:
connection.close()
def get_updated_questions_for_user(self,user):
-<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
- q_sel = []
- q_ask = []
- q_ans = []
- q_all = []
-=======
q_sel = None
q_ask = None
q_ans = None
q_all = None
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
now = datetime.datetime.now()
Q_set1 = Question.objects.exclude(
last_activity_by=user,
@@ -51,28 +39,17 @@ class Command(NoArgsCommand):
).exclude(
closed=True
)
-<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
-=======
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
user_feeds = EmailFeedSetting.objects.filter(subscriber=user).exclude(frequency='n')
for feed in user_feeds:
cutoff_time = now - EmailFeedSetting.DELTA_TABLE[feed.frequency]
if feed.reported_at == None or feed.reported_at <= cutoff_time:
-<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
- Q_set = Q_set1.exclude(last_activity_at__gt=cutoff_time)
-=======
Q_set = Q_set1.exclude(last_activity_at__gt=cutoff_time)#report these excluded later
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
feed.reported_at = now
feed.save()#may not actually report anything, depending on filters below
if feed.feed_type == 'q_sel':
q_sel = Q_set.filter(followed_by=user)
-<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
- q_sel.cutoff_time = cutoff_time
-=======
q_sel.cutoff_time = cutoff_time #store cutoff time per query set
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
elif feed.feed_type == 'q_ask':
q_ask = Q_set.filter(author=user)
q_ask.cutoff_time = cutoff_time
@@ -80,58 +57,12 @@ class Command(NoArgsCommand):
q_ans = Q_set.filter(answers__author=user)
q_ans.cutoff_time = cutoff_time
elif feed.feed_type == 'q_all':
-<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
- q_all = Q_set
- q_all.cutoff_time = cutoff_time
- #build list in this order
- q_tbl = {}
- def extend_question_list(src, dst):
- if isinstance(src,list):
- return
- cutoff_time = src.cutoff_time
- for q in src:
- if q in dst:
- if cutoff_time < dst[q]:
- dst[q] = cutoff_time
- else:
- dst[q] = cutoff_time
-
- extend_question_list(q_sel, q_tbl)
- extend_question_list(q_ask, q_tbl)
- extend_question_list(q_ans, q_tbl)
- extend_question_list(q_all, q_tbl)
-
- ctype = ContentType.objects.get_for_model(Question)
- out = {}
- for q, cutoff_time in q_tbl.items():
- #todo use Activity, but first start keeping more Activity records
- #act = Activity.objects.filter(content_type=ctype, object_id=q.id)
- #get info on question edits, answer edits, comments
- out[q] = {}
- q_rev = QuestionRevision.objects.filter(question=q,revised_at__lt=cutoff_time)
- q_rev = q_rev.exclude(author=user)
- out[q]['q_rev'] = len(q_rev)
- if len(q_rev) > 0 and q.added_at == q_rev[0].revised_at:
- out[q]['q_rev'] = 0
- out[q]['new_q'] = True
- else:
- out[q]['new_q'] = False
-
- new_ans = Answer.objects.filter(question=q,added_at__lt=cutoff_time)
- new_ans = new_ans.exclude(author=user)
- out[q]['new_ans'] = len(new_ans)
- ans_rev = AnswerRevision.objects.filter(answer__question=q,revised_at__lt=cutoff_time)
- ans_rev = ans_rev.exclude(author=user)
- out[q]['ans_rev'] = len(ans_rev)
- return out
- def __act_count(self,string,number,output):
-=======
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
@@ -206,17 +137,10 @@ class Command(NoArgsCommand):
return q_list
def __action_count(self,string,number,output):
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
if number > 0:
output.append(_(string) % {'num':number})
def send_email_alerts(self):
-
-<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
- for user in User.objects.all():
- q_list = self.get_updated_questions_for_user(user)
- num_q = len(q_list)
-=======
#todo: move this to template
for user in User.objects.all():
q_list = self.get_updated_questions_for_user(user)
@@ -227,28 +151,15 @@ class Command(NoArgsCommand):
num_q += 1
else:
num_moot += 1
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
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}
text += '<ul>'
-<<<<<<< HEAD:forum/management/commands/send_email_alerts.py
- for q, act in q_list.items():
- act_list = []
- if act['new_q']:
- act_list.append(_('new question'))
- self.__act_count('%(num)d rev', act['q_rev'],act_list)
- self.__act_count('%(num)d ans', act['new_ans'],act_list)
- self.__act_count('%(num)d ans rev',act['ans_rev'],act_list)
- act_token = ', '.join(act_list)
- text += '<li><a href="%s?sort=latest">%s</a> <font color="#777777">(%s)</font></li>' \
- % (url_prefix + q.get_absolute_url(), q.title, act_token)
- text += '</ul>'
-=======
for q, meta_data in q_list.items():
act_list = []
if meta_data['nothing_new']:
@@ -273,7 +184,6 @@ class Command(NoArgsCommand):
text += _('Perhaps you could look up previously sent forum reminders in your mailbox.')
text += '</p>'
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py
link = url_prefix + user.get_profile_url() + '?sort=email_subscriptions'
text += _('go to %(link)s to change frequency of email updates or %(email)s administrator') \
% {'link':link, 'email':settings.ADMINS[0][1]}
diff --git a/forum/management/commands/subscribe_everyone.py b/forum/management/commands/subscribe_everyone.py
index 3f8da9ec..c79528f3 100644
--- a/forum/management/commands/subscribe_everyone.py
+++ b/forum/management/commands/subscribe_everyone.py
@@ -6,14 +6,15 @@ 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):
try:
- self.subscribe_everyone()
- except Exception, e:
- print e
+ try:
+ self.subscribe_everyone()
+ except Exception, e:
+ print e
finally:
connection.close()
diff --git a/forum/managers.py b/forum/managers.py
index 1504491a..1705184a 100644
--- a/forum/managers.py
+++ b/forum/managers.py
@@ -1,4 +1,5 @@
import datetime
+import time
import logging
from django.contrib.auth.models import User, UserManager
from django.db import connection, models, transaction
@@ -92,7 +93,7 @@ class TagManager(models.Manager):
'SET used_count = ('
'SELECT COUNT(*) FROM question_tags '
'INNER JOIN question ON question_id=question.id '
- 'WHERE tag_id = tag.id AND question.deleted=0'
+ 'WHERE tag_id = tag.id AND question.deleted=False'
') '
'WHERE id IN (%s)')
@@ -175,7 +176,7 @@ class AnswerManager(models.Manager):
class VoteManager(models.Manager):
COUNT_UP_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = 1"
COUNT_DOWN_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = -1"
- COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = DATE(NOW())"
+ COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = %s"
def get_up_vote_count_from_user(self, user):
if user is not None:
cursor = connection.cursor()
@@ -197,7 +198,7 @@ class VoteManager(models.Manager):
def get_votes_count_today_from_user(self, user):
if user is not None:
cursor = connection.cursor()
- cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id])
+ cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())])
row = cursor.fetchone()
return row[0]
@@ -205,11 +206,11 @@ class VoteManager(models.Manager):
return 0
class FlaggedItemManager(models.Manager):
- COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = DATE(NOW())"
+ COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = %s"
def get_flagged_items_count_today(self, user):
if user is not None:
cursor = connection.cursor()
- cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id])
+ cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())])
row = cursor.fetchone()
return row[0]
@@ -217,7 +218,7 @@ class FlaggedItemManager(models.Manager):
return 0
class ReputeManager(models.Manager):
- COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = DATE(NOW())"
+ COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = %s"
def get_reputation_by_upvoted_today(self, user):
"""
For one user in one day, he can only earn rep till certain score (ep. +200)
@@ -226,7 +227,7 @@ class ReputeManager(models.Manager):
"""
if user is not None:
cursor = connection.cursor()
- cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id])
+ cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())])
row = cursor.fetchone()
return row[0]
diff --git a/forum/models.py b/forum/models.py
index 3e1e6543..86416030 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:
@@ -389,7 +389,7 @@ class QuestionRevision(models.Model):
return self.question.title
def get_absolute_url(self):
- print 'in QuestionRevision.get_absolute_url()'
+ #print 'in QuestionRevision.get_absolute_url()'
return reverse('question_revisions', args=[self.question.id])
def save(self, **kwargs):
@@ -416,7 +416,7 @@ class AnonymousAnswer(models.Model):
def publish(self,user):
from forum.views import create_new_answer
added_at = datetime.datetime.now()
- print user.id
+ #print user.id
create_new_answer(question=self.question,wiki=self.wiki,
added_at=added_at,text=self.text,
author=user)
@@ -481,8 +481,11 @@ class Answer(models.Model):
logging.debug('problem pinging google did you register you sitemap with google?')
def get_user_vote(self, user):
+ if user.__class__.__name__ == "AnonymousUser":
+ return None
+
votes = self.votes.filter(user=user)
- if votes.count() > 0:
+ if votes and votes.count() > 0:
return votes[0]
else:
return None
@@ -692,13 +695,13 @@ def user_is_username_taken(cls,username):
return False
def user_get_q_sel_email_feed_frequency(self):
- print 'looking for frequency for user %s' % self
+ #print 'looking for frequency for user %s' % self
try:
feed_setting = EmailFeedSetting.objects.get(subscriber=self,feed_type='q_sel')
except Exception, e:
- print 'have error %s' % e.message
+ #print 'have error %s' % e.message
raise e
- print 'have freq=%s' % feed_setting.frequency
+ #print 'have freq=%s' % feed_setting.frequency
return feed_setting.frequency
User.add_to_class('is_approved', models.BooleanField(default=False))
@@ -831,7 +834,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/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py
index b2199284..4f79e497 100644
--- a/forum/templatetags/extra_tags.py
+++ b/forum/templatetags/extra_tags.py
@@ -1,5 +1,6 @@
import time
import os
+import posixpath
import datetime
import math
import re
@@ -244,9 +245,9 @@ def diff_date(date, limen=2):
if days > 2:
if date.year == now.year:
- return date.strftime(_("%b %d at %H:%M"))
+ return date.strftime("%b %d at %H:%M")
else:
- return date.strftime(_("%b %d '%y at %H:%M"))
+ return date.strftime("%b %d '%y at %H:%M")
elif days == 2:
return _('2 days ago')
elif days == 1:
@@ -277,7 +278,7 @@ def get_latest_changed_timestamp():
@register.simple_tag
def href(url):
url = '///' + settings.FORUM_SCRIPT_ALIAS + '/' + url
- return os.path.normpath(url) + '?v=%d' % settings.RESOURCE_REVISION
+ return posixpath.normpath(url) + '?v=%d' % settings.RESOURCE_REVISION
class ItemSeparatorNode(template.Node):
def __init__(self,separator):
diff --git a/forum/templatetags/smart_if.py b/forum/templatetags/smart_if.py
index a8fc1944..ca3b43fe 100644
--- a/forum/templatetags/smart_if.py
+++ b/forum/templatetags/smart_if.py
@@ -1,401 +1,401 @@
-"""
-A smarter {% if %} tag for django templates.
-
-While retaining current Django functionality, it also handles equality,
-greater than and less than operators. Some common case examples::
-
- {% if articles|length >= 5 %}...{% endif %}
- {% if "ifnotequal tag" != "beautiful" %}...{% endif %}
-"""
-import unittest
-from django import template
-
-
-register = template.Library()
-
-
-#==============================================================================
-# Calculation objects
-#==============================================================================
-
-class BaseCalc(object):
- def __init__(self, var1, var2=None, negate=False):
- self.var1 = var1
- self.var2 = var2
- self.negate = negate
-
- def resolve(self, context):
- try:
- var1, var2 = self.resolve_vars(context)
- outcome = self.calculate(var1, var2)
- except:
- outcome = False
- if self.negate:
- return not outcome
- return outcome
-
- def resolve_vars(self, context):
- var2 = self.var2 and self.var2.resolve(context)
- return self.var1.resolve(context), var2
-
- def calculate(self, var1, var2):
- raise NotImplementedError()
-
-
-class Or(BaseCalc):
- def calculate(self, var1, var2):
- return var1 or var2
-
-
-class And(BaseCalc):
- def calculate(self, var1, var2):
- return var1 and var2
-
-
-class Equals(BaseCalc):
- def calculate(self, var1, var2):
- return var1 == var2
-
-
-class Greater(BaseCalc):
- def calculate(self, var1, var2):
- return var1 > var2
-
-
-class GreaterOrEqual(BaseCalc):
- def calculate(self, var1, var2):
- return var1 >= var2
-
-
-class In(BaseCalc):
- def calculate(self, var1, var2):
- return var1 in var2
-
-
-#==============================================================================
-# Tests
-#==============================================================================
-
-class TestVar(object):
- """
- A basic self-resolvable object similar to a Django template variable. Used
- to assist with tests.
- """
- def __init__(self, value):
- self.value = value
-
- def resolve(self, context):
- return self.value
-
-
-class SmartIfTests(unittest.TestCase):
- def setUp(self):
- self.true = TestVar(True)
- self.false = TestVar(False)
- self.high = TestVar(9000)
- self.low = TestVar(1)
-
- def assertCalc(self, calc, context=None):
- """
- Test a calculation is True, also checking the inverse "negate" case.
- """
- context = context or {}
- self.assert_(calc.resolve(context))
- calc.negate = not calc.negate
- self.assertFalse(calc.resolve(context))
-
- def assertCalcFalse(self, calc, context=None):
- """
- Test a calculation is False, also checking the inverse "negate" case.
- """
- context = context or {}
- self.assertFalse(calc.resolve(context))
- calc.negate = not calc.negate
- self.assert_(calc.resolve(context))
-
- def test_or(self):
- self.assertCalc(Or(self.true))
- self.assertCalcFalse(Or(self.false))
- self.assertCalc(Or(self.true, self.true))
- self.assertCalc(Or(self.true, self.false))
- self.assertCalc(Or(self.false, self.true))
- self.assertCalcFalse(Or(self.false, self.false))
-
- def test_and(self):
- self.assertCalc(And(self.true, self.true))
- self.assertCalcFalse(And(self.true, self.false))
- self.assertCalcFalse(And(self.false, self.true))
- self.assertCalcFalse(And(self.false, self.false))
-
- def test_equals(self):
- self.assertCalc(Equals(self.low, self.low))
- self.assertCalcFalse(Equals(self.low, self.high))
-
- def test_greater(self):
- self.assertCalc(Greater(self.high, self.low))
- self.assertCalcFalse(Greater(self.low, self.low))
- self.assertCalcFalse(Greater(self.low, self.high))
-
- def test_greater_or_equal(self):
- self.assertCalc(GreaterOrEqual(self.high, self.low))
- self.assertCalc(GreaterOrEqual(self.low, self.low))
- self.assertCalcFalse(GreaterOrEqual(self.low, self.high))
-
- def test_in(self):
- list_ = TestVar([1,2,3])
- invalid_list = TestVar(None)
- self.assertCalc(In(self.low, list_))
- self.assertCalcFalse(In(self.low, invalid_list))
-
- def test_parse_bits(self):
- var = IfParser([True]).parse()
- self.assert_(var.resolve({}))
- var = IfParser([False]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser([False, 'or', True]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([False, 'and', True]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser(['not', False, 'and', 'not', False]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser(['not', 'not', True]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([1, '=', 1]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([1, 'not', '=', 1]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser([1, 'not', 'not', '=', 1]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([1, '!=', 1]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser([3, '>', 2]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([1, '<', 2]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([2, 'not', 'in', [2, 3]]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser([1, 'or', 1, '=', 2]).parse()
- self.assert_(var.resolve({}))
-
- def test_boolean(self):
- var = IfParser([True, 'and', True, 'and', True]).parse()
- self.assert_(var.resolve({}))
- var = IfParser([False, 'or', False, 'or', True]).parse()
- self.assert_(var.resolve({}))
- var = IfParser([True, 'and', False, 'or', True]).parse()
- self.assert_(var.resolve({}))
- var = IfParser([False, 'or', True, 'and', True]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([True, 'and', True, 'and', False]).parse()
- self.assertFalse(var.resolve({}))
- var = IfParser([False, 'or', False, 'or', False]).parse()
- self.assertFalse(var.resolve({}))
- var = IfParser([False, 'or', True, 'and', False]).parse()
- self.assertFalse(var.resolve({}))
- var = IfParser([False, 'and', True, 'or', False]).parse()
- self.assertFalse(var.resolve({}))
-
- def test_invalid(self):
- self.assertRaises(ValueError, IfParser(['not']).parse)
- self.assertRaises(ValueError, IfParser(['==']).parse)
- self.assertRaises(ValueError, IfParser([1, 'in']).parse)
- self.assertRaises(ValueError, IfParser([1, '>', 'in']).parse)
- self.assertRaises(ValueError, IfParser([1, '==', 'not', 'not']).parse)
- self.assertRaises(ValueError, IfParser([1, 2]).parse)
-
-
-OPERATORS = {
- '=': (Equals, True),
- '==': (Equals, True),
- '!=': (Equals, False),
- '>': (Greater, True),
- '>=': (GreaterOrEqual, True),
- '<=': (Greater, False),
- '<': (GreaterOrEqual, False),
- 'or': (Or, True),
- 'and': (And, True),
- 'in': (In, True),
-}
-BOOL_OPERATORS = ('or', 'and')
-
-
-class IfParser(object):
- error_class = ValueError
-
- def __init__(self, tokens):
- self.tokens = tokens
-
- def _get_tokens(self):
- return self._tokens
-
- def _set_tokens(self, tokens):
- self._tokens = tokens
- self.len = len(tokens)
- self.pos = 0
-
- tokens = property(_get_tokens, _set_tokens)
-
- def parse(self):
- if self.at_end():
- raise self.error_class('No variables provided.')
- var1 = self.get_bool_var()
- while not self.at_end():
- op, negate = self.get_operator()
- var2 = self.get_bool_var()
- var1 = op(var1, var2, negate=negate)
- return var1
-
- def get_token(self, eof_message=None, lookahead=False):
- negate = True
- token = None
- pos = self.pos
- while token is None or token == 'not':
- if pos >= self.len:
- if eof_message is None:
- raise self.error_class()
- raise self.error_class(eof_message)
- token = self.tokens[pos]
- negate = not negate
- pos += 1
- if not lookahead:
- self.pos = pos
- return token, negate
-
- def at_end(self):
- return self.pos >= self.len
-
- def create_var(self, value):
- return TestVar(value)
-
- def get_bool_var(self):
- """
- Returns either a variable by itself or a non-boolean operation (such as
- ``x == 0`` or ``x < 0``).
-
- This is needed to keep correct precedence for boolean operations (i.e.
- ``x or x == 0`` should be ``x or (x == 0)``, not ``(x or x) == 0``).
- """
- var = self.get_var()
- if not self.at_end():
- op_token = self.get_token(lookahead=True)[0]
- if isinstance(op_token, basestring) and (op_token not in
- BOOL_OPERATORS):
- op, negate = self.get_operator()
- return op(var, self.get_var(), negate=negate)
- return var
-
- def get_var(self):
- token, negate = self.get_token('Reached end of statement, still '
- 'expecting a variable.')
- if isinstance(token, basestring) and token in OPERATORS:
- raise self.error_class('Expected variable, got operator (%s).' %
- token)
- var = self.create_var(token)
- if negate:
- return Or(var, negate=True)
- return var
-
- def get_operator(self):
- token, negate = self.get_token('Reached end of statement, still '
- 'expecting an operator.')
- if not isinstance(token, basestring) or token not in OPERATORS:
- raise self.error_class('%s is not a valid operator.' % token)
- if self.at_end():
- raise self.error_class('No variable provided after "%s".' % token)
- op, true = OPERATORS[token]
- if not true:
- negate = not negate
- return op, negate
-
-
-#==============================================================================
-# Actual templatetag code.
-#==============================================================================
-
-class TemplateIfParser(IfParser):
- error_class = template.TemplateSyntaxError
-
- def __init__(self, parser, *args, **kwargs):
- self.template_parser = parser
- return super(TemplateIfParser, self).__init__(*args, **kwargs)
-
- def create_var(self, value):
- return self.template_parser.compile_filter(value)
-
-
-class SmartIfNode(template.Node):
- def __init__(self, var, nodelist_true, nodelist_false=None):
- self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
- self.var = var
-
- def render(self, context):
- if self.var.resolve(context):
- return self.nodelist_true.render(context)
- if self.nodelist_false:
- return self.nodelist_false.render(context)
- return ''
-
- def __repr__(self):
- return "<Smart If node>"
-
- def __iter__(self):
- for node in self.nodelist_true:
- yield node
- if self.nodelist_false:
- for node in self.nodelist_false:
- yield node
-
- def get_nodes_by_type(self, nodetype):
- nodes = []
- if isinstance(self, nodetype):
- nodes.append(self)
- nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
- if self.nodelist_false:
- nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
- return nodes
-
-
-@register.tag('if')
-def smart_if(parser, token):
- """
- A smarter {% if %} tag for django templates.
-
- While retaining current Django functionality, it also handles equality,
- greater than and less than operators. Some common case examples::
-
- {% if articles|length >= 5 %}...{% endif %}
- {% if "ifnotequal tag" != "beautiful" %}...{% endif %}
-
- Arguments and operators _must_ have a space between them, so
- ``{% if 1>2 %}`` is not a valid smart if tag.
-
- All supported operators are: ``or``, ``and``, ``in``, ``=`` (or ``==``),
- ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
- """
- bits = token.split_contents()[1:]
- var = TemplateIfParser(parser, bits).parse()
- nodelist_true = parser.parse(('else', 'endif'))
- token = parser.next_token()
- if token.contents == 'else':
- nodelist_false = parser.parse(('endif',))
- parser.delete_first_token()
- else:
- nodelist_false = None
- return SmartIfNode(var, nodelist_true, nodelist_false)
-
-
-if __name__ == '__main__':
- unittest.main()
+"""
+A smarter {% if %} tag for django templates.
+
+While retaining current Django functionality, it also handles equality,
+greater than and less than operators. Some common case examples::
+
+ {% if articles|length >= 5 %}...{% endif %}
+ {% if "ifnotequal tag" != "beautiful" %}...{% endif %}
+"""
+import unittest
+from django import template
+
+
+register = template.Library()
+
+
+#==============================================================================
+# Calculation objects
+#==============================================================================
+
+class BaseCalc(object):
+ def __init__(self, var1, var2=None, negate=False):
+ self.var1 = var1
+ self.var2 = var2
+ self.negate = negate
+
+ def resolve(self, context):
+ try:
+ var1, var2 = self.resolve_vars(context)
+ outcome = self.calculate(var1, var2)
+ except:
+ outcome = False
+ if self.negate:
+ return not outcome
+ return outcome
+
+ def resolve_vars(self, context):
+ var2 = self.var2 and self.var2.resolve(context)
+ return self.var1.resolve(context), var2
+
+ def calculate(self, var1, var2):
+ raise NotImplementedError()
+
+
+class Or(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 or var2
+
+
+class And(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 and var2
+
+
+class Equals(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 == var2
+
+
+class Greater(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 > var2
+
+
+class GreaterOrEqual(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 >= var2
+
+
+class In(BaseCalc):
+ def calculate(self, var1, var2):
+ return var1 in var2
+
+
+#==============================================================================
+# Tests
+#==============================================================================
+
+class TestVar(object):
+ """
+ A basic self-resolvable object similar to a Django template variable. Used
+ to assist with tests.
+ """
+ def __init__(self, value):
+ self.value = value
+
+ def resolve(self, context):
+ return self.value
+
+
+class SmartIfTests(unittest.TestCase):
+ def setUp(self):
+ self.true = TestVar(True)
+ self.false = TestVar(False)
+ self.high = TestVar(9000)
+ self.low = TestVar(1)
+
+ def assertCalc(self, calc, context=None):
+ """
+ Test a calculation is True, also checking the inverse "negate" case.
+ """
+ context = context or {}
+ self.assert_(calc.resolve(context))
+ calc.negate = not calc.negate
+ self.assertFalse(calc.resolve(context))
+
+ def assertCalcFalse(self, calc, context=None):
+ """
+ Test a calculation is False, also checking the inverse "negate" case.
+ """
+ context = context or {}
+ self.assertFalse(calc.resolve(context))
+ calc.negate = not calc.negate
+ self.assert_(calc.resolve(context))
+
+ def test_or(self):
+ self.assertCalc(Or(self.true))
+ self.assertCalcFalse(Or(self.false))
+ self.assertCalc(Or(self.true, self.true))
+ self.assertCalc(Or(self.true, self.false))
+ self.assertCalc(Or(self.false, self.true))
+ self.assertCalcFalse(Or(self.false, self.false))
+
+ def test_and(self):
+ self.assertCalc(And(self.true, self.true))
+ self.assertCalcFalse(And(self.true, self.false))
+ self.assertCalcFalse(And(self.false, self.true))
+ self.assertCalcFalse(And(self.false, self.false))
+
+ def test_equals(self):
+ self.assertCalc(Equals(self.low, self.low))
+ self.assertCalcFalse(Equals(self.low, self.high))
+
+ def test_greater(self):
+ self.assertCalc(Greater(self.high, self.low))
+ self.assertCalcFalse(Greater(self.low, self.low))
+ self.assertCalcFalse(Greater(self.low, self.high))
+
+ def test_greater_or_equal(self):
+ self.assertCalc(GreaterOrEqual(self.high, self.low))
+ self.assertCalc(GreaterOrEqual(self.low, self.low))
+ self.assertCalcFalse(GreaterOrEqual(self.low, self.high))
+
+ def test_in(self):
+ list_ = TestVar([1,2,3])
+ invalid_list = TestVar(None)
+ self.assertCalc(In(self.low, list_))
+ self.assertCalcFalse(In(self.low, invalid_list))
+
+ def test_parse_bits(self):
+ var = IfParser([True]).parse()
+ self.assert_(var.resolve({}))
+ var = IfParser([False]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser([False, 'or', True]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([False, 'and', True]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser(['not', False, 'and', 'not', False]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser(['not', 'not', True]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([1, '=', 1]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([1, 'not', '=', 1]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser([1, 'not', 'not', '=', 1]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([1, '!=', 1]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser([3, '>', 2]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([1, '<', 2]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([2, 'not', 'in', [2, 3]]).parse()
+ self.assertFalse(var.resolve({}))
+
+ var = IfParser([1, 'or', 1, '=', 2]).parse()
+ self.assert_(var.resolve({}))
+
+ def test_boolean(self):
+ var = IfParser([True, 'and', True, 'and', True]).parse()
+ self.assert_(var.resolve({}))
+ var = IfParser([False, 'or', False, 'or', True]).parse()
+ self.assert_(var.resolve({}))
+ var = IfParser([True, 'and', False, 'or', True]).parse()
+ self.assert_(var.resolve({}))
+ var = IfParser([False, 'or', True, 'and', True]).parse()
+ self.assert_(var.resolve({}))
+
+ var = IfParser([True, 'and', True, 'and', False]).parse()
+ self.assertFalse(var.resolve({}))
+ var = IfParser([False, 'or', False, 'or', False]).parse()
+ self.assertFalse(var.resolve({}))
+ var = IfParser([False, 'or', True, 'and', False]).parse()
+ self.assertFalse(var.resolve({}))
+ var = IfParser([False, 'and', True, 'or', False]).parse()
+ self.assertFalse(var.resolve({}))
+
+ def test_invalid(self):
+ self.assertRaises(ValueError, IfParser(['not']).parse)
+ self.assertRaises(ValueError, IfParser(['==']).parse)
+ self.assertRaises(ValueError, IfParser([1, 'in']).parse)
+ self.assertRaises(ValueError, IfParser([1, '>', 'in']).parse)
+ self.assertRaises(ValueError, IfParser([1, '==', 'not', 'not']).parse)
+ self.assertRaises(ValueError, IfParser([1, 2]).parse)
+
+
+OPERATORS = {
+ '=': (Equals, True),
+ '==': (Equals, True),
+ '!=': (Equals, False),
+ '>': (Greater, True),
+ '>=': (GreaterOrEqual, True),
+ '<=': (Greater, False),
+ '<': (GreaterOrEqual, False),
+ 'or': (Or, True),
+ 'and': (And, True),
+ 'in': (In, True),
+}
+BOOL_OPERATORS = ('or', 'and')
+
+
+class IfParser(object):
+ error_class = ValueError
+
+ def __init__(self, tokens):
+ self.tokens = tokens
+
+ def _get_tokens(self):
+ return self._tokens
+
+ def _set_tokens(self, tokens):
+ self._tokens = tokens
+ self.len = len(tokens)
+ self.pos = 0
+
+ tokens = property(_get_tokens, _set_tokens)
+
+ def parse(self):
+ if self.at_end():
+ raise self.error_class('No variables provided.')
+ var1 = self.get_bool_var()
+ while not self.at_end():
+ op, negate = self.get_operator()
+ var2 = self.get_bool_var()
+ var1 = op(var1, var2, negate=negate)
+ return var1
+
+ def get_token(self, eof_message=None, lookahead=False):
+ negate = True
+ token = None
+ pos = self.pos
+ while token is None or token == 'not':
+ if pos >= self.len:
+ if eof_message is None:
+ raise self.error_class()
+ raise self.error_class(eof_message)
+ token = self.tokens[pos]
+ negate = not negate
+ pos += 1
+ if not lookahead:
+ self.pos = pos
+ return token, negate
+
+ def at_end(self):
+ return self.pos >= self.len
+
+ def create_var(self, value):
+ return TestVar(value)
+
+ def get_bool_var(self):
+ """
+ Returns either a variable by itself or a non-boolean operation (such as
+ ``x == 0`` or ``x < 0``).
+
+ This is needed to keep correct precedence for boolean operations (i.e.
+ ``x or x == 0`` should be ``x or (x == 0)``, not ``(x or x) == 0``).
+ """
+ var = self.get_var()
+ if not self.at_end():
+ op_token = self.get_token(lookahead=True)[0]
+ if isinstance(op_token, basestring) and (op_token not in
+ BOOL_OPERATORS):
+ op, negate = self.get_operator()
+ return op(var, self.get_var(), negate=negate)
+ return var
+
+ def get_var(self):
+ token, negate = self.get_token('Reached end of statement, still '
+ 'expecting a variable.')
+ if isinstance(token, basestring) and token in OPERATORS:
+ raise self.error_class('Expected variable, got operator (%s).' %
+ token)
+ var = self.create_var(token)
+ if negate:
+ return Or(var, negate=True)
+ return var
+
+ def get_operator(self):
+ token, negate = self.get_token('Reached end of statement, still '
+ 'expecting an operator.')
+ if not isinstance(token, basestring) or token not in OPERATORS:
+ raise self.error_class('%s is not a valid operator.' % token)
+ if self.at_end():
+ raise self.error_class('No variable provided after "%s".' % token)
+ op, true = OPERATORS[token]
+ if not true:
+ negate = not negate
+ return op, negate
+
+
+#==============================================================================
+# Actual templatetag code.
+#==============================================================================
+
+class TemplateIfParser(IfParser):
+ error_class = template.TemplateSyntaxError
+
+ def __init__(self, parser, *args, **kwargs):
+ self.template_parser = parser
+ return super(TemplateIfParser, self).__init__(*args, **kwargs)
+
+ def create_var(self, value):
+ return self.template_parser.compile_filter(value)
+
+
+class SmartIfNode(template.Node):
+ def __init__(self, var, nodelist_true, nodelist_false=None):
+ self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+ self.var = var
+
+ def render(self, context):
+ if self.var.resolve(context):
+ return self.nodelist_true.render(context)
+ if self.nodelist_false:
+ return self.nodelist_false.render(context)
+ return ''
+
+ def __repr__(self):
+ return "<Smart If node>"
+
+ def __iter__(self):
+ for node in self.nodelist_true:
+ yield node
+ if self.nodelist_false:
+ for node in self.nodelist_false:
+ yield node
+
+ def get_nodes_by_type(self, nodetype):
+ nodes = []
+ if isinstance(self, nodetype):
+ nodes.append(self)
+ nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
+ if self.nodelist_false:
+ nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
+ return nodes
+
+
+@register.tag('if')
+def smart_if(parser, token):
+ """
+ A smarter {% if %} tag for django templates.
+
+ While retaining current Django functionality, it also handles equality,
+ greater than and less than operators. Some common case examples::
+
+ {% if articles|length >= 5 %}...{% endif %}
+ {% if "ifnotequal tag" != "beautiful" %}...{% endif %}
+
+ Arguments and operators _must_ have a space between them, so
+ ``{% if 1>2 %}`` is not a valid smart if tag.
+
+ All supported operators are: ``or``, ``and``, ``in``, ``=`` (or ``==``),
+ ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
+ """
+ bits = token.split_contents()[1:]
+ var = TemplateIfParser(parser, bits).parse()
+ nodelist_true = parser.parse(('else', 'endif'))
+ token = parser.next_token()
+ if token.contents == 'else':
+ nodelist_false = parser.parse(('endif',))
+ parser.delete_first_token()
+ else:
+ nodelist_false = None
+ return SmartIfNode(var, nodelist_true, nodelist_false)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/forum/urls.py b/forum/urls.py
index 62e70161..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'),
@@ -86,6 +86,7 @@ urlpatterns = patterns('',
url(r'^%s(?P<short_name>[^/]+)/$' % _('books/'), app.book, name='book'),
url(r'^%s$' % _('search/'), app.search, name='search'),
url(r'^%s$' % _('feedback/'), app.feedback, name='feedback'),
+ (r'^%sfb/' % _('account/'), include('fbconnect.urls')),
(r'^%s' % _('account/'), include('django_authopenid.urls')),
(r'^i18n/', include('django.conf.urls.i18n')),
)
diff --git a/forum/views.py b/forum/views.py
index 65b80d0e..2ca4202e 100644
--- a/forum/views.py
+++ b/forum/views.py
@@ -33,7 +33,7 @@ from forum.forms import *
from forum.models 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
@@ -200,7 +200,7 @@ def questions(request, tagname=None, unanswered=False):
'SELECT COUNT(1) FROM forum_markedtag, question_tags '
+ 'WHERE forum_markedtag.user_id = %s '
+ 'AND forum_markedtag.tag_id = question_tags.tag_id '
- + 'AND forum_markedtag.reason = "good" '
+ + 'AND forum_markedtag.reason = \'good\' '
+ 'AND question_tags.question_id = question.id'
),
]),
@@ -218,7 +218,7 @@ def questions(request, tagname=None, unanswered=False):
'SELECT COUNT(1) FROM forum_markedtag, question_tags '
+ 'WHERE forum_markedtag.user_id = %s '
+ 'AND forum_markedtag.tag_id = question_tags.tag_id '
- + 'AND forum_markedtag.reason = "bad" '
+ + 'AND forum_markedtag.reason = \'bad\' '
+ 'AND question_tags.question_id = question.id'
)
]),
@@ -433,6 +433,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)
@@ -1232,7 +1247,7 @@ def edit_user(request, id):
from django_authopenid.views import set_new_email
set_new_email(user, new_email)
- user.username = sanitize_html(form.cleaned_data['username'])
+ #user.username = sanitize_html(form.cleaned_data['username'])
user.real_name = sanitize_html(form.cleaned_data['realname'])
user.website = sanitize_html(form.cleaned_data['website'])
user.location = sanitize_html(form.cleaned_data['city'])
@@ -1257,43 +1272,43 @@ def edit_user(request, id):
def user_stats(request, user_id, user_view):
user = get_object_or_404(User, id=user_id)
questions = Question.objects.extra(
- select={
- 'vote_count': 'question.score',
- 'favorited_myself': 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s AND f.question_id = question.id',
- 'la_user_id': 'auth_user.id',
- 'la_username': 'auth_user.username',
- 'la_user_gold': 'auth_user.gold',
- 'la_user_silver': 'auth_user.silver',
- 'la_user_bronze': 'auth_user.bronze',
- 'la_user_reputation': 'auth_user.reputation'
- },
- select_params=[user_id],
- tables=['question', 'auth_user'],
- where=['question.deleted = 0 AND question.author_id=%s AND question.last_activity_by_id = auth_user.id'],
- params=[user_id],
- order_by=['-vote_count', '-last_activity_at']
- ).values('vote_count',
- 'favorited_myself',
- 'id',
- 'title',
- 'author_id',
- 'added_at',
- 'answer_accepted',
- 'answer_count',
- 'comment_count',
- 'view_count',
- 'favourite_count',
- 'summary',
- 'tagnames',
- 'vote_up_count',
- 'vote_down_count',
- 'last_activity_at',
- 'la_user_id',
- 'la_username',
- 'la_user_gold',
- 'la_user_silver',
- 'la_user_bronze',
- 'la_user_reputation')[:100]
+ select={
+ 'vote_count' : 'question.score',
+ 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s AND f.question_id = question.id',
+ 'la_user_id' : 'auth_user.id',
+ 'la_username' : 'auth_user.username',
+ 'la_user_gold' : 'auth_user.gold',
+ 'la_user_silver' : 'auth_user.silver',
+ 'la_user_bronze' : 'auth_user.bronze',
+ 'la_user_reputation' : 'auth_user.reputation'
+ },
+ select_params=[user_id],
+ tables=['question', 'auth_user'],
+ where=['question.deleted=False AND question.author_id=%s AND question.last_activity_by_id = auth_user.id'],
+ params=[user_id],
+ order_by=['-vote_count', '-last_activity_at']
+ ).values('vote_count',
+ 'favorited_myself',
+ 'id',
+ 'title',
+ 'author_id',
+ 'added_at',
+ 'answer_accepted',
+ 'answer_count',
+ 'comment_count',
+ 'view_count',
+ 'favourite_count',
+ 'summary',
+ 'tagnames',
+ 'vote_up_count',
+ 'vote_down_count',
+ 'last_activity_at',
+ 'la_user_id',
+ 'la_username',
+ 'la_user_gold',
+ 'la_user_silver',
+ 'la_user_bronze',
+ 'la_user_reputation')[:100]
answered_questions = Question.objects.extra(
select={
@@ -1305,7 +1320,7 @@ def user_stats(request, user_id, user_view):
'comment_count' : 'answer.comment_count'
},
tables=['question', 'answer'],
- where=['answer.deleted=0 AND question.deleted=0 AND answer.author_id=%s AND answer.question_id=question.id'],
+ where=['answer.deleted=False AND question.deleted=False AND answer.author_id=%s AND answer.question_id=question.id'],
params=[user_id],
order_by=['-vote_count', '-answer_id'],
select_params=[user_id]
@@ -1427,7 +1442,7 @@ def user_recent(request, user_id, user_view):
},
tables=['activity', 'question'],
where=['activity.content_type_id = %s AND activity.object_id = ' +
- 'question.id AND question.deleted=0 AND activity.user_id = %s AND activity.activity_type = %s'],
+ 'question.id AND question.deleted=False AND activity.user_id = %s AND activity.activity_type = %s'],
params=[question_type_id, user_id, TYPE_ACTIVITY_ASK_QUESTION],
order_by=['-activity.active_at']
).values(
@@ -1452,8 +1467,8 @@ def user_recent(request, user_id, user_view):
},
tables=['activity', 'answer', 'question'],
where=['activity.content_type_id = %s AND activity.object_id = answer.id AND ' +
- 'answer.question_id=question.id AND answer.deleted=0 AND activity.user_id=%s AND '+
- 'activity.activity_type=%s AND question.deleted=0'],
+ 'answer.question_id=question.id AND answer.deleted=False AND activity.user_id=%s AND '+
+ 'activity.activity_type=%s AND question.deleted=False'],
params=[answer_type_id, user_id, TYPE_ACTIVITY_ANSWER],
order_by=['-activity.active_at']
).values(
@@ -1481,7 +1496,7 @@ def user_recent(request, user_id, user_view):
where=['activity.content_type_id = %s AND activity.object_id = comment.id AND '+
'activity.user_id = comment.user_id AND comment.object_id=question.id AND '+
'comment.content_type_id=%s AND activity.user_id = %s AND activity.activity_type=%s AND ' +
- 'question.deleted=0'],
+ 'question.deleted=False'],
params=[comment_type_id, question_type_id, user_id, TYPE_ACTIVITY_COMMENT_QUESTION],
order_by=['-comment.added_at']
).values(
@@ -1511,7 +1526,7 @@ def user_recent(request, user_id, user_view):
'activity.user_id = comment.user_id AND comment.object_id=answer.id AND '+
'comment.content_type_id=%s AND question.id = answer.question_id AND '+
'activity.user_id = %s AND activity.activity_type=%s AND '+
- 'answer.deleted=0 AND question.deleted=0'],
+ 'answer.deleted=False AND question.deleted=False'],
params=[comment_type_id, answer_type_id, user_id, TYPE_ACTIVITY_COMMENT_ANSWER],
order_by=['-comment.added_at']
).values(
@@ -1538,7 +1553,7 @@ def user_recent(request, user_id, user_view):
},
tables=['activity', 'question_revision', 'question'],
where=['activity.content_type_id = %s AND activity.object_id = question_revision.id AND '+
- 'question_revision.id=question.id AND question.deleted=0 AND '+
+ 'question_revision.id=question.id AND question.deleted=False AND '+
'activity.user_id = question_revision.author_id AND activity.user_id = %s AND '+
'activity.activity_type=%s'],
params=[question_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_QUESTION],
@@ -1571,7 +1586,7 @@ def user_recent(request, user_id, user_view):
where=['activity.content_type_id = %s AND activity.object_id = answer_revision.id AND '+
'activity.user_id = answer_revision.author_id AND activity.user_id = %s AND '+
'answer_revision.answer_id=answer.id AND answer.question_id = question.id AND '+
- 'question.deleted=0 AND answer.deleted=0 AND '+
+ 'question.deleted=False AND answer.deleted=False AND '+
'activity.activity_type=%s'],
params=[answer_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_ANSWER],
order_by=['-activity.active_at']
@@ -1600,7 +1615,7 @@ def user_recent(request, user_id, user_view):
tables=['activity', 'answer', 'question'],
where=['activity.content_type_id = %s AND activity.object_id = answer.id AND '+
'activity.user_id = question.author_id AND activity.user_id = %s AND '+
- 'answer.deleted=0 AND question.deleted=0 AND '+
+ 'answer.deleted=False AND question.deleted=False AND '+
'answer.question_id=question.id AND activity.activity_type=%s'],
params=[answer_type_id, user_id, TYPE_ACTIVITY_MARK_ANSWER],
order_by=['-activity.active_at']
@@ -1676,7 +1691,7 @@ def user_responses(request, user_id, user_view):
},
select_params=[user_id],
tables=['answer', 'question', 'auth_user'],
- where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+
+ where=['answer.question_id = question.id AND answer.deleted=False AND question.deleted=False AND '+
'question.author_id = %s AND answer.author_id <> %s AND answer.author_id=auth_user.id'],
params=[user_id, user_id],
order_by=['-answer.id']
@@ -1707,7 +1722,7 @@ def user_responses(request, user_id, user_view):
'user_id' : 'auth_user.id'
},
tables=['question', 'auth_user', 'comment'],
- where=['question.deleted = 0 AND question.author_id = %s AND comment.object_id=question.id AND '+
+ where=['question.deleted=False AND question.author_id = %s AND comment.object_id=question.id AND '+
'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id'],
params=[user_id, question_type_id, user_id],
order_by=['-comment.added_at']
@@ -1727,30 +1742,30 @@ def user_responses(request, user_id, user_view):
# answer comments
comments = Comment.objects.extra(
- select={
- 'title': 'question.title',
- 'question_id': 'question.id',
- 'answer_id': 'answer.id',
- 'added_at': 'comment.added_at',
- 'comment': 'comment.comment',
- 'username': 'auth_user.username',
- 'user_id': 'auth_user.id'
- },
- tables=['answer', 'auth_user', 'comment', 'question'],
- where=['answer.deleted = 0 AND answer.author_id = %s AND comment.object_id=answer.id AND ' +
- 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id ' +
- 'AND question.id = answer.question_id'],
- params=[user_id, answer_type_id, user_id],
- order_by=['-comment.added_at']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'comment',
- 'username',
- 'user_id'
- )
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'comment.added_at',
+ 'comment' : 'comment.comment',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ tables=['answer', 'auth_user', 'comment', 'question'],
+ where=['answer.deleted=False AND answer.author_id = %s AND comment.object_id=answer.id AND '+
+ 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id '+
+ 'AND question.id = answer.question_id'],
+ params=[user_id, answer_type_id, user_id],
+ order_by=['-comment.added_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'comment',
+ 'username',
+ 'user_id'
+ )
if len(comments) > 0:
comments = [(Response(TYPE_RESPONSE['ANSWER_COMMENTED'], c['title'], c['question_id'],
@@ -1759,30 +1774,30 @@ def user_responses(request, user_id, user_view):
# answer has been accepted
answers = Answer.objects.extra(
- select={
- 'title': 'question.title',
- 'question_id': 'question.id',
- 'answer_id': 'answer.id',
- 'added_at': 'answer.accepted_at',
- 'html': 'answer.html',
- 'username': 'auth_user.username',
- 'user_id': 'auth_user.id'
- },
- select_params=[user_id],
- tables=['answer', 'question', 'auth_user'],
- where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND ' +
- 'answer.author_id = %s AND answer.accepted=1 AND question.author_id=auth_user.id'],
- params=[user_id],
- order_by=['-answer.id']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'html',
- 'username',
- 'user_id'
- )
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'answer.accepted_at',
+ 'html' : 'answer.html',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ select_params=[user_id],
+ tables=['answer', 'question', 'auth_user'],
+ where=['answer.question_id = question.id AND answer.deleted=False AND question.deleted=False AND '+
+ 'answer.author_id = %s AND answer.accepted=True AND question.author_id=auth_user.id'],
+ params=[user_id],
+ order_by=['-answer.id']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'html',
+ 'username',
+ 'user_id'
+ )
if len(answers) > 0:
answers = [(Response(TYPE_RESPONSE['ANSWER_ACCEPTED'], a['title'], a['question_id'],
a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
@@ -1905,52 +1920,52 @@ def user_reputation(request, user_id, user_view):
def user_favorites(request, user_id, user_view):
user = get_object_or_404(User, id=user_id)
questions = Question.objects.extra(
- select={
- 'vote_count': 'question.vote_up_count + question.vote_down_count',
- 'favorited_myself': 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s ' +
- 'AND f.question_id = question.id',
- 'la_user_id': 'auth_user.id',
- 'la_username': 'auth_user.username',
- 'la_user_gold': 'auth_user.gold',
- 'la_user_silver': 'auth_user.silver',
- 'la_user_bronze': 'auth_user.bronze',
- 'la_user_reputation': 'auth_user.reputation'
- },
- select_params=[user_id],
- tables=['question', 'auth_user', 'favorite_question'],
- where=['question.deleted = 0 AND question.last_activity_by_id = auth_user.id ' +
- 'AND favorite_question.question_id = question.id AND favorite_question.user_id = %s'],
- params=[user_id],
- order_by=['-vote_count', '-question.id']
- ).values('vote_count',
- 'favorited_myself',
- 'id',
- 'title',
- 'author_id',
- 'added_at',
- 'answer_accepted',
- 'answer_count',
- 'comment_count',
- 'view_count',
- 'favourite_count',
- 'summary',
- 'tagnames',
- 'vote_up_count',
- 'vote_down_count',
- 'last_activity_at',
- 'la_user_id',
- 'la_username',
- 'la_user_gold',
- 'la_user_silver',
- 'la_user_bronze',
- 'la_user_reputation')
- return render_to_response(user_view.template_file, {
- "tab_name": user_view.id,
- "tab_description": user_view.tab_description,
- "page_title": user_view.page_title,
- "questions": questions[:user_view.data_size],
- "view_user": user
- }, context_instance=RequestContext(request))
+ select={
+ 'vote_count' : 'question.vote_up_count + question.vote_down_count',
+ 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s '+
+ 'AND f.question_id = question.id',
+ 'la_user_id' : 'auth_user.id',
+ 'la_username' : 'auth_user.username',
+ 'la_user_gold' : 'auth_user.gold',
+ 'la_user_silver' : 'auth_user.silver',
+ 'la_user_bronze' : 'auth_user.bronze',
+ 'la_user_reputation' : 'auth_user.reputation'
+ },
+ select_params=[user_id],
+ tables=['question', 'auth_user', 'favorite_question'],
+ where=['question.deleted=True AND question.last_activity_by_id = auth_user.id '+
+ 'AND favorite_question.question_id = question.id AND favorite_question.user_id = %s'],
+ params=[user_id],
+ order_by=['-vote_count', '-question.id']
+ ).values('vote_count',
+ 'favorited_myself',
+ 'id',
+ 'title',
+ 'author_id',
+ 'added_at',
+ 'answer_accepted',
+ 'answer_count',
+ 'comment_count',
+ 'view_count',
+ 'favourite_count',
+ 'summary',
+ 'tagnames',
+ 'vote_up_count',
+ 'vote_down_count',
+ 'last_activity_at',
+ 'la_user_id',
+ 'la_username',
+ 'la_user_gold',
+ 'la_user_silver',
+ 'la_user_bronze',
+ 'la_user_reputation')
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "questions" : questions[:user_view.data_size],
+ "view_user" : user
+ }, context_instance=RequestContext(request))
def user_email_subscriptions(request, user_id, user_view):
user = get_object_or_404(User, id=user_id)
@@ -2080,16 +2095,16 @@ def badges(request):
def badge(request, id):
badge = get_object_or_404(Badge, id=id)
awards = Award.objects.extra(
- select={'id': 'auth_user.id',
- 'name': 'auth_user.username',
- 'rep':'auth_user.reputation',
- 'gold': 'auth_user.gold',
- 'silver': 'auth_user.silver',
- 'bronze': 'auth_user.bronze'},
- tables=['award', 'auth_user'],
- where=['badge_id=%s AND user_id=auth_user.id'],
- params=[id]
- ).values('id').distinct()
+ select={'id': 'auth_user.id',
+ 'name': 'auth_user.username',
+ 'rep':'auth_user.reputation',
+ 'gold': 'auth_user.gold',
+ 'silver': 'auth_user.silver',
+ 'bronze': 'auth_user.bronze'},
+ tables=['award', 'auth_user'],
+ where=['badge_id=%s AND user_id=auth_user.id'],
+ params=[id]
+ ).distinct('id')
return render_to_response('badge.html', {
'awards': awards,
@@ -2333,8 +2348,18 @@ def search(request):
except KeyError:
view_id = "latest"
orderby = "-added_at"
-
- if settings.USE_SPHINX_SEARCH == True:
+
+ if settings.USE_PG_FTS:
+ objects = Question.objects.filter(deleted=False).extra(
+ select={
+ 'ranking': "ts_rank_cd(tsv, plainto_tsquery(%s), 32)",
+ },
+ where=["tsv @@ plainto_tsquery(%s)"],
+ params=[keywords],
+ select_params=[keywords]
+ ).order_by('-ranking')
+
+ elif settings.USE_SPHINX_SEARCH == True:
#search index is now free of delete questions and answers
#so there is not "antideleted" filtering here
objects = Question.search.query(keywords)
@@ -2355,26 +2380,31 @@ def search(request):
if tag not in related_tags:
related_tags.append(tag)
+ #if is_search is true in the context, prepend this string to soting tabs urls
+ search_uri = "?q=%s&page=%d&t=question" % ("+".join(keywords.split()), page)
+
return render_to_response(template_file, {
- "questions": questions,
- "tab_id": view_id,
- "questions_count": objects_list.count,
- "tags": related_tags,
- "searchtag": None,
- "searchtitle": keywords,
- "keywords": keywords,
- "is_unanswered": False,
- "context": {
- 'is_paginated': True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url': request.path + '?t=question&q=%s&sort=%s&' % (keywords, view_id),
- 'pagesize': pagesize
- }}, context_instance=RequestContext(request))
+ "questions" : questions,
+ "tab_id" : view_id,
+ "questions_count" : objects_list.count,
+ "tags" : related_tags,
+ "searchtag" : None,
+ "searchtitle" : keywords,
+ "keywords" : keywords,
+ "is_unanswered" : False,
+ "is_search": True,
+ "search_uri": search_uri,
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?t=question&q=%s&sort=%s&' % (keywords, view_id),
+ 'pagesize' : pagesize
+ }}, context_instance=RequestContext(request))
else:
raise Http404
diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po
index 3f554733..3ce3d53d 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/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/migration b/migration
deleted file mode 100644
index eb5dffa1..00000000
--- a/migration
+++ /dev/null
@@ -1,7 +0,0 @@
-cp cnprog-current/templates/content/style/style.css test/templates/content/style/
-cp cnprog-current/templates/footer.html test/templates/
-cp cnprog-current/templates/content/images/logo.png test/templates/content/images
-cp cnprog-current/locale/en/LC_MESSAGES/django.po test/locale/en/LC_MESSAGES/
-python manage.py makemessages -l en -e html,py,txt
-#fix fuzzy messages
-python manage.py compilemessages
diff --git a/osqa.wsgi.dist b/osqa.wsgi.dist
new file mode 100644
index 00000000..c3a269da
--- /dev/null
+++ b/osqa.wsgi.dist
@@ -0,0 +1,7 @@
+import os
+import sys
+sys.path.append('/path/to_dir_above')
+sys.path.append('/path/to_dir_above/osqa')
+os.environ['DJANGO_SETTINGS_MODULE'] = 'osqa.settings'
+import django.core.handlers.wsgi
+application = django.core.handlers.wsgi.WSGIHandler()
diff --git a/pgfulltext/__init__.py b/pgfulltext/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pgfulltext/__init__.py
diff --git a/pgfulltext/management.py b/pgfulltext/management.py
new file mode 100644
index 00000000..04303092
--- /dev/null
+++ b/pgfulltext/management.py
@@ -0,0 +1,23 @@
+import os
+
+from django.db import connection, transaction
+from django.conf import settings
+
+import forum.models
+
+if settings.USE_PG_FTS:
+ from django.db.models.signals import post_syncdb
+
+ def setup_pgfulltext(sender, **kwargs):
+ if sender == forum.models:
+ install_pg_fts()
+
+ post_syncdb.connect(setup_pgfulltext)
+
+def install_pg_fts():
+ f = open(os.path.join(os.path.dirname(__file__), '../sql_scripts/pg_fts_install.sql'), 'r')
+ cursor = connection.cursor()
+ cursor.execute(f.read())
+ transaction.commit_unless_managed()
+ f.close()
+ \ No newline at end of file
diff --git a/settings.py b/settings.py
index 3bce2879..96c20cc3 100644..100755
--- a/settings.py
+++ b/settings.py
@@ -13,19 +13,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',
-)
+ 'debug_toolbar.middleware.DebugToolbarMiddleware',
+ 'recaptcha_django.middleware.ReCaptchaMiddleware',
+ 'django.middleware.transaction.TransactionMiddleware',
+]
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
@@ -51,7 +54,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',
@@ -61,11 +67,33 @@ INSTALLED_APPS = (
'django.contrib.sitemaps',
'forum',
'django_authopenid',
- 'djangosphinx',
- #'debug_toolbar' ,
+ 'debug_toolbar' ,
'user_messages',
-)
-import django
-DJANGO_VERSION = django.get_version()
-# User settings
-from settings_local import *
+]
+
+AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend',]
+
+if USE_SPHINX_SEARCH:
+ INSTALLED_APPS.append('djangosphinx')
+
+if USE_FB_CONNECT:
+ INSTALLED_APPS.append('fbconnect')
+
+if DATABASE_ENGINE in ('postgresql_psycopg2', 'postgresql', ):
+ USE_PG_FTS = True
+ INSTALLED_APPS.append('pgfulltext')
+else:
+ USE_PG_FTS = False
+
+#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 136d4bdd..2251e58e 100644..100755
--- a/settings_local.py.dist
+++ b/settings_local.py.dist
@@ -1,24 +1,30 @@
# encoding:utf-8
import os.path
-<<<<<<< HEAD:settings_local.py.dist
-=======
from django.utils.translation import ugettext as _
->>>>>>> evgenyfadeev/master:settings_local.py.dist
SITE_SRC_ROOT = os.path.dirname(__file__)
LOG_FILENAME = 'django.lanai.log'
#for logging
import logging
logging.basicConfig(filename=os.path.join(SITE_SRC_ROOT, 'log', LOG_FILENAME), level=logging.DEBUG,)
-<<<<<<< HEAD:settings_local.py.dist
-
-DATABASE_NAME = '' # Or path to database file if using sqlite3.
-DATABASE_USER = '' # Not used with sqlite3.
-DATABASE_PASSWORD = '' # Not used with sqlite3.
-DATABASE_ENGINE = '' #mysql, etc
-<<<<<<< HEAD:settings_local.py.dist
-=======
+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'
+
+#for logging
+import logging
+logging.basicConfig(
+ filename=os.path.join(SITE_SRC_ROOT, 'log', LOG_FILENAME),
+ level=logging.DEBUG,
+ format='%(pathname)s TIME: %(asctime)s MSG: %(filename)s:%(funcName)s:%(lineno)d %(message)s',
+)
#ADMINS and MANAGERS
ADMINS = (('Forum Admin', 'forum@example.com'),)
@@ -29,109 +35,73 @@ DEBUG = False
TEMPLATE_DEBUG = DEBUG
INTERNAL_IPS = ('127.0.0.1',)
-DATABASE_NAME = 'cnprog' # Or path to database file if using sqlite3.
+DATABASE_NAME = 'osqa' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_ENGINE = 'mysql' #mysql, etc
->>>>>>> evgenyfadeev/master:settings_local.py.dist
-
-#Moved from settings.py for better organization. (please check it up to clean up settings.py)
-
-=======
-
-#Moved from settings.py for better organization. (please check it up to clean up settings.py)
+DATABASE_HOST = ''
+DATABASE_PORT = ''
->>>>>>> 6214863f362fd0702af79abaade0de6736d12e96:settings_local.py.dist
#email server settings
SERVER_EMAIL = ''
DEFAULT_FROM_EMAIL = ''
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
-<<<<<<< HEAD:settings_local.py.dist
-EMAIL_SUBJECT_PREFIX = '[cnprog.com]'
-EMAIL_HOST='smtp.gmail.com'
-EMAIL_PORT='587'
-EMAIL_USE_TLS=True
-<<<<<<< HEAD:settings_local.py.dist
-=======
-EMAIL_SUBJECT_PREFIX = '[CNPROG] '
-EMAIL_HOST='cnprog.com'
+EMAIL_SUBJECT_PREFIX = '[OSQA] '
+EMAIL_HOST='osqa.net'
EMAIL_PORT='25'
EMAIL_USE_TLS=False
->>>>>>> evgenyfadeev/master:settings_local.py.dist
-#LOCALIZATIONS
-TIME_ZONE = 'America/Tijuana'
-
-###########################
-#
-# this will allow running your forum with url like http://site.com/forum
-#
-# FORUM_SCRIPT_ALIAS = 'forum/'
-#
FORUM_SCRIPT_ALIAS = '' #no leading slash, default = '' empty string
-
-
-=======
#LOCALIZATIONS
TIME_ZONE = 'Asia/Chongqing Asia/Chungking'
# LANGUAGE_CODE = 'en-us'
->>>>>>> 6214863f362fd0702af79abaade0de6736d12e96:settings_local.py.dist
#OTHER SETTINGS
-<<<<<<< HEAD:settings_local.py.dist
-APP_TITLE = u'CNProg.com 程序员问答社区'
-APP_KEYWORDS = u'技术问答社区,中国程序员,编程技术社区,程序员社区,程序员论坛,程序员wiki,程序员博客'
-APP_DESCRIPTION = u'中国程序员的编程技术问答社区。我们做专业的、可协作编辑的技术问答社区。'
-APP_INTRO = u' <p>CNProg是一个<strong>面向程序员</strong>的可协作编辑的<strong>开放源代码问答社区</strong>。</p><p> 您可以在这里提问各类<strong>程序技术问题</strong> - 问题不分语言和平台。 同时也希望您对力所能及的问题,给予您的宝贵答案。</p>'
APP_COPYRIGHT = 'Copyright CNPROG.COM 2009'
-<<<<<<< HEAD:settings_local.py.dist
-=======
-APP_TITLE = u'CNPROG Q&A Forum'
-APP_KEYWORDS = u'CNPROG,forum,community'
+APP_TITLE = u'OSQA: Open Source Q&A Forum'
+APP_SHORT_NAME = u'OSQA'
+APP_KEYWORDS = u'OSQA,CNPROG,forum,community'
APP_DESCRIPTION = u'Ask and answer questions.'
APP_INTRO = u'<p>Ask and answer questions, make the world better!</p>'
-APP_COPYRIGHT = 'Copyright CNPROG, 2009. Some rights reserved under creative commons license.'
+APP_COPYRIGHT = 'Copyright OSQA, 2009. Some rights reserved under creative commons license.'
LOGIN_URL = '/%s%s%s' % (FORUM_SCRIPT_ALIAS,'account/','signin/')
GREETING_URL = LOGIN_URL #may be url of "faq" page or "about", etc
->>>>>>> evgenyfadeev/master:settings_local.py.dist
-=======
-
->>>>>>> 6214863f362fd0702af79abaade0de6736d12e96:settings_local.py.dist
USE_I18N = True
LANGUAGE_CODE = 'en'
EMAIL_VALIDATION = 'off' #string - on|off
MIN_USERNAME_LENGTH = 1
EMAIL_UNIQUE = False
-APP_URL = 'http://cnprog.com' #used by email notif system and RSS
-GOOGLE_SITEMAP_CODE = '55uGNnQVJW8p1bbXeF/Xbh9I7nZBM/wLhRz6N/I1kkA='
-<<<<<<< HEAD:settings_local.py.dist
-GOOGLE_ANALYTICS_KEY = ''
-<<<<<<< HEAD:settings_local.py.dist
-BOOKS_ON = True
-=======
-GOOGLE_ANALYTICS_KEY = ''
->>>>>>> 6214863f362fd0702af79abaade0de6736d12e96:settings_local.py.dist
-=======
+APP_URL = 'http://osqa.net' #used by email notif system and RSS
+GOOGLE_SITEMAP_CODE = ''
BOOKS_ON = False
WIKI_ON = True
USE_EXTERNAL_LEGACY_LOGIN = False
-EXTERNAL_LEGACY_LOGIN_HOST = 'login.cnprog.com'
+EXTERNAL_LEGACY_LOGIN_HOST = 'login.osqa.net'
EXTERNAL_LEGACY_LOGIN_PORT = 80
-EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME = '<span class="orange">CNPROG</span>'
+EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME = '<span class="orange">OSQA</span>'
FEEDBACK_SITE_URL = None #None or url
+EDITABLE_SCREEN_NAME = False #True or False - can user change screen name?
DJANGO_VERSION = 1.1
RESOURCE_REVISION=4
-USE_SPHINX_SEARCH = True #if True all SPHINX_* settings are required
+USE_SPHINX_SEARCH = False #if True all SPHINX_* settings are required
#also sphinx search engine and djangosphinxs app must be installed
#sample sphinx configuration file is /sphinx/sphinx.conf
SPHINX_API_VERSION = 0x113 #refer to djangosphinx documentation
-SPHINX_SEARCH_INDICES=('cnprog',) #a tuple of index names remember about a comma after the
+SPHINX_SEARCH_INDICES=('osqa',) #a tuple of index names remember about a comma after the
#last item, especially if you have just one :)
SPHINX_SERVER='localhost'
SPHINX_PORT=3312
->>>>>>> evgenyfadeev/master:settings_local.py.dist
+
+#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..5fd03d18
--- /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', TRUE, 0),
+(2, 'Peer Pressure', 3, 'peer-pressure', 'Deleted own post with score of -3 or lower', TRUE, 0),
+(3, 'Nice answer', 3, 'nice-answer', 'Answer voted up 10 times', TRUE, 0),
+(4, 'Nice Question', 3, 'nice-question', 'Question voted up 10 times', TRUE, 0),
+(5, 'Pundit', 3, 'pundit', 'Left 10 comments with score of 10 or more', FALSE, 0),
+(6, 'Popular Question', 3, 'popular-question', 'Asked a question with 1,000 views', TRUE, 0),
+(7, 'Citizen patrol', 3, 'citizen-patrol', 'First flagged post', FALSE, 0),
+(8, 'Cleanup', 3, 'cleanup', 'First rollback', FALSE, 0),
+(9, 'Critic', 3, 'critic', 'First down vote', FALSE, 0),
+(10, 'Editor', 3, 'editor', 'First edit', FALSE, 0),
+(11, 'Organizer', 3, 'organizer', 'First retag', FALSE, 0),
+(12, 'Scholar', 3, 'scholar', 'First accepted answer on your own question', FALSE, 0),
+(13, 'Student', 3, 'student', 'Asked first question with at least one up vote', FALSE, 0),
+(14, 'Supporter', 3, 'supporter', 'First up vote', FALSE, 0),
+(15, 'Teacher', 3, 'teacher', 'Answered first question with at least one up vote', FALSE, 0),
+(16, 'Autobiographer', 3, 'autobiographer', 'Completed all user profile fields', FALSE, 0),
+(17, 'Self-Learner', 3, 'self-learner', 'Answered your own question with at least 3 up votes', TRUE, 0),
+(18, 'Great Answer', 1, 'great-answer', 'Answer voted up 100 times', TRUE, 0),
+(19, 'Great Question', 1, 'great-question', 'Question voted up 100 times', TRUE, 0),
+(20, 'Stellar Question', 1, 'stellar-question', 'Question favorited by 100 users', TRUE, 0),
+(21, 'Famous question', 1, 'famous-question', 'Asked a question with 10,000 views', TRUE, 0),
+(22, 'Alpha', 2, 'alpha', 'Actively participated in the private alpha', FALSE, 0),
+(23, 'Good Answer', 2, 'good-answer', 'Answer voted up 25 times', TRUE, 0),
+(24, 'Good Question', 2, 'good-question', 'Question voted up 25 times', TRUE, 0),
+(25, 'Favorite Question', 2, 'favorite-question', 'Question favorited by 25 users', TRUE, 0),
+(26, 'Civic duty', 2, 'civic-duty', 'Voted 300 times', FALSE, 0),
+(27, 'Strunk & White', 2, 'strunk-and-white', 'Edited 100 entries', FALSE, 0),
+(28, 'Generalist', 2, 'generalist', 'Active in many different tags', FALSE, 0),
+(29, 'Expert', 2, 'export', 'Very active in one tag', FALSE, 0),
+(30, 'Yearling', 2, 'yearling', 'Active member for a year', FALSE, 0),
+(31, 'Notable Question', 2, 'notable-question', 'Asked a question with 2,500 views', TRUE, 0),
+(32, 'Enlightened', 2, 'enlightened', 'First answer was accepted with at least 10 up votes', FALSE, 0),
+(33, 'Beta', 2, 'beta', 'Actively participated in the private beta', FALSE, 0),
+(34, 'Guru', 2, 'guru', 'Accepted answer and voted up 40 times', TRUE, 0),
+(35, 'Necromancer', 2, 'necromancer', 'Answered a question more than 60 days later with at least 5 votes', TRUE, 0),
+(36, 'Taxonomist', 2, 'taxonomist', 'Created a tag used by 50 questions', TRUE, 0);
diff --git a/drop-all-tables.sh b/sql_scripts/drop-all-tables.sh
index 1e55cb1f..1e55cb1f 100644
--- a/drop-all-tables.sh
+++ b/sql_scripts/drop-all-tables.sh
diff --git a/sql_scripts/drop-auth.sql b/sql_scripts/drop-auth.sql
new file mode 100644
index 00000000..bc17dce3
--- /dev/null
+++ b/sql_scripts/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/sql_scripts/pg_fts_install.sql b/sql_scripts/pg_fts_install.sql
new file mode 100644
index 00000000..d0655134
--- /dev/null
+++ b/sql_scripts/pg_fts_install.sql
@@ -0,0 +1,38 @@
+ALTER TABLE question ADD COLUMN tsv tsvector;
+
+CREATE OR REPLACE FUNCTION public.create_plpgsql_language ()
+ RETURNS TEXT
+ AS $$
+ CREATE LANGUAGE plpgsql;
+ SELECT 'language plpgsql created'::TEXT;
+ $$
+LANGUAGE 'sql';
+
+SELECT CASE WHEN
+ (SELECT true::BOOLEAN
+ FROM pg_language
+ WHERE lanname='plpgsql')
+ THEN
+ (SELECT 'language already installed'::TEXT)
+ ELSE
+ (SELECT public.create_plpgsql_language())
+ END;
+
+DROP FUNCTION public.create_plpgsql_language ();
+
+CREATE OR REPLACE FUNCTION set_question_tsv() RETURNS TRIGGER AS $$
+begin
+ new.tsv :=
+ setweight(to_tsvector('english', coalesce(new.tagnames,'')), 'A') ||
+ setweight(to_tsvector('english', coalesce(new.title,'')), 'B') ||
+ setweight(to_tsvector('english', coalesce(new.summary,'')), 'C');
+ RETURN new;
+end
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
+ON question FOR EACH ROW EXECUTE PROCEDURE set_question_tsv();
+
+CREATE INDEX blog_entry_tsv ON blog_entry USING gin(body_tsv);
+
+UPDATE question SET title = title;
diff --git a/sql_scripts/update_2010_01_23.sql b/sql_scripts/update_2010_01_23.sql
new file mode 100755
index 00000000..621207be
--- /dev/null
+++ b/sql_scripts/update_2010_01_23.sql
@@ -0,0 +1,9 @@
+CREATE TABLE `fbconnect_fbassociation` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `user_id` integer NOT NULL,
+ `fbuid` varchar(12) NOT NULL UNIQUE
+)
+;
+ALTER TABLE `fbconnect_fbassociation` ADD CONSTRAINT `user_id_refs_id_3534873d`
+FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
+CREATE INDEX `fbconnect_fbassociation_user_id` ON `fbconnect_fbassociation` (`user_id`);
diff --git a/tables.sql b/tables.sql
deleted file mode 100644
index 6034c08c..00000000
--- a/tables.sql
+++ /dev/null
@@ -1,440 +0,0 @@
-BEGIN;
-CREATE TABLE `forum_emailfeedsetting` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `subscriber_id` integer NOT NULL,
- `feed_type` varchar(16) NOT NULL,
- `frequency` varchar(8) NOT NULL,
- `added_at` datetime NOT NULL,
- `reported_at` datetime NULL
-)
-;
-ALTER TABLE `forum_emailfeedsetting` ADD CONSTRAINT subscriber_id_refs_id_6fee6730cc813af8 FOREIGN KEY (`subscriber_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `tag` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `name` varchar(255) NOT NULL UNIQUE,
- `created_by_id` integer NOT NULL,
- `deleted` bool NOT NULL,
- `deleted_at` datetime NULL,
- `deleted_by_id` integer NULL,
- `used_count` integer UNSIGNED NOT NULL
-)
-;
-ALTER TABLE `tag` ADD CONSTRAINT created_by_id_refs_id_6ae4d97547205d6d FOREIGN KEY (`created_by_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `tag` ADD CONSTRAINT deleted_by_id_refs_id_6ae4d97547205d6d FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `comment` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `content_type_id` integer NOT NULL,
- `object_id` integer UNSIGNED NOT NULL,
- `user_id` integer NOT NULL,
- `comment` varchar(300) NOT NULL,
- `added_at` datetime NOT NULL
-)
-;
-ALTER TABLE `comment` ADD CONSTRAINT content_type_id_refs_id_89a4b13ec5a7994 FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
-ALTER TABLE `comment` ADD CONSTRAINT user_id_refs_id_5ba842626be725e8 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `vote` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `content_type_id` integer NOT NULL,
- `object_id` integer UNSIGNED NOT NULL,
- `user_id` integer NOT NULL,
- `vote` smallint NOT NULL,
- `voted_at` datetime NOT NULL,
- UNIQUE (`content_type_id`, `object_id`, `user_id`)
-)
-;
-ALTER TABLE `vote` ADD CONSTRAINT content_type_id_refs_id_77dc6ffafedbbec FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
-ALTER TABLE `vote` ADD CONSTRAINT user_id_refs_id_3ce5b20589f5b210 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `flagged_item` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `content_type_id` integer NOT NULL,
- `object_id` integer UNSIGNED NOT NULL,
- `user_id` integer NOT NULL,
- `flagged_at` datetime NOT NULL,
- UNIQUE (`content_type_id`, `object_id`, `user_id`)
-)
-;
-ALTER TABLE `flagged_item` ADD CONSTRAINT content_type_id_refs_id_261d26c8891bb28c FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
-ALTER TABLE `flagged_item` ADD CONSTRAINT user_id_refs_id_92ae9d35e3c608 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `question` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `title` varchar(300) NOT NULL,
- `author_id` integer NOT NULL,
- `added_at` datetime NOT NULL,
- `wiki` bool NOT NULL,
- `wikified_at` datetime NULL,
- `answer_accepted` bool NOT NULL,
- `closed` bool NOT NULL,
- `closed_by_id` integer NULL,
- `closed_at` datetime NULL,
- `close_reason` smallint NULL,
- `deleted` bool NOT NULL,
- `deleted_at` datetime NULL,
- `deleted_by_id` integer NULL,
- `locked` bool NOT NULL,
- `locked_by_id` integer NULL,
- `locked_at` datetime NULL,
- `score` integer NOT NULL,
- `vote_up_count` integer NOT NULL,
- `vote_down_count` integer NOT NULL,
- `answer_count` integer UNSIGNED NOT NULL,
- `comment_count` integer UNSIGNED NOT NULL,
- `view_count` integer UNSIGNED NOT NULL,
- `offensive_flag_count` smallint NOT NULL,
- `favourite_count` integer UNSIGNED NOT NULL,
- `last_edited_at` datetime NULL,
- `last_edited_by_id` integer NULL,
- `last_activity_at` datetime NOT NULL,
- `last_activity_by_id` integer NOT NULL,
- `tagnames` varchar(125) NOT NULL,
- `summary` varchar(180) NOT NULL,
- `html` longtext NOT NULL
-)
-;
-ALTER TABLE `question` ADD CONSTRAINT author_id_refs_id_5159d9f3a9162ff4 FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `question` ADD CONSTRAINT closed_by_id_refs_id_5159d9f3a9162ff4 FOREIGN KEY (`closed_by_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `question` ADD CONSTRAINT deleted_by_id_refs_id_5159d9f3a9162ff4 FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `question` ADD CONSTRAINT locked_by_id_refs_id_5159d9f3a9162ff4 FOREIGN KEY (`locked_by_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `question` ADD CONSTRAINT last_edited_by_id_refs_id_5159d9f3a9162ff4 FOREIGN KEY (`last_edited_by_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `question` ADD CONSTRAINT last_activity_by_id_refs_id_5159d9f3a9162ff4 FOREIGN KEY (`last_activity_by_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `forum_questionview` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `question_id` integer NOT NULL,
- `who_id` integer NOT NULL,
- `when` datetime NOT NULL
-)
-;
-ALTER TABLE `forum_questionview` ADD CONSTRAINT question_id_refs_id_fe63ebce6b3cbac FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
-ALTER TABLE `forum_questionview` ADD CONSTRAINT who_id_refs_id_293b67239e957c53 FOREIGN KEY (`who_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `favorite_question` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `question_id` integer NOT NULL,
- `user_id` integer NOT NULL,
- `added_at` datetime NOT NULL
-)
-;
-ALTER TABLE `favorite_question` ADD CONSTRAINT question_id_refs_id_2cafd2f21ebe1cc3 FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
-ALTER TABLE `favorite_question` ADD CONSTRAINT user_id_refs_id_1632ce11ad7ac7de FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `question_revision` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `question_id` integer NOT NULL,
- `revision` integer UNSIGNED NOT NULL,
- `title` varchar(300) NOT NULL,
- `author_id` integer NOT NULL,
- `revised_at` datetime NOT NULL,
- `tagnames` varchar(125) NOT NULL,
- `summary` varchar(300) NOT NULL,
- `text` longtext NOT NULL
-)
-;
-ALTER TABLE `question_revision` ADD CONSTRAINT question_id_refs_id_61316ec87bef5296 FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
-ALTER TABLE `question_revision` ADD CONSTRAINT author_id_refs_id_79de7cc0b077fdb1 FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `forum_anonymousanswer` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `question_id` integer NOT NULL,
- `session_key` varchar(40) NOT NULL,
- `wiki` bool NOT NULL,
- `added_at` datetime NOT NULL,
- `ip_addr` char(15) NOT NULL,
- `author_id` integer NULL,
- `text` longtext NOT NULL,
- `summary` varchar(180) NOT NULL
-)
-;
-ALTER TABLE `forum_anonymousanswer` ADD CONSTRAINT question_id_refs_id_17dd6b2f4cc171c7 FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
-ALTER TABLE `forum_anonymousanswer` ADD CONSTRAINT author_id_refs_id_3ac41be013fb542e FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `forum_anonymousquestion` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `title` varchar(300) NOT NULL,
- `session_key` varchar(40) NOT NULL,
- `text` longtext NOT NULL,
- `summary` varchar(180) NOT NULL,
- `tagnames` varchar(125) NOT NULL,
- `wiki` bool NOT NULL,
- `added_at` datetime NOT NULL,
- `ip_addr` char(15) NOT NULL,
- `author_id` integer NULL
-)
-;
-ALTER TABLE `forum_anonymousquestion` ADD CONSTRAINT author_id_refs_id_2a673297511a98a FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `answer` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `question_id` integer NOT NULL,
- `author_id` integer NOT NULL,
- `added_at` datetime NOT NULL,
- `wiki` bool NOT NULL,
- `wikified_at` datetime NULL,
- `accepted` bool NOT NULL,
- `accepted_at` datetime NULL,
- `deleted` bool NOT NULL,
- `deleted_by_id` integer NULL,
- `locked` bool NOT NULL,
- `locked_by_id` integer NULL,
- `locked_at` datetime NULL,
- `score` integer NOT NULL,
- `vote_up_count` integer NOT NULL,
- `vote_down_count` integer NOT NULL,
- `comment_count` integer UNSIGNED NOT NULL,
- `offensive_flag_count` smallint NOT NULL,
- `last_edited_at` datetime NULL,
- `last_edited_by_id` integer NULL,
- `html` longtext NOT NULL
-)
-;
-ALTER TABLE `answer` ADD CONSTRAINT question_id_refs_id_2300e0297d6550c9 FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
-ALTER TABLE `answer` ADD CONSTRAINT author_id_refs_id_6573e62f192b0170 FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `answer` ADD CONSTRAINT deleted_by_id_refs_id_6573e62f192b0170 FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `answer` ADD CONSTRAINT locked_by_id_refs_id_6573e62f192b0170 FOREIGN KEY (`locked_by_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `answer` ADD CONSTRAINT last_edited_by_id_refs_id_6573e62f192b0170 FOREIGN KEY (`last_edited_by_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `answer_revision` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `answer_id` integer NOT NULL,
- `revision` integer UNSIGNED NOT NULL,
- `author_id` integer NOT NULL,
- `revised_at` datetime NOT NULL,
- `summary` varchar(300) NOT NULL,
- `text` longtext NOT NULL
-)
-;
-ALTER TABLE `answer_revision` ADD CONSTRAINT answer_id_refs_id_47145eaebe77d8fe FOREIGN KEY (`answer_id`) REFERENCES `answer` (`id`);
-ALTER TABLE `answer_revision` ADD CONSTRAINT author_id_refs_id_2c17693c3ccc055f FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `badge` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `name` varchar(50) NOT NULL,
- `type` smallint NOT NULL,
- `slug` varchar(50) NOT NULL,
- `description` varchar(300) NOT NULL,
- `multiple` bool NOT NULL,
- `awarded_count` integer UNSIGNED NOT NULL,
- UNIQUE (`name`, `type`)
-)
-;
-CREATE TABLE `award` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `user_id` integer NOT NULL,
- `badge_id` integer NOT NULL,
- `content_type_id` integer NOT NULL,
- `object_id` integer UNSIGNED NOT NULL,
- `awarded_at` datetime NOT NULL,
- `notified` bool NOT NULL
-)
-;
-ALTER TABLE `award` ADD CONSTRAINT user_id_refs_id_5d197ea32d83e9b6 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `award` ADD CONSTRAINT badge_id_refs_id_4237a025651af0e1 FOREIGN KEY (`badge_id`) REFERENCES `badge` (`id`);
-ALTER TABLE `award` ADD CONSTRAINT content_type_id_refs_id_72f17e2d83bbde26 FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
-CREATE TABLE `repute` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `user_id` integer NOT NULL,
- `positive` smallint NOT NULL,
- `negative` smallint NOT NULL,
- `question_id` integer NOT NULL,
- `reputed_at` datetime NOT NULL,
- `reputation_type` smallint NOT NULL,
- `reputation` integer NOT NULL
-)
-;
-ALTER TABLE `repute` ADD CONSTRAINT user_id_refs_id_fcf719405a426cd FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `repute` ADD CONSTRAINT question_id_refs_id_4749166abeb39c4e FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
-CREATE TABLE `activity` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `user_id` integer NOT NULL,
- `activity_type` smallint NOT NULL,
- `active_at` datetime NOT NULL,
- `content_type_id` integer NOT NULL,
- `object_id` integer UNSIGNED NOT NULL,
- `is_auditted` bool NOT NULL
-)
-;
-ALTER TABLE `activity` ADD CONSTRAINT user_id_refs_id_6015206347c8583f FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `activity` ADD CONSTRAINT content_type_id_refs_id_78877d15efa8edfd FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
-CREATE TABLE `book` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `user_id` integer NOT NULL,
- `title` varchar(255) NOT NULL,
- `short_name` varchar(255) NOT NULL,
- `author` varchar(255) NOT NULL,
- `price` numeric(6, 2) NOT NULL,
- `pages` smallint NOT NULL,
- `published_at` datetime NOT NULL,
- `publication` varchar(255) NOT NULL,
- `cover_img` varchar(255) NOT NULL,
- `tagnames` varchar(125) NOT NULL,
- `added_at` datetime NOT NULL,
- `last_edited_at` datetime NOT NULL
-)
-;
-ALTER TABLE `book` ADD CONSTRAINT user_id_refs_id_607b4cfdf0283c8d FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `book_author_info` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `user_id` integer NOT NULL,
- `book_id` integer NOT NULL,
- `blog_url` varchar(255) NOT NULL,
- `added_at` datetime NOT NULL,
- `last_edited_at` datetime NOT NULL
-)
-;
-ALTER TABLE `book_author_info` ADD CONSTRAINT user_id_refs_id_3781e2a5fbe1cfda FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `book_author_info` ADD CONSTRAINT book_id_refs_id_688c8f047c49bbf8 FOREIGN KEY (`book_id`) REFERENCES `book` (`id`);
-CREATE TABLE `book_author_rss` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `user_id` integer NOT NULL,
- `book_id` integer NOT NULL,
- `title` varchar(255) NOT NULL,
- `url` varchar(255) NOT NULL,
- `rss_created_at` datetime NOT NULL,
- `added_at` datetime NOT NULL
-)
-;
-ALTER TABLE `book_author_rss` ADD CONSTRAINT user_id_refs_id_1fd25dcf3596f741 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `book_author_rss` ADD CONSTRAINT book_id_refs_id_f64066171717121 FOREIGN KEY (`book_id`) REFERENCES `book` (`id`);
-CREATE TABLE `forum_anonymousemail` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `key` varchar(32) NOT NULL,
- `email` varchar(75) NOT NULL UNIQUE,
- `isvalid` bool NOT NULL
-)
-;
-CREATE TABLE `question_tags` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `question_id` integer NOT NULL,
- `tag_id` integer NOT NULL,
- UNIQUE (`question_id`, `tag_id`)
-)
-;
-ALTER TABLE `question_tags` ADD CONSTRAINT question_id_refs_id_35d758e3d99eb83a FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
-ALTER TABLE `question_tags` ADD CONSTRAINT tag_id_refs_id_3b0ddddfbc0346ad FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`);
-CREATE TABLE `question_followed_by` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `question_id` integer NOT NULL,
- `user_id` integer NOT NULL,
- UNIQUE (`question_id`, `user_id`)
-)
-;
-ALTER TABLE `question_followed_by` ADD CONSTRAINT question_id_refs_id_6ea9c52125c22aae FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
-ALTER TABLE `question_followed_by` ADD CONSTRAINT user_id_refs_id_49cca2976d30712d FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `book_question` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `book_id` integer NOT NULL,
- `question_id` integer NOT NULL,
- UNIQUE (`book_id`, `question_id`)
-)
-;
-ALTER TABLE `book_question` ADD CONSTRAINT book_id_refs_id_535ac8946a43c4d1 FOREIGN KEY (`book_id`) REFERENCES `book` (`id`);
-ALTER TABLE `book_question` ADD CONSTRAINT question_id_refs_id_372b7e81c7aff6d8 FOREIGN KEY (`question_id`) REFERENCES `question` (`id`);
-CREATE TABLE `django_authopenid_nonce` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `server_url` varchar(255) NOT NULL,
- `timestamp` integer NOT NULL,
- `salt` varchar(40) NOT NULL
-)
-;
-CREATE TABLE `django_authopenid_association` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `server_url` longtext NOT NULL,
- `handle` varchar(255) NOT NULL,
- `secret` longtext NOT NULL,
- `issued` integer NOT NULL,
- `lifetime` integer NOT NULL,
- `assoc_type` longtext NOT NULL
-)
-;
-CREATE TABLE `django_authopenid_userassociation` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `openid_url` varchar(255) NOT NULL,
- `user_id` integer NOT NULL UNIQUE
-)
-;
-ALTER TABLE `django_authopenid_userassociation` ADD CONSTRAINT user_id_refs_id_f63a9e7163d208d FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `django_authopenid_userpasswordqueue` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `user_id` integer NOT NULL UNIQUE,
- `new_password` varchar(30) NOT NULL,
- `confirm_key` varchar(40) NOT NULL
-)
-;
-ALTER TABLE `django_authopenid_userpasswordqueue` ADD CONSTRAINT user_id_refs_id_7f488ca76bcaaa4 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `django_authopenid_externallogindata` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `external_username` varchar(40) NOT NULL UNIQUE,
- `external_session_data` longtext NOT NULL,
- `user_id` integer NULL
-)
-;
-ALTER TABLE `django_authopenid_externallogindata` ADD CONSTRAINT user_id_refs_id_462c0ee2c3e5e139 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `auth_permission` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `name` varchar(50) NOT NULL,
- `content_type_id` integer NOT NULL,
- `codename` varchar(100) NOT NULL,
- UNIQUE (`content_type_id`, `codename`)
-)
-;
-ALTER TABLE `auth_permission` ADD CONSTRAINT content_type_id_refs_id_6bc81a32728de91f FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`);
-CREATE TABLE `auth_group` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `name` varchar(80) NOT NULL UNIQUE
-)
-;
-CREATE TABLE `auth_user` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `username` varchar(30) NOT NULL UNIQUE,
- `first_name` varchar(30) NOT NULL,
- `last_name` varchar(30) NOT NULL,
- `email` varchar(75) NOT NULL,
- `password` varchar(128) NOT NULL,
- `is_staff` bool NOT NULL,
- `is_active` bool NOT NULL,
- `is_superuser` bool NOT NULL,
- `last_login` datetime NOT NULL,
- `date_joined` datetime NOT NULL,
- `is_approved` bool NOT NULL,
- `email_isvalid` bool NOT NULL,
- `email_key` varchar(32) NULL,
- `reputation` integer UNSIGNED NOT NULL,
- `gravatar` varchar(32) NOT NULL,
- `gold` smallint NOT NULL,
- `silver` smallint NOT NULL,
- `bronze` smallint NOT NULL,
- `questions_per_page` smallint NOT NULL,
- `last_seen` datetime NOT NULL,
- `real_name` varchar(100) NOT NULL,
- `website` varchar(200) NOT NULL,
- `location` varchar(100) NOT NULL,
- `date_of_birth` date NULL,
- `about` longtext NOT NULL
-)
-;
-CREATE TABLE `auth_message` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `user_id` integer NOT NULL,
- `message` longtext NOT NULL
-)
-;
-ALTER TABLE `auth_message` ADD CONSTRAINT user_id_refs_id_7837edc69af0b65a FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-CREATE TABLE `auth_group_permissions` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `group_id` integer NOT NULL,
- `permission_id` integer NOT NULL,
- UNIQUE (`group_id`, `permission_id`)
-)
-;
-ALTER TABLE `auth_group_permissions` ADD CONSTRAINT group_id_refs_id_2ccea4c93cea63fe FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`);
-ALTER TABLE `auth_group_permissions` ADD CONSTRAINT permission_id_refs_id_4de83ca7792de1 FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`);
-CREATE TABLE `auth_user_groups` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `user_id` integer NOT NULL,
- `group_id` integer NOT NULL,
- UNIQUE (`user_id`, `group_id`)
-)
-;
-ALTER TABLE `auth_user_groups` ADD CONSTRAINT user_id_refs_id_1993cb70831107f1 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `auth_user_groups` ADD CONSTRAINT group_id_refs_id_321a8efef0ee9890 FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`);
-CREATE TABLE `auth_user_user_permissions` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `user_id` integer NOT NULL,
- `permission_id` integer NOT NULL,
- UNIQUE (`user_id`, `permission_id`)
-)
-;
-ALTER TABLE `auth_user_user_permissions` ADD CONSTRAINT user_id_refs_id_166738bf2045483 FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-ALTER TABLE `auth_user_user_permissions` ADD CONSTRAINT permission_id_refs_id_6d7fb3c2067e79cb FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`);
-COMMIT;
diff --git a/templates/about.html b/templates/about.html
index 2ca75500..66dcc3fd 100644
--- a/templates/about.html
+++ b/templates/about.html
@@ -12,13 +12,8 @@
</div>
<div class="content">
- <p><strong>NMR Wiki <span class="orange">Q&amp;A</span></strong> is a collaboratively edited question
- and answer site created for the <strong>Magnetic Resonance</strong> community, i.e. those people who
- work in the fields of <strong>NMR</strong>, <strong>EPR</strong>, <strong>MRI</strong></strong>, etc.
- NMR Wiki Q&amp;A is affiliated with <strong><a href="http://nmrwiki.org">NMR Wiki</a></strong> -
- the public wiki knowledge base about the Magnetic Resonance, which currently counts ~300 registered users. The most useful information collected here
- will be further distilled on the wiki site.
- </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
diff --git a/templates/authopenid/complete.html b/templates/authopenid/complete.html
index e3c12ae5..85f6d8b1 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 %}
@@ -34,6 +34,8 @@ parameters:
{% else %}
{% blocktrans %}register new external {{provider}} account info, see {{gravatar_faq_url}}{% endblocktrans %}
{% endif %}
+ {% else %}
+ {% blocktrans %}register new Facebook connect account info, see {{gravatar_faq_url}}{% endblocktrans %}
{% endifequal %}
{% endifequal %}
</div>
@@ -69,7 +71,11 @@ parameters:
{% ifequal login_type 'openid' %}
<form name="fregister" action="{% url user_register %}" method="POST">
{% else %}
- <form name="fregister" action="{% url user_signin %}" method="POST">
+ {% ifequal login_type 'facebook' %}
+ <form name="fregister" action="" method="POST">
+ {% else %}
+ <form name="fregister" action="{% url user_signin %}" method="POST">
+ {% endifequal %}
{% endifequal %}
{{ form1.next }}
<div class="form-row-vertical">
@@ -86,12 +92,12 @@ parameters:
{% endif %}
{{ form1.email }}
</div>
- <p class='nomargin'>{% trans "receive updates motivational blurb" %}</p>
- {% include "edit_user_email_feeds_form.html" %}
-<<<<<<< HEAD:templates/authopenid/complete.html
-=======
<p class='nomargin'>{% trans "Tag filter tool will be your right panel, once you log in." %}</p>
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/authopenid/complete.html
+ <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>
@@ -105,7 +111,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 dda394c7..3318499c 100644
--- a/templates/authopenid/external_legacy_login_info.html
+++ b/templates/authopenid/external_legacy_login_info.html
@@ -8,14 +8,8 @@
</div>
{% spaceless %}
<div class="message">
-<<<<<<< HEAD:templates/authopenid/external_legacy_login_info.html
-fill in template templates/authopenid/external_legacy_login_info.html
-and explain how to change password, recover password, etc.
<!--add info about your external login site here-->
-=======
-<!--add info about your external login site here-->
-{% trans "how to login with password through external login website" %}
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/authopenid/external_legacy_login_info.html
+{% blocktrans %}how to login with password through external login website or use {{feedback_url}}{% endblocktrans %}
</div>
{% endspaceless %}
{% endblock %}
diff --git a/templates/authopenid/signin.html b/templates/authopenid/signin.html
index 1363661e..51b8aa7f 100644..100755
--- a/templates/authopenid/signin.html
+++ b/templates/authopenid/signin.html
@@ -1,173 +1,186 @@
-{% extends "base.html" %}
-<!-- signin.html -->
-{% load i18n %}
-{% load extra_tags %}
-{% block title %}{% spaceless %}{% trans "User login" %}{% endspaceless %}{% endblock %}
-{% block forejs %}
- <script type='text/javascript' src='{% href "/content/js/jquery.validate.pack.js" %}'></script>
-
- <link rel="stylesheet" type="text/css" media="screen" href="{% href "/content/jquery-openid/openid.css" %}"/>
- <script type="text/javascript" src="{% href "/content/jquery-openid/jquery.openid.js" %}"></script>
- <script type="text/javascript"> $().ready( function() { $("form.openid:eq(0)").openid(); })</script>
- <!--<script type="text/javascript">
- $().ready(function(){
- openid.init('id_openid_url');
- setupFormValidation("#openid_form", {bsignin:{required: true}});
- });
- </script>-->
-{% endblock %}
-{% block content %}
-<div class="headNormal">
- {% trans "User login" %}
-</div>
- {% if msg %}
- <p class="warning">{{ msg }}</p>
- {% endif %}
- {% if answer %}
- <div class="message">
- {% blocktrans with answer.question.title as title and answer.summary as summary %}
- Your answer to {{title}} {{summary}} will be posted once you log in
- {% endblocktrans %}
- </div>
- {% endif %}
- {% if question %}
- <div class="message">
- {% blocktrans with question.title as title and question.summary as summary %}Your question
- {{title}} {{summary}} will be posted once you log in
- {% endblocktrans %}
- </div>
- {% endif %}
- <form id="openid_form" name="openid_form" class="openid" method="post" action="{% url user_signin %}">
- <div style="width:600px;float:left;margin-bottom:5px;">
- {% trans "Click to sign in through any of these services." %}
- </div>
- <ul class="providers">
- <li class="local" title="Local login">
- <div class="logo_box local_login_box">
- <img src="{% href "/content/jquery-openid/images/local-login.png" %}" alt="your icon here" />
- </div>
- <span></span>
- </li>
- <li class="direct" title="Google">
- <div class="logo_box google_box">
- <img src="{% href "/content/jquery-openid/images/google.gif" %}" alt="icon" /><span>https://www.google.com/accounts/o8/id</span>
- </div>
- </li>
- <li class="direct" title="Yahoo">
- <div class="logo_box yahoo_box">
- <img src="{% href "/content/jquery-openid/images/yahoo.gif" %}" alt="icon" /><span>http://yahoo.com/</span>
- </div>
- </li>
- <li class="username" title="AOL screen name">
- <div class="logo_box aol_box">
- <img src="{% href "/content/jquery-openid/images/aol.gif" %}" alt="icon" /><span>http://openid.aol.com/<strong>username</strong></span>
- </div>
- </li>
- </ul>
- <ul class="providers">
- <!--<li class="openid" title="OpenID">
- <div class="logo_box openid_box">
- <img src="/content/jquery-openid/images/openid.gif" alt="icon" />
- </div>
- <span><strong>http://{your-openid-url}</strong></span>
- </li>-->
- <li class="openid first_tiny_li" title="OpenID URL">
- <img src="{% href "/content/jquery-openid/images/openidico16.png" %}" alt="icon" />
- <span>http://{your-openid-url}</span>
- </li>
- <li class="username" title="MyOpenID user name">
- <img src="{% href "/content/jquery-openid/images/myopenid-2.png" %}" alt="icon" />
- <span>http://<strong>username</strong>.myopenid.com/</span>
- </li>
- <li class="username" title="Flickr user name">
- <img src="{% href "/content/jquery-openid/images/flickr.png" %}" alt="icon" />
- <span>http://flickr.com/<strong>username</strong>/</span>
- </li>
- <li class="username" title="Technorati user name">
- <img src="{% href "/content/jquery-openid/images/technorati-1.png" %}" alt="icon" />
- <span>http://technorati.com/people/technorati/<strong>username</strong>/</span>
- </li>
- <li class="username" title="Wordpress blog name">
- <img src="{% href "/content/jquery-openid/images/wordpress.png" %}" alt="icon" />
- <span>http://<strong>username</strong>.wordpress.com</span>
- </li>
- <li class="username" title="Blogger blog name">
- <img src="{% href "/content/jquery-openid/images/blogger-1.png" %}" alt="icon" />
- <span>http://<strong>username</strong>.blogspot.com/</span>
- </li>
- <li class="username" title="LiveJournal blog name">
- <img src="{% href "/content/jquery-openid/images/livejournal-1.png" %}" alt="icon" />
- <span>http://<strong>username</strong>.livejournal.com</span>
- </li>
- <li class="username" title="ClaimID user name">
- <img src="{% href "/content/jquery-openid/images/claimid-0.png" %}" alt="icon" />
- <span>http://claimid.com/<strong>username</strong></span>
- </li>
- <li class="username" title="Vidoop user name">
- <img src="{% href "/content/jquery-openid/images/vidoop.png" %}" alt="icon" />
- <span>http://<strong>username</strong>.myvidoop.com/</span>
- </li>
- <li class="username" title="Verisign user name">
- <img src="{% href "/content/jquery-openid/images/verisign-2.png" %}" alt="icon" />
- <span>http://<strong>username</strong>.pip.verisignlabs.com/</span>
- </li>
- </ul>
- {{ form2.next }}
- <fieldset>
- <p id="provider_name_slot">{% trans 'Enter your <span id="enter_your_what">Provider user name</span>' %}</p>
- <div><p><span></span>
- <input id="openid_username" type="text" name="openid_username" /><span></span>
- <input type="submit" value="Login" />
- </p></div>
- </fieldset>
- <fieldset>
- <p>{% trans 'Enter your <a class="openid_logo" href="http://openid.net">OpenID</a> web address' %}</p>
- <div><p><input id="openid_url" type="text" value="http://" name="openid_url" />
- <input id="bsignin" name="bsignin" type="submit" value="{% trans "Login" %}" /></p></div>
- </fieldset>
- <fieldset id='local_login_fs'>
- <p>{% trans 'Enter your login name and password' %}</p>
- {% if form1.errors %}
- {{form1.non_field_errors.as_ul}}
- {% endif %}
- <div><p class="login"><label for="id_username">{% trans "Login name" %}</label>
- {{form1.username}}</p>
- <p class="login"><label for="id_password">{% trans "Password" %}</label>
- {{form1.password}}</p>
- <p id="local_login_buttons">
- <input id="blogin" name="blogin" type="submit" value="{% trans "Login" %}" />
- <a href="{% url user_signup %}">{% trans "Create account" %}</a><br/>
- <a href="{% url user_sendpw %}">{% trans "Forgot your password?" %}</a>
- </p>
- </div>
- </fieldset>
- </form>
-{% endblock %}
-
-{% block sidebar %}
-<div class="boxC">
- <h3 class="subtitle">{% trans "Why use OpenID?" %}</h3>
- <ul class="list-item">
- <li>
- {% trans "with openid it is easier" %}
- </li>
- <li>
- {% trans "reuse openid" %}
- </li>
- <li>
- {% trans "openid is widely adopted" %}
- </li>
- <li>
- {% trans "openid is supported open standard" %}
- </li>
-
- </ul>
- <p class="info-box-follow-up-links">
- <a href="http://openid.net/what/" target="_blank">{% trans "Find out more" %} »</a><br/>
- <a href="http://openid.net/get/" target="_blank">{% trans "Get OpenID" %} »</a>
- </p>
-</div>
-{% endblock%}
-
- <script type="text/javascript"> $( function() { $("form.openid:eq(0)").openid(); })</script>
-<!-- end signin.html -->
+{% extends "base.html" %}
+<!-- signin.html -->
+{% load i18n %}
+{% load extra_tags %}
+{% block title %}{% spaceless %}{% trans "User login" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type='text/javascript' src='{% href "/content/js/jquery.validate.pack.js" %}'></script>
+
+ <link rel="stylesheet" type="text/css" media="screen" href="{% href "/content/jquery-openid/openid.css" %}"/>
+ <script type="text/javascript" src="{% href "/content/jquery-openid/jquery.openid.js" %}"></script>
+ <script type="text/javascript"> $().ready( function() { $("form.openid:eq(0)").openid(); })</script>
+ <!--<script type="text/javascript">
+ $().ready(function(){
+ openid.init('id_openid_url');
+ setupFormValidation("#openid_form", {bsignin:{required: true}});
+ });
+ </script>-->
+{% endblock %}
+{% block content %}
+<div class="headNormal">
+ {% trans "User login" %}
+</div>
+ {% if msg %}
+ <p class="warning">{{ msg }}</p>
+ {% endif %}
+ {% if answer %}
+ <div class="message">
+ {% blocktrans with answer.question.title as title and answer.summary as summary %}
+ Your answer to {{title}} {{summary}} will be posted once you log in
+ {% endblocktrans %}
+ </div>
+ {% endif %}
+ {% if question %}
+ <div class="message">
+ {% blocktrans with question.title as title and question.summary as summary %}Your question
+ {{title}} {{summary}} will be posted once you log in
+ {% endblocktrans %}
+ </div>
+ {% endif %}
+ <form id="openid_form" name="openid_form" class="openid" method="post" action="{% url user_signin %}">
+ <div style="width:600px;float:left;margin-bottom:5px;">
+ {% trans "Click to sign in through any of these services." %}
+ </div>
+ <ul class="providers">
+ <li class="local" title="Local login">
+ <div class="logo_box local_login_box">
+ <img src="{% href "/content/jquery-openid/images/local-login.png" %}" alt="your icon here" />
+ </div>
+ <span></span>
+ </li>
+ <li class="direct" title="Google">
+ <div class="logo_box google_box">
+ <img src="{% href "/content/jquery-openid/images/google.gif" %}" alt="icon" /><span>https://www.google.com/accounts/o8/id</span>
+ </div>
+ </li>
+ <li class="direct" title="Yahoo">
+ <div class="logo_box yahoo_box">
+ <img src="{% href "/content/jquery-openid/images/yahoo.gif" %}" alt="icon" /><span>http://yahoo.com/</span>
+ </div>
+ </li>
+ <li class="username" title="AOL screen name">
+ <div class="logo_box aol_box">
+ <img src="{% href "/content/jquery-openid/images/aol.gif" %}" alt="icon" /><span>http://openid.aol.com/<strong>username</strong></span>
+ </div>
+ </li>
+ </ul>
+ <ul id="openid_small_providers" class="providers">
+ <!--<li class="openid" title="OpenID">
+ <div class="logo_box openid_box">
+ <img src="/content/jquery-openid/images/openid.gif" alt="icon" />
+ </div>
+ <span><strong>http://{your-openid-url}</strong></span>
+ </li>-->
+ <li class="direct first_tiny_li facebook" title="Facebook Connect">
+ {% if question %}
+ <fb:login-button onlogin="window.location = '{% url fb_signin_new_question %}'"></fb:login-button>
+ {% else %}
+ {% if answer %}
+ <fb:login-button onlogin="window.location = '{% url fb_signin_new_answer %}'"></fb:login-button>
+ {% else %}
+ <fb:login-button onlogin="window.location = '{% url fb_signin %}'"></fb:login-button>
+ {% endif %}
+ {% endif %}
+ </li>
+ <li class="openid" title="OpenID URL">
+ <img src="{% href "/content/jquery-openid/images/openidico16.png" %}" alt="icon" />
+ <span>http://{your-openid-url}</span>
+ </li>
+ <li class="username" title="MyOpenID user name">
+ <img src="{% href "/content/jquery-openid/images/myopenid-2.png" %}" alt="icon" />
+ <span>http://<strong>username</strong>.myopenid.com/</span>
+ </li>
+ <li class="username" title="Flickr user name">
+ <img src="{% href "/content/jquery-openid/images/flickr.png" %}" alt="icon" />
+ <span>http://flickr.com/<strong>username</strong>/</span>
+ </li>
+ <li class="username" title="Technorati user name">
+ <img src="{% href "/content/jquery-openid/images/technorati-1.png" %}" alt="icon" />
+ <span>http://technorati.com/people/technorati/<strong>username</strong>/</span>
+ </li>
+ <li class="username" title="Wordpress blog name">
+ <img src="{% href "/content/jquery-openid/images/wordpress.png" %}" alt="icon" />
+ <span>http://<strong>username</strong>.wordpress.com</span>
+ </li>
+ <li class="username" title="Blogger blog name">
+ <img src="{% href "/content/jquery-openid/images/blogger-1.png" %}" alt="icon" />
+ <span>http://<strong>username</strong>.blogspot.com/</span>
+ </li>
+ <li class="username" title="LiveJournal blog name">
+ <img src="{% href "/content/jquery-openid/images/livejournal-1.png" %}" alt="icon" />
+ <span>http://<strong>username</strong>.livejournal.com</span>
+ </li>
+ <li class="username" title="ClaimID user name">
+ <img src="{% href "/content/jquery-openid/images/claimid-0.png" %}" alt="icon" />
+ <span>http://claimid.com/<strong>username</strong></span>
+ </li>
+ <li class="username" title="Vidoop user name">
+ <img src="{% href "/content/jquery-openid/images/vidoop.png" %}" alt="icon" />
+ <span>http://<strong>username</strong>.myvidoop.com/</span>
+ </li>
+ <li class="username" title="Verisign user name">
+ <img src="{% href "/content/jquery-openid/images/verisign-2.png" %}" alt="icon" />
+ <span>http://<strong>username</strong>.pip.verisignlabs.com/</span>
+ </li>
+ </ul>
+ {{ form2.next }}
+ <fieldset>
+ <p id="provider_name_slot">{% trans 'Enter your <span id="enter_your_what">Provider user name</span>' %}</p>
+ <div><p><span></span>
+ <input id="openid_username" type="text" name="openid_username" /><span></span>
+ <input type="submit" value="Login" />
+ </p></div>
+ </fieldset>
+ <fieldset>
+ <p>{% trans 'Enter your <a class="openid_logo" href="http://openid.net">OpenID</a> web address' %}</p>
+ <div><p><input id="openid_url" type="text" value="http://" name="openid_url" />
+ <input id="bsignin" name="bsignin" type="submit" value="{% trans "Login" %}" /></p></div>
+ </fieldset>
+ <fieldset id='local_login_fs'>
+ <p>{% trans 'Enter your login name and password' %}</p>
+ {% if form1.errors %}
+ {{form1.non_field_errors.as_ul}}
+ {% endif %}
+ <div><p class="login"><label for="id_username">{% trans "Login name" %}</label>
+ {{form1.username}}</p>
+ <p class="login"><label for="id_password">{% trans "Password" %}</label>
+ {{form1.password}}</p>
+ <p id="local_login_buttons">
+ <input id="blogin" name="blogin" type="submit" value="{% trans "Login" %}" />
+ <a href="{% url user_signup %}">{% trans "Create account" %}</a><br/>
+ <a href="{% url user_sendpw %}">{% trans "Forgot your password?" %}</a>
+ </p>
+ </div>
+ </fieldset>
+ </form>
+{% endblock %}
+
+{% block sidebar %}
+<div class="boxC">
+ <h3 class="subtitle">{% trans "Why use OpenID?" %}</h3>
+ <ul class="list-item">
+ <li>
+ {% trans "with openid it is easier" %}
+ </li>
+ <li>
+ {% trans "reuse openid" %}
+ </li>
+ <li>
+ {% trans "openid is widely adopted" %}
+ </li>
+ <li>
+ {% trans "openid is supported open standard" %}
+ </li>
+
+ </ul>
+ <p class="info-box-follow-up-links">
+ <a href="http://openid.net/what/" target="_blank">{% trans "Find out more" %} »</a><br/>
+ <a href="http://openid.net/get/" target="_blank">{% trans "Get OpenID" %} »</a>
+ </p>
+</div>
+<script type="text/javascript" src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php"></script>
+<script type="text/javascript"> FB.init("{{ fb_api_key }}","{% url xd_receiver %}");</script>
+{% endblock%}
+
+ <script type="text/javascript"> $( function() { $("form.openid:eq(0)").openid(); })</script>
+<!-- end signin.html -->
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 b4751be1..d4eb731b 100644..100755
--- a/templates/base.html
+++ b/templates/base.html
@@ -3,7 +3,7 @@
{% load extra_filters %}
{% load extra_tags %}
{% load i18n %}
-<html xmlns="http://www.w3.org/1999/xhtml">
+<html xmlns="http://www.w3.org/1999/xhtml"{% if fb_api_key %} xmlns:fb="http://www.facebook.com/2008/fbml"{% endif %}>
<head>
<title>{% block title %}{% endblock %} - {{ settings.APP_TITLE }}</title>
{% spaceless %}
@@ -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" />
@@ -50,7 +50,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();
});
@@ -65,7 +65,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/base_content.html b/templates/base_content.html
index 12297215..0ff4a9fd 100644
--- a/templates/base_content.html
+++ b/templates/base_content.html
@@ -10,7 +10,7 @@
<meta name="verify-v1" content="{{ settings.GOOGLE_SITEMAP_CODE }}" />
=======
{% 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 %}
>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/base_content.html
<link rel="shortcut icon" href="{% href "/content/images/favicon.ico" %}" />
@@ -56,7 +56,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();
@@ -72,7 +72,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
index b53732e3..6a250e35 100644
--- a/templates/content/images/logo.png
+++ b/templates/content/images/logo.png
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/jquery-openid/openid.css b/templates/content/jquery-openid/openid.css
index 88960b56..1b7aaf82 100644
--- a/templates/content/jquery-openid/openid.css
+++ b/templates/content/jquery-openid/openid.css
@@ -67,3 +67,9 @@ form.openid ul.errorlist li {
float: none;
color:blue;
}
+#openid_small_providers li {
+ margin-top:4px;
+}
+#openid_small_providers li.facebook {
+ margin-top:0px;
+}
diff --git a/templates/content/js/com.cnprog.admin.js b/templates/content/js/com.cnprog.admin.js
index 7e91af79..974dce23 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 018927aa..58cb8f16 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 a884b571..668c80fe 100644
--- a/templates/content/js/com.cnprog.post.js
+++ b/templates/content/js/com.cnprog.post.js
@@ -27,6 +27,8 @@ var lanai =
var Vote = function(){
// All actions are related to a question
var questionId;
+ //question slug to build redirect urls
+ var questionSlug;
// The object we operate on actually. It can be a question or an answer.
var postId;
var questionAuthorId;
@@ -53,21 +55,13 @@ var Vote = function(){
var acceptAnonymousMessage = $.i18n._('insufficient privilege');
var acceptOwnAnswerMessage = $.i18n._('cannot pick own answer as best');
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- var pleaseLogin = "<a href='" + $.i18n._("/") + $.i18n._("account/") + $.i18n._("signin/")
- + "?next=" + $.i18n._("/") + $.i18n._("questions/") + "{{QuestionID}}'>"
- + $.i18n._('please login') + "</a>";
-
- var pleaseSeeFAQ = $.i18n._('please see') + "<a href='" + $.i18n._("/") + $.i18n._("faq/") + "'>faq</a>";
-=======
var pleaseLogin = "<a href='" + scriptUrl + $.i18n._("account/") + $.i18n._("signin/")
- + "?next=" + scriptUrl + $.i18n._("questions/") + "{{QuestionID}}'>"
+ + "?next=" + scriptUrl + $.i18n._("question/") + "{{QuestionID}}/{{questionSlug}}'>"
+ $.i18n._('please login') + "</a>";
var pleaseSeeFAQ = $.i18n._('please see') + "<a href='" + scriptUrl + $.i18n._("faq/") + "'>faq</a>";
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
- 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;
@@ -159,30 +153,17 @@ var Vote = function(){
var setVoteImage = function(voteType, undo, object){
var flag = undo ? "" : "-on";
var arrow = (voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote) ? "up" : "down";
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- object.attr("src", $.i18n._("/") + "content/images/vote-arrow-"+ arrow + flag +".png");
-=======
object.attr("src", scriptUrl + "content/images/vote-arrow-"+ arrow + flag +".png");
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
// if undo voting, then undo the pair of arrows.
if(undo){
if(voteType == VoteType.questionUpVote || voteType == VoteType.questionDownVote){
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- $(getQuestionVoteUpButton()).attr("src", $.i18n._("/") + "content/images/vote-arrow-up.png");
- $(getQuestionVoteDownButton()).attr("src", $.i18n._("/") + "content/images/vote-arrow-down.png");
- }
- else{
- $(getAnswerVoteUpButton(postId)).attr("src", $.i18n._("/") + "content/images/vote-arrow-up.png");
- $(getAnswerVoteDownButton(postId)).attr("src", $.i18n._("/") + "content/images/vote-arrow-down.png");
-=======
$(getQuestionVoteUpButton()).attr("src", scriptUrl + "content/images/vote-arrow-up.png");
$(getQuestionVoteDownButton()).attr("src", scriptUrl + "content/images/vote-arrow-down.png");
}
else{
$(getAnswerVoteUpButton(postId)).attr("src", scriptUrl + "content/images/vote-arrow-up.png");
$(getAnswerVoteDownButton(postId)).attr("src", scriptUrl + "content/images/vote-arrow-down.png");
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
}
}
};
@@ -258,14 +239,11 @@ var Vote = function(){
type: "POST",
cache: false,
dataType: "json",
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- url: $.i18n._("/") + $.i18n._("questions/") + questionId + "/" + $.i18n._("vote/"),
-=======
url: scriptUrl + $.i18n._("questions/") + questionId + "/" + $.i18n._("vote/"),
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
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){
@@ -281,31 +259,19 @@ var Vote = function(){
showMessage(object, acceptOwnAnswerMessage);
}
else if(data.status == "1"){
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- object.attr("src", $.i18n._("/") + "content/images/vote-accepted.png");
-=======
object.attr("src", scriptUrl + "content/images/vote-accepted.png");
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
$("#"+answerContainerIdPrefix+postId).removeClass("accepted-answer");
$("#"+commentLinkIdPrefix+postId).removeClass("comment-link-accepted");
}
else if(data.success == "1"){
var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- $(acceptedButtons).attr("src", $.i18n._("/") + "content/images/vote-accepted.png");
-=======
$(acceptedButtons).attr("src", scriptUrl + "content/images/vote-accepted.png");
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
var answers = ("div[id^="+answerContainerIdPrefix +"]");
$(answers).removeClass("accepted-answer");
var commentLinks = ("div[id^="+answerContainerIdPrefix +"] div[id^="+ commentLinkIdPrefix +"]");
$(commentLinks).removeClass("comment-link-accepted");
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- object.attr("src", $.i18n._("/") + "content/images/vote-accepted-on.png");
-=======
object.attr("src", scriptUrl + "content/images/vote-accepted-on.png");
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
$("#"+answerContainerIdPrefix+postId).addClass("accepted-answer");
$("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted");
}
@@ -319,23 +285,16 @@ var Vote = function(){
showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
}
else if(data.status == "1"){
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- object.attr("src", $.i18n._("/") + "content/images/vote-favorite-off.png");
-=======
object.attr("src", scriptUrl + "content/images/vote-favorite-off.png");
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
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"){
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- object.attr("src", $.i18n._("/") + "/content/images/vote-favorite-on.png");
-=======
object.attr("src", scriptUrl + "content/images/vote-favorite-on.png");
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
var fav = getFavoriteNumber();
fav.text(data.count);
fav.addClass("my-favorite-number");
@@ -404,11 +363,7 @@ var Vote = function(){
}
else if (data.success == "1"){
if (voteType == VoteType.removeQuestion){
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- window.location.href = $.i18n._("/") + $.i18n._("questions/");
-=======
window.location.href = scriptUrl + $.i18n._("questions/");
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
}
else {
if (removeActionType == 'delete'){
@@ -426,8 +381,9 @@ var Vote = function(){
};
return {
- init : function(qId, questionAuthor, userId){
+ init : function(qId, qSlug, questionAuthor, userId){
questionId = qId;
+ questionSlug = qSlug;
questionAuthorId = questionAuthor;
currentUserId = userId;
bindEvents();
@@ -449,7 +405,7 @@ var Vote = function(){
vote: function(object, voteType){
if (!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
+ showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId).replace("{{questionSlug}}", questionSlug));
return false;
}
if (voteType == VoteType.answerUpVote){
@@ -502,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) {
@@ -528,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);
@@ -545,28 +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')
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- + '<a href="' + $.i18n._('/') + $.i18n._('faq/') + '" class="comment-user">'
-=======
- + '<a href="' + scriptUrl + $.i18n._('faq/') + '" class="comment-user">'
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
- + $.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);
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- $.getJSON($.i18n._("/") + objectType + "s/" + id + "/" + $.i18n._("comments/")
-=======
- $.getJSON(scriptUrl + objectType + "s/" + id + "/" + $.i18n._("comments/")
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
- , function(json) { showComments(id, json); });
+ $.getJSON(scriptUrl + objectType + "s/" + id + "/" + $.i18n._("comments/"),
+ function(json) { showComments(id, json); });
};
var showComments = function(id, json) {
@@ -577,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();
}
};
@@ -586,22 +538,17 @@ function createComments(type) {
var renderDeleteCommentIcon = function(post_id, delete_url){
if (canPostComments(post_id)){
var html = '';
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- var img = $.i18n._("/") + "content/images/close-small.png";
- var imgHover = $.i18n._("/") + "content/images/close-small-hover.png";
-=======
var img = scriptUrl + "content/images/close-small.png";
var imgHover = scriptUrl + "content/images/close-small-hover.png";
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
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) {
@@ -631,11 +578,7 @@ function createComments(type) {
//todo fix url translations!!!
$.ajax({
type: "POST",
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- url: $.i18n._("/") + objectType + "s/" + id + "/" + $.i18n._("comments/"),
-=======
url: scriptUrl + objectType + "s/" + id + "/" + $.i18n._("comments/"),
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
dataType: "json",
data: { comment: textarea.val() },
success: function(json) {
@@ -667,12 +610,8 @@ function createComments(type) {
$(this).children().each(
function(i){
var comment_id = $(this).attr('id').replace('comment-','');
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- var delete_url = $.i18n._('/') + objectType + 's/' + post_id + '/'
-=======
- var delete_url = scriptUrl + objectType + 's/' + post_id + '/'
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
- + $.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'){
@@ -685,20 +624,12 @@ function createComments(type) {
delete_icon.click(function(){CommentsClass.deleteComment($(this),comment_id,delete_url);});
delete_icon.unbind('mouseover').bind('mouseover',
function(){
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- $(this).attr('src',$.i18n._('/') + 'content/images/close-small-hover.png');
-=======
$(this).attr('src',scriptUrl + 'content/images/close-small-hover.png');
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
}
);
delete_icon.unbind('mouseout').bind('mouseout',
function(){
-<<<<<<< HEAD:templates/content/js/com.cnprog.post.js
- $(this).attr('src',$.i18n._('/') + 'content/images/close-small.png');
-=======
$(this).attr('src',scriptUrl + 'content/images/close-small.png');
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.post.js
}
);
}
@@ -713,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);
@@ -741,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 5c0c4a27..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(); });
@@ -23,11 +24,7 @@ var notify = function() {
},
close: function(doPostback) {
if (doPostback) {
-<<<<<<< HEAD:templates/content/js/com.cnprog.utils.js
- $.post($.i18n._("/") + $.i18n._("messages/") +
-=======
$.post(scriptUrl + $.i18n._("messages/") +
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.utils.js
$.i18n._("markread/"), { formdata: "required" });
}
$(".notify").fadeOut("fast");
@@ -39,22 +36,30 @@ var notify = function() {
} ();
function appendLoader(containerSelector) {
- $(containerSelector).append('<img class="ajax-loader" '
-<<<<<<< HEAD:templates/content/js/com.cnprog.utils.js
- +'src="' + $.i18n._('/') + 'content/images/indicator.gif" title="'
-=======
- +'src="' + scriptUrl + 'content/images/indicator.gif" title="'
->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/js/com.cnprog.utils.js
- +$.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({
@@ -64,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);
@@ -72,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(){
@@ -116,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/style/style.css b/templates/content/style/style.css
index cf35ff68..3ff95fd2 100644
--- a/templates/content/style/style.css
+++ b/templates/content/style/style.css
@@ -204,7 +204,6 @@ blockquote
/*border-bottom:1px solid #888a85;*/
}
.evenMore {font-size:14px; font-weight:800;}
-
.questions-count{
font-size:32px;
font-family:sans-serif;
@@ -687,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;
@@ -1159,291 +1163,305 @@ 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;
}
- .warning{color:red;}
- .darkred{color:darkred;}
- .submit{
- cursor:pointer;
- /*letter-spacing:1px;*/
- background-color:#D4D0C8;
- height:40px;
- border:1px solid #777777;
- /* width:100px; */
- font-weight:bold;
- padding-bottom:4px;
- font-size:120%;}
- .submit:hover{text-decoration:underline;}
- .ask-body{padding-right:10px;}
- .thousand{color:orange;}
-
- .notify
- {
- position: fixed;
- top: 0px;
- left: 0px;
- width: 100%;
- z-index: 100;
- padding: 0;
- text-align: center;
- font-weight: Bold;
- color: #444;
- background-color: #F4A83D;
- }
-
- .notify p {
- margin-top:5px;
- margin-bottom:5px;
- font-size:16px;
- }
-
- #close-notify
- {
- position:absolute;
- right:5px;
- top:5px;
- padding:0 3px 0 3px;
- color: #735005;
- text-decoration: none;
- font-size:14px;
- line-height:18px;
- background-color: #FAD163;
- border: 2px #735005 solid;
- cursor:pointer;
- }
- #close-notify:hover {
- text-decoration:none;
- }
-
- .big {
- font-size:15px;
- }
- .bigger {
- font-size:14px;
- }
- .strong {
- font-weight:bold;
- }
- .orange
- {
- color:#d64000;
- font-weight:bold;
- }
- .grey {
- color:#808080;
- }
- .about div {
- padding:10px 5px 10px 5px;
- border-top:1px dashed #aaaaaa;
- }
- .about div.first {
- padding-top:0;
- border-top:none;
- }
- .about p {
- margin-bottom:10px;
- }
- .about a {color:#d64000;text-decoration:underline;}
- .about h3{
- line-height:30px;
- font-size:15px;
- font-weight:700;
- padding-top: 0px;
- }
- .highlight {
- background-color:#FFF8C6;
- }
- .nomargin {
- margin:0;
- }
- .margin-bottom {
- margin-bottom: 10px;
- }
- .inline-block {
- display:inline-block;
- }
- .action-status {
- margin:0;
- border:none;
- text-align:center;
- line-height:10px;
- font-size:12px;
- padding:0;
- }
- .action-status span {
- padding:3px 5px 3px 5px;
- background-color:#fff380;/* nice yellow */
- font-weight:normal;
- -moz-border-radius: 5px;
- -khtml-border-radius: 5px;
- -webkit-border-radius: 5px;
- }
- .tight {
- margin:0;
- padding:0;
- }
-
- .list-table td {
- vertical-align:top;
- }
-
- p.comment {
- border-top: 1px dotted #ccccce;
- margin:0;
- font-size:11px;
- color: #444444;
- padding:5px 0 5px 0;
- }
-
- .delete-icon {
- vertical-align:middle;
- padding-left:3px;
- }
- /* these need to go */
- table.form-as-table .errorlist {
- display: block;
- margin:0;
- padding:0 0 0 5px;
- text-align:left;
- font-size:10px;
- color:darkred;
- }
- table.form-as-table input {
- display: inline;
- margin-left: 4px;
- }
- table.form-as-table th {
- vertical-align:bottom;
- padding-bottom:4px;
- }
- .form-row-vertical {
- margin-top: 8px;
- display: block;
- }
- .form-row-vertical label {
- margin-bottom:3px;
- display:block;
- }
- /* above stuff needs to go */
- .text-align-right {
- text-align: center;
- }
- ul.form-horizontal-rows {
- list-style:none;
- margin:0;
- }
- ul.form-horizontal-rows li {
- position:relative;
- height:40px;
- }
- ul.form-horizontal-rows label {
- display:inline-block;
- }
- ul.form-horizontal-rows ul.errorlist {
- list-style:none;
- color:darkred;
- font-size:10px;
- line-height:10px;
- position:absolute;
- top:2px;
- left:180px;
- text-align:left;
- margin:0;
- }
- ul.form-horizontal-rows ul.errorlist li {
- height:10px;
- }
- ul.form-horizontal-rows label {
- position:absolute;
- left:0px;
- bottom:6px;
- margin:0px;
- line-height: 12px;
- font-size: 12px;
- }
- ul.form-horizontal-rows li input {
- position:absolute;
- bottom:0px;
- left:180px;
- margin:0px;
- }
- #emailpw-form li input {
- left:170px;
- }
- #emailpw-form ul.errorlist {
- left:170px;
- }
- #changepw-form li input {
- left:150px;
- }
- #changepw-form ul.errorlist {
- left:150px;
- }
- .narrow .summary {
- float: left;
- }
- .narrow .summary .question-title {
- font-weight: bold;
- font-size: 120%;
- }
- .user-profile-tool-links {
- padding-bottom:10px;
- font-weight: bold;
- }
- .post-controls {
- float:left;
- font-size:11px;
- line-height:12px;
- min-width:200px;
- margin-bottom:5px;
- }
- #question-controls .tags {
- margin:0 0 3px 0;
- }
- .post-update-info-container {
- float: right;
- min-width:190px;
- }
- .post-update-info {
- display:inline-block;
- float:right;
- width:190px;
- margin-bottom:5px;
- }
- .post-update-info p {
- font-size:11px;
- line-height:15px;
- margin:0 0 4px 0;
- padding:0;
- }
- .post-update-info img {
- float: left;
- width: 32px;
- margin: 4px 8px 0 0;
- }
- .comments-container {
- clear:both;
- }
- .admin {
- background-color:#fff380;/* nice yellow */
- border: 1px solid darkred;
- padding: 0 5px 0 5px;
- }
- .admin p {
- margin-bottom: 3px;
- }
- .admin #action_status {
- text-align:center;
- font-weight:bold;
- }
- #tagSelector {
- padding-bottom: 2px;
- }
- #hideIgnoredTagsControl {
- margin: 5px 0 0 0;
- }
- #hideIgnoredTagsCb {
- margin: 0 2px 0 1px;
- }
+.warning{color:red;}
+.darkred{color:darkred;}
+.submit{
+ cursor:pointer;
+ /*letter-spacing:1px;*/
+ background-color:#D4D0C8;
+ height:40px;
+ border:1px solid #777777;
+/* width:100px; */
+ font-weight:bold;
+ padding-bottom:4px;
+ font-size:120%;}
+.submit:hover{text-decoration:underline;}
+.ask-body{padding-right:10px;}
+.thousand{color:orange;}
+
+.notify
+{
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ z-index: 100;
+ padding: 0;
+ text-align: center;
+ font-weight: Bold;
+ color: #444;
+ background-color: #F4A83D;
+}
+
+.notify p {
+ margin-top:5px;
+ margin-bottom:5px;
+ font-size:16px;
+}
+
+#close-notify
+{
+ position:absolute;
+ right:5px;
+ top:5px;
+ padding:0 3px 0 3px;
+ color: #735005;
+ text-decoration: none;
+ font-size:14px;
+ line-height:18px;
+ background-color: #FAD163;
+ border: 2px #735005 solid;
+ cursor:pointer;
+}
+#close-notify:hover {
+ text-decoration:none;
+}
+
+.big {
+ font-size:15px;
+}
+.bigger {
+ font-size:14px;
+}
+.strong {
+ font-weight:bold;
+}
+.orange
+{
+ color:#d64000;
+ font-weight:bold;
+}
+.grey {
+ color:#808080;
+}
+.about div {
+ padding:10px 5px 10px 5px;
+ border-top:1px dashed #aaaaaa;
+}
+.about div.first {
+ padding-top:0;
+ border-top:none;
+}
+.about p {
+ margin-bottom:10px;
+}
+.about a {color:#d64000;text-decoration:underline;}
+.about h3{
+ line-height:30px;
+ font-size:15px;
+ font-weight:700;
+ padding-top: 0px;
+}
+.highlight {
+ background-color:#FFF8C6;
+}
+.nomargin {
+ margin:0;
+}
+.margin-bottom {
+ margin-bottom: 10px;
+}
+.inline-block {
+ display:inline-block;
+}
+.action-status {
+ margin:0;
+ border:none;
+ text-align:center;
+ line-height:10px;
+ font-size:12px;
+ padding:0;
+}
+.action-status span {
+ padding:3px 5px 3px 5px;
+ background-color:#fff380;/* nice yellow */
+ font-weight:normal;
+ -moz-border-radius: 5px;
+ -khtml-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+.tight {
+ margin:0;
+ padding:0;
+}
+
+.list-table td {
+ vertical-align:top;
+}
+
+p.comment {
+ border-top: 1px dotted #ccccce;
+ margin:0;
+ font-size:11px;
+ color: #444444;
+ padding:5px 0 5px 0;
+}
+
+.delete-icon {
+ vertical-align:middle;
+ padding-left:3px;
+}
+/* these need to go */
+table.form-as-table .errorlist {
+ display: block;
+ margin:0;
+ padding:0 0 0 5px;
+ text-align:left;
+ font-size:10px;
+ color:darkred;
+}
+table.form-as-table input {
+ display: inline;
+ margin-left: 4px;
+}
+table.form-as-table th {
+ vertical-align:bottom;
+ padding-bottom:4px;
+}
+.form-row-vertical {
+ margin-top: 8px;
+ display: block;
+}
+.form-row-vertical label {
+ margin-bottom:3px;
+ display:block;
+}
+/* above stuff needs to go */
+.text-align-right {
+ text-align: center;
+}
+ul.form-horizontal-rows {
+ list-style:none;
+ margin:0;
+}
+ul.form-horizontal-rows li {
+ position:relative;
+ height:40px;
+}
+ul.form-horizontal-rows label {
+ display:inline-block;
+}
+ul.form-horizontal-rows ul.errorlist {
+ list-style:none;
+ color:darkred;
+ font-size:10px;
+ line-height:10px;
+ position:absolute;
+ top:2px;
+ left:180px;
+ text-align:left;
+ margin:0;
+}
+ul.form-horizontal-rows ul.errorlist li {
+ height:10px;
+}
+ul.form-horizontal-rows label {
+ position:absolute;
+ left:0px;
+ bottom:6px;
+ margin:0px;
+ line-height: 12px;
+ font-size: 12px;
+}
+ul.form-horizontal-rows li input {
+ position:absolute;
+ bottom:0px;
+ left:180px;
+ margin:0px;
+}
+#emailpw-form li input {
+ left:170px;
+}
+#emailpw-form ul.errorlist {
+ left:170px;
+}
+#changepw-form li input {
+ left:150px;
+}
+#changepw-form ul.errorlist {
+ left:150px;
+}
+.narrow .summary {
+ float: left;
+}
+.narrow .summary .question-title {
+ font-weight: bold;
+ font-size: 120%;
+}
+.user-profile-tool-links {
+ padding-bottom:10px;
+ font-weight: bold;
+}
+.post-controls {
+ float:left;
+ font-size:11px;
+ line-height:12px;
+ min-width:200px;
+ margin-bottom:5px;
+}
+#question-controls .tags {
+ margin:0 0 3px 0;
+}
+.post-update-info-container {
+ float: right;
+ min-width:190px;
+}
+.post-update-info {
+ display:inline-block;
+ float:right;
+ width:190px;
+ margin-bottom:5px;
+}
+.post-update-info p {
+ font-size:11px;
+ line-height:15px;
+ margin:0 0 4px 0;
+ padding:0;
+}
+.post-update-info img {
+ float: left;
+ width: 32px;
+ margin: 4px 8px 0 0;
+}
+.comments-container {
+ clear:both;
+}
+.admin {
+ background-color:#fff380;/* nice yellow */
+ border: 1px solid darkred;
+ padding: 0 5px 0 5px;
+}
+.admin p {
+ margin-bottom: 3px;
+}
+.admin #action_status {
+ text-align:center;
+ font-weight:bold;
+}
+#tagSelector {
+ padding-bottom: 2px;
+}
+#hideIgnoredTagsControl {
+ margin: 5px 0 0 0;
+}
+#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/fbconnect/xd_receiver.html b/templates/fbconnect/xd_receiver.html
new file mode 100755
index 00000000..c67c57b7
--- /dev/null
+++ b/templates/fbconnect/xd_receiver.html
@@ -0,0 +1 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <body> <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.js" type="text/javascript"></script> </body> </html>
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 ede6cce5..60644d0e 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/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/question.html b/templates/question.html
index 9652e3f6..5fbbbfa7 100644
--- a/templates/question.html
+++ b/templates/question.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD:templates/question.html
{% extends "base.html" %}
<!-- question.html -->
{% load extra_tags %}
@@ -516,3 +517,515 @@
{% block endjs %}
{% endblock %}
<!-- end question.html -->
+=======
+{% extends "base.html" %}
+<!-- question.html -->
+{% load extra_tags %}
+{% load extra_filters %}
+{% load smart_if %}
+{% load humanize %}
+{% load i18n %}
+{% block title %}{% spaceless %}{{ question.get_question_title }}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <meta name="description" content="{{question.summary}}" />
+ <meta name="keywords" content="{{question.tagname_meta_generator}}" />
+ <link rel="canonical" href="{{settings.APP_URL}}{{question.get_absolute_url}}" />
+ {% if not question.closed %}
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/wmd/showdown.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/wmd/wmd.js" %}'></script>
+ <link rel="stylesheet" type="text/css" href="{% href "/content/js/wmd/wmd.css" %}" />
+ {% endif %}
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.post.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/jquery.validate.pack.js" %}'></script>
+
+ <script type="text/javascript">
+ // define reputation needs for comments
+ var repNeededForComments = 50;
+ $().ready(function(){
+ $("#nav_questions").attr('className',"on");
+ var answer_sort_tab = "{{ tab_id }}";
+ $("#" + answer_sort_tab).attr('className',"on");
+
+ Vote.init({{ question.id }}, '{{ question.title|slugify }}', '{{ question.author.id }}','{{ request.user.id }}');
+
+ {% if not question.closed and request.user.is_authenticated %}initEditor();{% endif %}
+
+ lanai.highlightSyntax();
+ $('#btLogin').bind('click', function(){window.location.href='{% url user_signin %}'; } )
+ });
+
+ function initEditor(){
+ $('#editor').TextAreaResizer();
+ //highlight code synctax when editor has new text
+ $("#editor").typeWatch({highlight: false, wait: 3000,
+ captureLength: 5, callback: lanai.highlightSyntax});
+
+ var display = true;
+ var txt = "[{% trans "hide preview" %}]";
+ $('#pre-collapse').text(txt);
+ $('#pre-collapse').bind('click', function(){
+ txt = display ? "[{% trans "show preview" %}]" : "[{% trans "hide preview" %}]";
+ display = !display;
+ $('#previewer').toggle();
+ $('#pre-collapse').text(txt);
+ });
+
+ setupFormValidation("#fmanswer", CPValidator.getQuestionFormRules(), CPValidator.getQuestionFormMessages());
+ }
+
+ </script>
+{% endblock %}
+
+{% block content %}
+<div class="headNormal">
+ <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
+</div>
+<div id="main-body" class="">
+ <div id="askform">
+ <table style="width:100%;" id="question-table" {% if question.deleted %}class="deleted"{%endif%}>
+ <tr>
+ <td style="width:30px;vertical-align:top">
+ <div class="vote-buttons">
+ {% if question_vote %}
+ <img id="question-img-upvote-{{ question.id }}" class="question-img-upvote"
+ {% if question_vote.is_upvote %}
+ src="{% href "/content/images/vote-arrow-up-on.png" %}"
+ {% else %}
+ src="{% href "/content/images/vote-arrow-up.png" %}"
+ {% endif %}
+ alt="{% trans "i like this post (click again to cancel)" %}"
+ title="{% trans "i like this post (click again to cancel)" %}" />
+ <div id="question-vote-number-{{ question.id }}" class="vote-number"
+ title="{% trans "current number of votes" %}">
+ {{ question.score }}
+ </div>
+ <img id="question-img-downvote-{{ question.id }}" class="question-img-downvote"
+ {% if question_vote.is_downvote %}
+ src="{% href "/content/images/vote-arrow-down-on.png" %}"
+ {% else %}
+ src="{% href "/content/images/vote-arrow-down.png" %}"
+ {% endif %}
+ alt="{% trans "i dont like this post (click again to cancel)" %}"
+ title="{% trans "i dont like this post (click again to cancel)" %}" />
+
+ {% else %}
+ <img id="question-img-upvote-{{ question.id }}" class="question-img-upvote"
+ alt="{% trans "i like this post (click again to cancel)" %}"
+ src="{% href "/content/images/vote-arrow-up.png" %}"
+ title="{% trans "i like this post (click again to cancel)" %}" />
+ <div id="question-vote-number-{{ question.id }}" class="vote-number"
+ title="{% trans "current number of votes" %}">
+ {{ question.score }}
+ </div>
+ <img id="question-img-downvote-{{ question.id }}" class="question-img-downvote"
+ src="{% href "/content/images/vote-arrow-down.png" %}"
+ alt="{% trans "i dont like this post (click again to cancel)" %}"
+ title="{% trans "i dont like this post (click again to cancel)" %}" />
+
+ {% endif %}
+ {% if favorited %}
+ <img class="question-img-favorite" src="{% href "/content/images/vote-favorite-on.png" %}"
+ alt="{% trans "mark this question as favorite (click again to cancel)" %}"
+ title="{% trans "mark this question as favorite (click again to cancel)" %}" />
+ <div id="favorite-number" class="favorite-number my-favorite-number">
+ {{ question.favourite_count }}
+ </div>
+ {% else %}
+ <img class="question-img-favorite" src="{% href "/content/images/vote-favorite-off.png" %}"
+ alt="{% trans "remove favorite mark from this question (click again to restore mark)" %}"
+ title="{% trans "remove favorite mark from this question (click again to restore mark)" %}" />
+ <div id="favorite-number" class="favorite-number">
+ {% ifnotequal question.favourite_count 0 %}{{ question.favourite_count }}{% endifnotequal %}
+ </div>
+
+ {% endif %}
+
+ </div>
+ </td>
+ <td>
+ <div id="item-right">
+ <div class="question-body">
+ {{ question.html|safe }}
+ </div>
+ <div id="question-controls" class="post-controls">
+ <div id="question-tags" class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url forum.views.tag tag|urlencode %}" class="post-tag"
+ title="{% blocktrans with tag as tagname %}see questions tagged '{{ tagname }}'{% endblocktrans %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ {% joinitems using '<span class="action-link-separator">|</span>' %}
+ {% if request.user|can_edit_post:question %}
+ <span class="action-link"><a href="{% url edit_question question.id %}">{% trans 'edit' %}</a></span>
+ {% endif %}
+ {% separator %}
+ {% if question.closed %}
+ {% if request.user|can_reopen_question:question %}
+ <span class="action-link"><a href="{% url reopen question.id %}">{% trans "reopen" %}</a></span>
+ {% endif %}
+ {% else %}
+ {% if request.user|can_close_question:question %}
+ <span class="action-link"><a href="{% url close question.id %}">{% trans "close" %}</a></span>
+ {% endif %}
+ {% endif %}
+ {% separator %}
+ {% if request.user|can_flag_offensive %}
+ <span id="question-offensive-flag-{{ question.id }}" class="offensive-flag"
+ title="{% trans "report as offensive (i.e containing spam, advertising, malicious text, etc.)" %}">
+ <a>{% trans "flag offensive" %}</a>
+ {% if request.user|can_view_offensive_flags and question.offensive_flag_count %}
+ <span class="darkred">({{ question.offensive_flag_count }})</span>
+ {% endif %}
+ </span>
+ {% endif %}
+ {% separator %}
+ {% if request.user|can_delete_post:question %}
+ <span class="action-link"><a id="question-delete-link-{{question.id}}">{% trans "delete" %}</a></span>
+ {% endif %}
+ {% endjoinitems %}
+ </div>
+ <div class="post-update-info-container">
+ {% post_contributor_info question "original_author" %}
+ {% post_contributor_info question "last_updater" %}
+ </div>
+ <div class="comments-container" id="comments-container-question-{{question.id}}">
+ {% for comment in question.get_comments|slice:":5" %}
+ <p class="comment" id="comment-{{comment.id}}">
+ {{comment.comment}}
+ - <a class="comment-user" href="{{comment.user.get_profile_url}}">{{comment.user}}</a>
+ {% spaceless %}
+ <span class="comment-age">({% diff_date comment.added_at %})</span>
+ {% if request.user|can_delete_comment:comment %}
+ <img class="delete-icon"
+ src="{% href "/content/images/close-small.png" %}"
+ title="{% trans "delete this comment" %}"/>
+ {% endif %}
+ {% endspaceless %}
+ </p>
+ {% endfor %}
+ </div>
+ <div class="post-comments" style="margin-bottom:20px">
+ <input id="can-post-comments-question-{{question.id}}" type="hidden" value="{{ request.user|can_add_comments:question }}"/>
+ {% if request.user|can_add_comments:question or question.comment_count > 5 %}
+ <a id="comments-link-question-{{question.id}}" class="comments-link">
+ {% if request.user|can_add_comments:question %}
+ {% trans "add comment" %}
+ {% endif %}
+ {% if question.comment_count > 5 %}
+ {% if request.user|can_add_comments:question %}/
+ {% blocktrans count question.get_comments|slice:"5:"|length as counter %}
+ see <strong>one</strong> more
+ {% plural %}
+ see <strong>{{counter}}</strong> more
+ {% endblocktrans %}
+ {% else %}
+ {% blocktrans count question.get_comments|slice:"5:"|length as counter %}
+ see <strong>one</strong> more comment
+ {% plural %}
+ see <strong>{{counter}}</strong> more comments
+ {% endblocktrans %}
+ {% endif %}
+ {% endif %}</a>
+ {% endif %}
+ </div>
+ </div>
+
+ </td>
+ </tr>
+ </table>
+ {% if question.closed %}
+ <div class="question-status" style="margin-bottom:15px">
+ <h3>{% blocktrans with question.get_close_reason_display as close_reason %}The question has been closed for the following reason "{{ close_reason }}" by{% endblocktrans %}
+ <a href="{{ question.closed_by.get_profile_url }}">{{ question.closed_by.username }}</a>
+ {% blocktrans with question.closed_at as closed_at %}close date {{closed_at}}{% endblocktrans %}</h3>
+ </div>
+ {% endif %}
+ {% if answers %}
+ <hr/>
+ <div class="tabBar">
+ <a name="sort-top"></a>
+ <div class="headQuestions">
+ {% blocktrans count answers|length as counter %}
+ One Answer:
+ {% plural %}
+ {{counter}} Answers:
+ {% endblocktrans %}
+ </div>
+ <div class="tabsA">
+ <a id="oldest" href="{% url question question.id %}?sort=oldest#sort-top"
+ title="{% trans "oldest answers will be shown first" %}">{% trans "oldest answers" %}</a>
+ <a id="latest" href="{% url question question.id %}?sort=latest#sort-top"
+ title="{% trans "newest answers will be shown first" %}">{% trans "newest answers" %}</a>
+ <a id="votes" href="{% url question question.id %}?sort=votes#sort-top"
+ title="{% trans "most voted answers will be shown first" %}">{% trans "popular answers" %}</a>
+ </div>
+ </div>
+ {% cnprog_paginator context %}
+
+ {% for answer in answers %}
+ <a name="{{ answer.id }}"></a>
+ <div id="answer-container-{{ answer.id }}" class="answer {% if answer.accepted %}accepted-answer{% endif %} {% ifequal answer.author_id question.author_id %} answered-by-owner{% endifequal %} {% if answer.deleted %}deleted{% endif %}">
+ <table style="width:100%;">
+ <tr>
+ <td style="width:30px;vertical-align:top">
+ <div class="vote-buttons">
+ <img id="answer-img-upvote-{{ answer.id }}" class="answer-img-upvote"
+ src="{% blockresource %}/content/images/vote-arrow-up{% get_user_vote_image user_answer_votes answer.id 1 %}.png{% endblockresource %}"
+ alt="{% trans "i like this answer (click again to cancel)" %}"
+ title="{% trans "i like this answer (click again to cancel)" %}"/>
+ <div id="answer-vote-number-{{ answer.id }}" class="vote-number" title="{% trans "current number of votes" %}">
+ {{ answer.score }}
+ </div>
+ <img id="answer-img-downvote-{{ answer.id }}" class="answer-img-downvote"
+ src="{% blockresource %}/content/images/vote-arrow-down{% get_user_vote_image user_answer_votes answer.id -1 %}.png{% endblockresource %}"
+ alt="{% trans "i dont like this answer (click again to cancel)" %}"
+ title="{% trans "i dont like this answer (click again to cancel)" %}" />
+
+ {% ifequal request.user question.author %}
+ <img id="answer-img-accept-{{ answer.id }}" class="answer-img-accept"
+ src="{% blockresource %}/content/images/vote-accepted{% if answer.accepted %}-on{% endif %}.png{% endblockresource %}"
+ alt="{% trans "mark this answer as favorite (click again to undo)" %}"
+ title="{% trans "mark this answer as favorite (click again to undo)" %}" />
+ {% else %}
+ {% if answer.accepted %}
+ <img id="answer-img-accept-{{ answer.id }}" class="answer-img-accept"
+ src="{% blockresource %}/content/images/vote-accepted{% if answer.accepted %}-on{% endif %}.png{% endblockresource %}"
+ alt="{% trans "the author of the question has selected this answer as correct" %}"
+ title="{% trans "the author of the question has selected this answer as correct" %}" />
+ {% endif %}
+ {% endifequal %}
+ </div>
+ </td>
+ <td>
+ <div class="item-right">
+ <div class="answer-body">
+ {{ answer.html|safe }}
+ </div>
+ <div class="answer-controls post-controls">
+ {% joinitems using '<span class="action-link-separator">|</span>' %}
+ <span class="linksopt">
+ <a href="#{{ answer.id }}" title="{% trans "answer permanent link" %}">
+ {% trans "permanent link" %}
+ </a>
+ </span>
+ {% separator %}
+ {% if request.user|can_edit_post:answer %}
+ <span class="action-link"><a href="{% url edit_answer answer.id %}">{% trans 'edit' %}</a></span>
+ {% endif %}
+ {% separator %}
+ {% if request.user|can_flag_offensive %}
+ <span id="answer-offensive-flag-{{ answer.id }}" class="offensive-flag"
+ title="{% trans "report as offensive (i.e containing spam, advertising, malicious text, etc.)" %}">
+ <a>{% trans "flag offensive" %}</a>
+ {% if request.user|can_view_offensive_flags and answer.offensive_flag_count %}
+ <span class="darkred">({{ answer.offensive_flag_count }})</span>
+ {% endif %}
+ </span>
+ {% endif %}
+ {% separator %}
+ {% if request.user|can_delete_post:answer %}
+ {% spaceless %}
+ <span class="action-link">
+ <a id="answer-delete-link-{{answer.id}}">
+ {% if answer.deleted %}{% trans "undelete" %}{% else %}{% trans "delete" %}{% endif %}</a>
+ </span>
+ {% endspaceless %}
+ {% endif %}
+ {% endjoinitems %}
+ </div>
+ <div class="post-update-info-container">
+ {% post_contributor_info answer "original_author" %}
+ {% post_contributor_info answer "last_updater" %}
+ </div>
+ <div class="comments-container" id="comments-container-answer-{{answer.id}}">
+ {% for comment in answer.get_comments|slice:":5" %}
+ <p id="comment-{{comment.id}}" class="comment">
+ {{comment.comment}}
+ - <a class="comment-user" href="{{comment.user.get_profile_url}}">{{comment.user}}</a>
+ {% spaceless %}
+ <span class="comment-age">({% diff_date comment.added_at %})</span>
+ {% if request.user|can_delete_comment:comment %}
+ <img class="delete-icon"
+ src="{% href "/content/images/close-small.png" %}"
+ title="{% trans "delete this comment" %}"/>
+ {% endif %}
+ {% endspaceless %}
+ </p>
+ {% endfor %}
+ </div>
+ <div class="post-comments" style="margin-bottom:20px">
+ <input id="can-post-comments-answer-{{answer.id}}" type="hidden" value="{{ request.user|can_add_comments:answer}}"/>
+ {% if request.user|can_add_comments:answer or answer.comment_count > 5 %}
+ <a id="comments-link-answer-{{answer.id}}" class="comments-link">
+ {% if request.user|can_add_comments:answer %}
+ {% trans "add comment" %}
+ {% endif %}
+ {% if answer.comment_count > 5 %}
+ {% if request.user|can_add_comments:answer %}/
+ {% blocktrans count answer.get_comments|slice:"5:"|length as counter %}
+ see <strong>one</strong> more
+ {% plural %}
+ see <strong>{{counter}}</strong> more
+ {% endblocktrans %}
+ {% else %}
+ {% blocktrans count answer.get_comments|slice:"5:"|length as counter %}
+ see <strong>one</strong> more comment
+ {% plural %}
+ see <strong>{{counter}}</strong> more comments
+ {% endblocktrans %}
+ {% endif %}
+ {% endif %}</a>
+ {% endif %}
+ </div>
+ </div>
+
+ </td>
+ </tr>
+ </table>
+ </div>
+ {% endfor %}
+ <div class="paginator-container-left">
+ {% cnprog_paginator context %}
+ </div>
+ {% endif %}
+ <form id="fmanswer" action="{% url answer question.id %}" method="post">
+ {% if request.user.is_authenticated %}
+ <p style="padding-left:3px">
+ {{ answer.email_notify }}
+ <label for="question-subscribe-updates">
+ {% ifequal request.user.get_q_sel_email_feed_frequency 'n' %}
+ {% trans "Notify me once a day when there are any new answers" %}
+ {% else %}
+ {% ifequal request.user.get_q_sel_email_feed_frequency 'd' %}
+ {% trans "Notify me once a day when there are any new answers" %}
+ {% else %}
+ {% ifequal request.user.get_q_sel_email_feed_frequency 'w' %}
+ {% trans "Notify me weekly when there are any new answers" %}
+ {% endifequal %}
+ {% endifequal %}
+ {% endifequal %}
+ </label>
+ {% blocktrans with request.user.get_profile_url as profile_url %}
+ You can always adjust frequency of email updates from your {{profile_url}}
+ {% endblocktrans %}
+ </p>
+ {% else %}
+ <p style="padding-left:3px">
+ <input class="nomargin" type="checkbox" disabled="disabled" />
+ <label>{% trans "once you sign in you will be able to subscribe for any updates here" %}</label>
+ </p>
+ {% endif %}
+ <div style="clear:both">
+ </div>
+
+ {% if not question.closed %}
+ <div style="padding:10px 0 0 0;">
+ {% spaceless %}
+ <div class="headNormal">
+ {% if answers %}
+ {% trans "Your answer" %}
+ {% else %}
+ {% trans "Be the first one to answer this question!" %}
+ {% endif %}
+ </div>
+ {% endspaceless %}
+ </div>
+ {% if not request.user.is_authenticated %}
+ <div class="message">{% trans "you can answer anonymously and then login" %}</div>
+ {% else %}
+ <p class="message">
+ {% ifequal request.user question.author %}
+ {% trans "answer your own question only to give an answer" %}
+ {% else %}
+ {% trans "please only give an answer, no discussions" %}
+ {% endifequal %}
+ </p>
+ {% endif %}
+
+ <div id="description" class="" >
+ <div id="wmd-button-bar" class="wmd-panel"></div>
+ {{ answer.text }}
+ <div class="preview-toggle">
+ <table width="100%">
+ <tr>
+ <td>
+ <span id="pre-collapse"
+ title="{% trans "Toggle the real time Markdown editor preview" %}">
+ {% trans "toggle preview" %}
+ </span>
+ </td>
+ {% if settings.WIKI_ON %}
+ <td style="text-align:right;">
+ {{ answer.wiki }}
+ <span style="font-weight:normal;cursor:help"
+ title="{{answer.wiki.help_text}}">
+ {{ answer.wiki.label_tag }}
+ </span>
+ </td>
+ {% endif %}
+ </tr>
+
+ </table>
+ </div>
+ <div id="previewer" class="wmd-preview"></div>
+ {{ answer.text.errors }}
+ </div>
+ <p><span class="form-error"></span></p>
+ <input type="submit"
+ {% if user.is_anonymous %}
+ value="{% trans "Login/Signup to Post Your Answer" %}"
+ {% else %}
+ {% if user == question.author %}
+ value="{% trans "Answer Your Own Question" %}"
+ {% else %}
+ value="{% trans "Answer the question" %}"
+ {% endif %}
+ {% endif %}
+ class="submit" style="float:left"/>
+ {% endif %}
+ </form>
+ </div>
+</div>
+{% endblock %}
+
+{% block sidebar %}
+<div class="boxC">
+ <p>
+ {% trans "Question tags" %}:
+ </p>
+ <p class="tags" >
+ {% for tag in tags %}
+ <a href="{% url forum.views.tag tag.name|urlencode %}"
+ title="{% trans "see questions tagged"%}'{{tag.name}}'{% trans "using tags" %}"
+ rel="tag">{{ tag.name }}</a> <span class="tag-number">&#215;{{ tag.used_count|intcomma }}</span><br/>
+ {% endfor %}
+ </p>
+ <p>
+ {% trans "question asked" %}: <strong title="{{ question.added_at }}">{% diff_date question.added_at %}</strong>
+ </p>
+ <p>
+ {% trans "question was seen" %}: <strong>{{ question.view_count|intcomma }} {% trans "times" %}</strong>
+ </p>
+ <p>
+ {% trans "last updated" %}: <strong title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</strong>
+ </p>
+</div>
+
+<div class="boxC">
+ <h3 class="subtitle">{% trans "Related questions" %}</h3>
+ <div class="questions-related">
+ {% for question in similar_questions %}
+ <p>
+ <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
+ </p>
+ {% endfor %}
+ </div>
+</div>
+
+{% endblock %}
+
+{% block endjs %}
+{% endblock %}
+<!-- end question.html -->
+>>>>>>> evgenyfadeev/master:templates/question.html
diff --git a/templates/questions.html b/templates/questions.html
index f74256cf..aa9a38e3 100644
--- a/templates/questions.html
+++ b/templates/questions.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD:templates/questions.html
{% extends "base.html" %}
<!-- questions.html -->
{% load extra_tags %}
@@ -270,3 +271,240 @@
{% endblock %}
<!-- end questions.html -->
+=======
+{% extends "base.html" %}
+<!-- questions.html -->
+{% load extra_tags %}
+{% load i18n %}
+{% load humanize %}
+{% load extra_filters %}
+{% load smart_if %}
+{% block title %}{% spaceless %}{% trans "Questions" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ var tags = {{ tags_autocomplete|safe }};
+ $().ready(function(){
+ var tab_id = "{{ tab_id }}";
+ $("#"+tab_id).attr('className',"on");
+ var on_tab = {% if is_unanswered %}'#nav_unanswered'{% else %}'#nav_questions'{% endif %};
+ $(on_tab).attr('className','on');
+ Hilite.exact = false;
+ Hilite.elementid = "listA";
+ Hilite.debug_referrer = location.href;
+ });
+ </script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.editor.js" %}'></script>
+ <script type='text/javascript' src='{% href "/content/js/com.cnprog.tag_selector.js" %}'></script>
+{% endblock %}
+{% block content %}
+<div class="tabBar">
+ <div class="headQuestions">
+ {% if searchtag %}
+ {% trans "Found by tags" %}
+ {% else %}
+ {% if searchtitle %}
+ {% if settings.USE_SPHINX_SEARCH %}
+ {% trans "Search results" %}
+ {% else %}
+ {% trans "Found by title" %}
+ {% endif %}
+ {% else %}
+ {% if is_unanswered %}
+ {% trans "Unanswered questions" %}
+ {% else %}
+ {% trans "All questions" %}
+ {% endif %}
+ {% endif %}
+ {% endif %}
+ </div>
+ <div class="tabsA">
+ <a id="latest" href="{% if is_search %}{{ search_uri }}&{% else %}?{% endif %}sort=latest" class="off" title="{% trans "most recently asked questions" %}">{% trans "newest" %}</a>
+ <a id="active" href="{% if is_search %}{{ search_uri }}&{% else %}?{% endif %}sort=active" class="off" title="{% trans "most recently updated questions" %}">{% trans "active" %}</a>
+ <a id="hottest" href="{% if is_search %}{{ search_uri }}&{% else %}?{% endif %}sort=hottest" class="off" title="{% trans "hottest questions" %}">{% trans "hottest" %}</a>
+ <a id="mostvoted" href="{% if is_search %}{{ search_uri }}&{% else %}?{% endif %}sort=mostvoted" class="off" title="{% trans "most voted questions" %}">{% trans "most voted" %}</a>
+ </div>
+</div>
+<div id="listA">
+ {% for question in questions.object_list %}
+ <div class="qstA"
+ {% if request.user.is_authenticated %}
+ {% if question.interesting_score > 0 %}
+ style="background:#ffff99;"
+ {% else %}
+ {% if not request.user.hide_ignored_questions %}
+ {% if question.ignored_score > 0 %}
+ style="background:#f3f3f3;"
+ {% endif %}
+ {% endif %}
+ {% endif %}
+ {% endif %}
+ >
+ <h2>
+ <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
+ </h2>
+ <div class="stat">
+ <table>
+ <tr>
+ <td><span class="num">{{ question.answer_count|intcomma }}</span> </td>
+ <td><span class="num">{{ question.score|intcomma }}</span> </td>
+ <td><span class="num">{{ question.view_count|cnprog_intword|safe }}</span> </td>
+ </tr>
+ <tr>
+ <td><span class="unit">{% trans "answers" %}</span></td>
+ <td><span class="unit">{% trans "votes" %}</span></td>
+ <td><span class="unit">{% trans "views" %}</span></td>
+ </tr>
+ </table>
+ </div>
+
+ <div class="summary">
+ {{ question.summary }}...
+ </div>
+
+ {% ifequal tab_id 'active'%}
+ {% if question.wiki and settings.WIKI_ON %}
+ <span class="from wiki">{% trans "community wiki" %}</span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% else %}
+ <div class="from">
+ {% comment %}{% gravatar question.last_activity_by 24 %}{% endcomment %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ </div>
+ {% endif %}
+ {% else %}
+ {% if question.wiki and settings.WIKI_ON %}
+ <span class="from wiki">{% trans "community wiki" %}</span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% else %}
+ <div class="from">
+ {% comment %}{% gravatar question.author 24 %}{% endcomment %}
+ {% if question.last_activity_at != question.added_at %}
+ {% if question.author.id != question.last_activity_by.id %}
+ {% trans "Posted:" %}
+ <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
+ <span class="score">{% get_score_badge question.author %} </span>
+ / {% trans "Updated:" %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ {% else %}
+ {% trans "Updated:" %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ {% endif %}
+ {% else %}
+ {% trans "Posted:" %}
+ <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
+ <span class="score">{% get_score_badge question.author %} </span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% endif %}
+ </div>
+ {% endif %}
+ {% endifequal %}
+
+ <div class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url forum.views.tag tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+ {% if searchtitle %}
+ {% if questions_count == 0 %}
+ <p class="evenMore" style="padding-top:30px;text-align:center;">
+ {% trans "Did not find anything?" %}
+ {% else %}
+ <p class="evenMore" style="padding-left:9px">
+ {% trans "Did not find what you were looking for?" %}
+ {% endif %}
+ <a href="{% url ask %}">{% trans "Please, post your question!" %}</a>
+ </p>
+ {% endif %}
+</div>
+{% endblock %}
+
+{% block tail %}
+ <div class="pager">{% cnprog_paginator context %}</div>
+ <div class="pagesize">{% cnprog_pagesize context %}</div>
+{% endblock %}
+
+{% block sidebar %}
+<div class="boxC">
+ {% if searchtag %}
+ {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num and searchtag as tagname %}
+ have total {{q_num}} questions tagged {{tagname}}
+ {% plural %}
+ have total {{q_num}} questions tagged {{tagname}}
+ {% endblocktrans %}
+ {% else %}
+ {% if searchtitle %}
+ {% if settings.USE_SPHINX_SEARCH %}
+ {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num %}
+ have total {{q_num}} questions containing {{searchtitle}} in full text
+ {% plural %}
+ have total {{q_num}} questions containing {{searchtitle}} in full text
+ {% endblocktrans %}
+ {% else %}
+ {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num %}
+ have total {{q_num}} questions containing {{searchtitle}}
+ {% plural %}
+ have total {{q_num}} questions containing {{searchtitle}}
+ {% endblocktrans %}
+ {% endif %}
+ {% else %}
+ {% if is_unanswered %}
+ {% blocktrans count questions as cnt with questions_count|intcomma as q_num %}
+ have total {{q_num}} unanswered questions
+ {% plural %}
+ have total {{q_num}} unanswered questions
+ {% endblocktrans %}
+ {% else %}
+ {% blocktrans count questions as cnt with questions_count|intcomma as q_num %}
+ have total {{q_num}} questions
+ {% plural %}
+ have total {{q_num}} questions
+ {% endblocktrans %}
+ {% endif %}
+ {% endif %}
+ {% endif %}
+ <p class="nomargin">
+ {% ifequal tab_id "latest" %}
+ {% trans "latest questions info" %}
+ {% endifequal %}
+
+ {% ifequal tab_id "active" %}
+ {% trans "Questions are sorted by the <strong>time of last update</strong>." %}
+ {% trans "Most recently answered ones are shown first." %}
+ {% endifequal %}
+
+ {% ifequal tab_id "hottest" %}
+ {% trans "Questions sorted by <strong>number of responses</strong>." %}
+ {% trans "Most answered questions are shown first." %}
+ {% endifequal %}
+
+ {% ifequal tab_id "mostvoted" %}
+ {% trans "Questions are sorted by the <strong>number of votes</strong>." %}
+ {% trans "Most voted questions are shown first." %}
+ {% endifequal %}
+ </p>
+</div>
+{% if request.user.is_authenticated %}
+{% include "tag_selector.html" %}
+{% endif %}
+<div class="boxC">
+ <h3 class="subtitle">{% trans "Related tags" %}</h3>
+ <div class="tags">
+ {% for tag in tags %}
+ <a rel="tag" title="{% blocktrans with tag.name as tag_name %}see questions tagged '{{ tag_name }}'{% endblocktrans %}" href="{% url forum.views.tag tag.name|urlencode %}">{{ tag.name }}</a>
+ <span class="tag-number">&#215; {{ tag.used_count|intcomma }}</span>
+ <br />
+ {% endfor %}
+ </div>
+</div>
+
+{% endblock %}
+<!-- end questions.html -->
+>>>>>>> evgenyfadeev/master:templates/questions.html
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/templates/user_edit.html b/templates/user_edit.html
index 5886c071..bc5056f9 100644
--- a/templates/user_edit.html
+++ b/templates/user_edit.html
@@ -1,95 +1,95 @@
-{% extends "base_content.html" %}
-<!-- user_edit.html -->
-{% load extra_tags %}
-{% load humanize %}
-{% load i18n %}
-{% block title %}{% spaceless %}{% trans "Edit user profile" %}{% endspaceless %}{% endblock %}
-{% block forejs %}
- <script type="text/javascript">
- $().ready(function(){
- $("#nav_profile").attr('className',"on");
- $("#cancel").bind('click', function(){history.go(-1);})
- });
- </script>
- {% block userjs %}
- {% endblock %}
-{% endblock %}
-{% block content %}
-<div id="main-bar" class="headNormal">
- {{ request.user.username }} - {% trans "edit profile" %}
-</div>
-<div id="main-body" style="width:100%;padding-top:10px">
- <form name="" action="{% url edit_user request.user.id %}" method="post">
- <div id="left" style="float:left;width:180px">
- {% if request.user.email %}
- {% gravatar request.user 128 %}
- {% else %}
- <img src="{% href "/content/images/nophoto.png" %}">
- {% endif %}
- <div style="padding:20px 0 0 20px;font-weight:bold;font-size:150%">
- <a href="http://www.gravatar.com/" target="_blank"
- title="gravatar {% trans "image associated with your email address" %}">{% blocktrans %}avatar, see {{gravatar_faq_url}}{% endblocktrans %}</a>
- </div>
- </div>
-
- <div id="askform" style="float:right;width:750px;text-align:left;">
- <h2>{% trans "Registered user" %}</h2>
- <table class="user-details">
- <tr>
- <th width="100px"></th>
- <th></th>
- </tr>
- <tr style="height:35px">
- <td>{{ form.username.label_tag }}:</td>
- <td>{{ form.username }} <span class="form-error"></span> {{ form.username.errors }} </td>
- </tr>
-
- <tr style="height:35px">
- <td>{{ form.email.label_tag }}:</td>
- <td>{{ form.email }} <span class="form-error"></span> {{ form.email.errors }} </td>
- </tr>
- <tr style="height:35px">
- <td></td>
- <td class="title-desc">{{ form.email.help_text }}</td>
- </tr>
- <tr style="height:35px">
- <td>{{ form.realname.label_tag }}:</td>
- <td>{{ form.realname }} <span class="form-error"></span> {{ form.realname.errors }} </td>
- </tr>
- <tr style="height:35px">
- <td>{{ form.website.label_tag }}:</td>
- <td>{{ form.website }} <span class="form-error"></span> {{ form.website.errors }} </td>
- </tr>
- <tr style="height:35px">
- <td>{{ form.city.label_tag }}:</td>
- <td>{{ form.city }} <span class="form-error"></span> {{ form.city.errors }} </td>
- </tr>
- <tr style="height:35px">
- <td>{{ form.birthday.label_tag }}:</td>
- <td>{{ form.birthday }} <span class="form-error"></span> {{ form.birthday.errors }} </td>
- </tr>
- <tr style="height:35px">
- <td></td>
- <td class="title-desc">{{ form.birthday.help_text }}</td>
- </tr>
- <tr style="height:10px">
- <td colspan="2">
- </td>
- </tr>
- <tr>
- <td style="vertical-align:top">{{ form.about.label_tag }}:</td>
- <td>{{ form.about }} <span class="form-error"></span> {{ form.about.errors }} </td>
- </tr>
-
- </table>
- <div style="margin:30px 0 60px 0">
- <input type="submit" value="{% trans "Update" %}" class="submit" >
- <input id="cancel" type="button" value="{% trans "Cancel" %}" class="submit" >
-
- </div>
- </div>
- </form>
-
-</div>
-{% endblock %}
-<!-- end user_edit.html -->
+{% extends "base_content.html" %}
+<!-- user_edit.html -->
+{% load extra_tags %}
+{% load humanize %}
+{% load i18n %}
+{% block title %}{% spaceless %}{% trans "Edit user profile" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $("#nav_profile").attr('className',"on");
+ $("#cancel").bind('click', function(){history.go(-1);})
+ });
+ </script>
+ {% block userjs %}
+ {% endblock %}
+{% endblock %}
+{% block content %}
+<div id="main-bar" class="headNormal">
+ {{ request.user.username }} - {% trans "edit profile" %}
+</div>
+<div id="main-body" style="width:100%;padding-top:10px">
+ <form name="" action="{% url edit_user request.user.id %}" method="post">
+ <div id="left" style="float:left;width:180px">
+ {% if request.user.email %}
+ {% gravatar request.user 128 %}
+ {% else %}
+ <img src="{% href "/content/images/nophoto.png" %}">
+ {% endif %}
+ <div style="padding:20px 0 0 20px;font-weight:bold;font-size:150%">
+ <a href="http://www.gravatar.com/" target="_blank"
+ title="gravatar {% trans "image associated with your email address" %}">{% blocktrans %}avatar, see {{gravatar_faq_url}}{% endblocktrans %}</a>
+ </div>
+ </div>
+
+ <div id="askform" style="float:right;width:750px;text-align:left;">
+ <h2>{% trans "Registered user" %}</h2>
+ <table class="user-details">
+ <tr>
+ <th width="100px"></th>
+ <th></th>
+ </tr>
+ <tr style="height:35px">
+ <td>{% trans "Screen Name" %}:</td>
+ <td>{{ request.user.username }} <span class="form-error"></span> {{ form.username.errors }} </td>
+ </tr>
+
+ <tr style="height:35px">
+ <td>{{ form.email.label_tag }}:</td>
+ <td>{{ form.email }} <span class="form-error"></span> {{ form.email.errors }} </td>
+ </tr>
+ <tr style="height:35px">
+ <td></td>
+ <td class="title-desc">{{ form.email.help_text }}</td>
+ </tr>
+ <tr style="height:35px">
+ <td>{{ form.realname.label_tag }}:</td>
+ <td>{{ form.realname }} <span class="form-error"></span> {{ form.realname.errors }} </td>
+ </tr>
+ <tr style="height:35px">
+ <td>{{ form.website.label_tag }}:</td>
+ <td>{{ form.website }} <span class="form-error"></span> {{ form.website.errors }} </td>
+ </tr>
+ <tr style="height:35px">
+ <td>{{ form.city.label_tag }}:</td>
+ <td>{{ form.city }} <span class="form-error"></span> {{ form.city.errors }} </td>
+ </tr>
+ <tr style="height:35px">
+ <td>{{ form.birthday.label_tag }}:</td>
+ <td>{{ form.birthday }} <span class="form-error"></span> {{ form.birthday.errors }} </td>
+ </tr>
+ <tr style="height:35px">
+ <td></td>
+ <td class="title-desc">{{ form.birthday.help_text }}</td>
+ </tr>
+ <tr style="height:10px">
+ <td colspan="2">
+ </td>
+ </tr>
+ <tr>
+ <td style="vertical-align:top">{{ form.about.label_tag }}:</td>
+ <td>{{ form.about }} <span class="form-error"></span> {{ form.about.errors }} </td>
+ </tr>
+
+ </table>
+ <div style="margin:30px 0 60px 0">
+ <input type="submit" value="{% trans "Update" %}" class="submit" >
+ <input id="cancel" type="button" value="{% trans "Cancel" %}" class="submit" >
+
+ </div>
+ </div>
+ </form>
+
+</div>
+{% endblock %}
+<!-- end user_edit.html -->
diff --git a/urls.py b/urls.py
index 0608aaa8..3ebc19e8 100644
--- a/urls.py
+++ b/urls.py
@@ -1,7 +1,11 @@
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()
urlpatterns = patterns('',
(r'^%s' % settings.FORUM_SCRIPT_ALIAS, include('forum.urls')),
+ (r'^admin/', include(admin.site.urls)),
)
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']
+