diff options
author | Adolfo Fitoria <fitoria@fitoria-laptop.(none)> | 2010-02-09 14:56:49 -0600 |
---|---|---|
committer | Adolfo Fitoria <fitoria@fitoria-laptop.(none)> | 2010-02-09 14:56:49 -0600 |
commit | 81e2244eca04c02de0d7c32f0063f64f2ca3b1c4 (patch) | |
tree | 1014127ab7b69d121aa2fe4ed80de5a43e7b078a | |
parent | 9d1fb9890b97beb55461ca34f9757bc685461130 (diff) | |
parent | 3e7c42f81352ad73a1395ed0601f2d4fdf9b5359 (diff) | |
download | askbot-81e2244eca04c02de0d7c32f0063f64f2ca3b1c4.tar.gz askbot-81e2244eca04c02de0d7c32f0063f64f2ca3b1c4.tar.bz2 askbot-81e2244eca04c02de0d7c32f0063f64f2ca3b1c4.zip |
Merge branch 'master' of git://github.com/evgenyfadeev/CNPROG into evgenyfadeev/master
Conflicts:
forum/management/commands/send_email_alerts.py
locale/en/LC_MESSAGES/django.mo
settings.py
templates/about.html
templates/authopenid/complete.html
templates/authopenid/external_legacy_login_info.html
templates/content/js/com.cnprog.post.js
templates/content/js/com.cnprog.utils.js
templates/content/style/style.css
templates/question.html
templates/questions.html
89 files changed, 3463 insertions, 2869 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. @@ -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 @@ -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. + @@ -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() @@ -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 edb6808e..969af0b9 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,16 @@ 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('/'): - 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>™', 'google':'Google™', @@ -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 ad1d5cbd..e4b929e9 100644 --- a/forum/feed.py +++ b/forum/feed.py @@ -13,16 +13,16 @@ from django.contrib.syndication.feeds import Feed, FeedDoesNotExist from django.utils.translation import ugettext as _ from models import Question -import settings +from django.conf import settings class RssLastestQuestionsFeed(Feed): title = settings.APP_TITLE + _(' - ')+ _('latest questions') - link = settings.APP_URL + '/' + _('question/') + link = settings.APP_URL #+ '/' + _('question/') description = settings.APP_DESCRIPTION #ttl = 10 copyright = settings.APP_COPYRIGHT def item_link(self, item): - return self.link + '%s/' % item.id + return self.link + item.get_absolute_url() def item_author_name(self, item): return item.author.username diff --git a/forum/forms.py b/forum/forms.py index ad2c5bac..308f853b 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) @@ -194,8 +199,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})) @@ -204,7 +210,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 @@ -298,14 +307,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: @@ -315,3 +334,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 06fae761..9079e9a6 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 @@ -122,7 +123,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)') @@ -200,7 +201,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() @@ -222,7 +223,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] @@ -230,11 +231,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] @@ -242,7 +243,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) @@ -251,7 +252,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 f1876588..fe2121b5 100644 --- a/forum/models.py +++ b/forum/models.py @@ -22,7 +22,7 @@ import settings ======= 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: @@ -415,7 +415,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): @@ -442,7 +442,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) @@ -510,8 +510,11 @@ class Answer(models.Model): >>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py 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 @@ -729,13 +732,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)) @@ -871,7 +874,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 8ed79d3c..29fdd831 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: @@ -281,7 +282,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 296745f9..ade6aaa7 100644 --- a/forum/views.py +++ b/forum/views.py @@ -33,7 +33,7 @@ from forum.auth import * from forum.const import * from forum.user import * from forum import auth -from django_authopenid.util import get_next_url +from utils.forms import get_next_url # used in index page INDEX_PAGE_SIZE = 20 @@ -202,7 +202,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' ), ]), @@ -220,7 +220,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' ) ]), @@ -436,6 +436,21 @@ def question(request, id): logging.debug('view_id=' + str(view_id)) question = get_object_or_404(Question, id=id) + try: + pattern = r'/%s%s%d/([\w-]+)' % (settings.FORUM_SCRIPT_ALIAS,_('question/'), question.id) + path_re = re.compile(pattern) + logging.debug(pattern) + logging.debug(request.path) + m = path_re.match(request.path) + if m: + slug = m.group(1) + logging.debug('have slug %s' % slug) + assert(slug == slugify(question.title)) + else: + logging.debug('no match!') + except: + return HttpResponseRedirect(question.get_absolute_url()) + if question.deleted and not can_view_deleted_post(request.user, question): raise Http404 answer_form = AnswerForm(question,request.user) @@ -1236,7 +1251,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']) @@ -1273,7 +1288,7 @@ def user_stats(request, user_id, user_view): }, 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'], + 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', @@ -1309,7 +1324,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] @@ -1431,7 +1446,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( @@ -1456,8 +1471,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( @@ -1485,7 +1500,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( @@ -1515,7 +1530,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( @@ -1542,7 +1557,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], @@ -1575,7 +1590,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'] @@ -1604,7 +1619,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'] @@ -1680,7 +1695,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'] @@ -1711,7 +1726,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'] @@ -1741,7 +1756,7 @@ def user_responses(request, user_id, user_view): '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 '+ + 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], @@ -1774,8 +1789,8 @@ 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 '+ - 'answer.author_id = %s AND answer.accepted=1 AND question.author_id=auth_user.id'], + 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( @@ -1922,7 +1937,7 @@ def user_favorites(request, user_id, user_view): }, select_params=[user_id], tables=['question', 'auth_user', 'favorite_question'], - where=['question.deleted = 0 AND question.last_activity_by_id = auth_user.id '+ + 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'] @@ -2093,7 +2108,7 @@ def badge(request, id): tables=['award', 'auth_user'], where=['badge_id=%s AND user_id=auth_user.id'], params=[id] - ).values('id').distinct() + ).distinct('id') return render_to_response('badge.html', { 'awards' : awards, @@ -2337,8 +2352,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) @@ -2359,6 +2384,9 @@ 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, @@ -2368,6 +2396,8 @@ def search(request): "searchtitle" : keywords, "keywords" : keywords, "is_unanswered" : False, + "is_search": True, + "search_uri": search_uri, "context" : { 'is_paginated' : True, 'pages': objects_list.num_pages, 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 45685788..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,10 +67,33 @@ INSTALLED_APPS = ( 'django.contrib.sitemaps', 'forum', 'django_authopenid', - 'djangosphinx', - #'debug_toolbar' , + 'debug_toolbar' , 'user_messages', -) +] -# 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 5447e517..c38e3dd8 100644..100755 --- a/settings_local.py.dist +++ b/settings_local.py.dist @@ -2,12 +2,23 @@ import os.path from django.utils.translation import ugettext as _ +def check_local_setting(name, value): + local_vars = locals() + if name in local_vars and local_var[name] == value: + return True + else: + return False + SITE_SRC_ROOT = os.path.dirname(__file__) -LOG_FILENAME = 'django.lanai.log' +LOG_FILENAME = 'django.osqa.log' #for logging import logging -logging.basicConfig(filename=os.path.join(SITE_SRC_ROOT, 'log', LOG_FILENAME), level=logging.DEBUG,) +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'),) @@ -18,10 +29,12 @@ 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 +DATABASE_HOST = '' +DATABASE_PORT = '' #Moved from settings.py for better organization. (please check it up to clean up settings.py) @@ -30,13 +43,13 @@ SERVER_EMAIL = '' DEFAULT_FROM_EMAIL = '' EMAIL_HOST_USER = '' EMAIL_HOST_PASSWORD = '' -EMAIL_SUBJECT_PREFIX = '[CNPROG] ' -EMAIL_HOST='cnprog.com' +EMAIL_SUBJECT_PREFIX = '[OSQA] ' +EMAIL_HOST='osqa.net' EMAIL_PORT='25' EMAIL_USE_TLS=False #LOCALIZATIONS -TIME_ZONE = 'America/Tijuana' +TIME_ZONE = 'America/New_York' ########################### # @@ -48,11 +61,12 @@ FORUM_SCRIPT_ALIAS = '' #no leading slash, default = '' empty string #OTHER SETTINGS -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 @@ -61,25 +75,35 @@ 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=' +APP_URL = 'http://osqa.net' #used by email notif system and RSS +GOOGLE_SITEMAP_CODE = '' GOOGLE_ANALYTICS_KEY = '' 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 + +#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 db8c764e..66dcc3fd 100644 --- a/templates/about.html +++ b/templates/about.html @@ -12,43 +12,8 @@ </div> <div class="content"> -<<<<<<< HEAD:templates/about.html - <!-- default text - <p>edit file templates/about.html. Below are just suggestions of what can go here</p> - <p>what is your site for?</p> - <p>how does it work? what are roles of members?</p> - <p>is there a place to find out more about this website?</p> - --> - <p>{% blocktrans %}<strong>CNPROG <span class="orange">Q&A</span></strong> is a collaboratively edited question - and answer site created for the <strong>CNPROG</strong> community. - {% endblocktrans %} - </p> - <p>{% blocktrans %}Here you can <strong>ask</strong> and <strong>answer</strong> questions, <strong>comment</strong> - and <strong>vote</strong> for the questions of others and their answers. Both questions and answers - <strong>can be revised</strong> and improved. Questions can be <strong>tagged</strong> with - the relevant keywords to simplify future access and organize the accumulated material.{% endblocktrans %} - </p> + <p class="strong">Please customize file templates/about.html</p> - <p>{% blocktrans %}This <span class="orange">Q&A</span> site is moderated by its members, hopefully - including yourself! - Moderation rights are gradually assigned to the site users based on the accumulated <strong>"reputation"</strong> - points. These points are added to the users account when others vote for his/her questions or answers. - These points (very) roughly reflect the level of trust of the community.{% endblocktrans %} - </p> - <p>{% blocktrans %}No points are necessary to ask or answer the questions - so please - - <strong><a href="{% url user_signin %}">join us!</a></strong>{% endblocktrans %} - </p> - <p> - {% blocktrans %} - If you would like to find out more about this site - please see <strong><a href="{% url faq %}">frequently asked questions</a></strong>. - {% endblocktrans %} -======= - <p><strong>NMR Wiki <span class="orange">Q&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&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>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 @@ -65,7 +30,6 @@ </p> <p> If you would like to find out more about this site - please see <strong><a href="{% url faq %}">frequently asked questions</a></strong>. ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/about.html </p> </div> {% endblock %} 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 }}">●</span> {{ badge.name }}</a><strong> × {{ 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 Binary files differindex b53732e3..6a250e35 100644 --- a/templates/content/images/logo.png +++ b/templates/content/images/logo.png diff --git a/templates/content/jquery-openid/images/local-login.png b/templates/content/jquery-openid/images/local-login.png Binary files differdeleted file mode 100644 index 258cedac..00000000 --- a/templates/content/jquery-openid/images/local-login.png +++ /dev/null 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 0a928cd2..aba67eee 100644 --- a/templates/content/style/style.css +++ b/templates/content/style/style.css @@ -114,10 +114,6 @@ blockquote margin-left:20px;text-decoration:underline; font-size:12px; color:#333333;} #logo { padding: 5px 0px 0px 0px; -<<<<<<< HEAD:templates/content/style/style.css - margin-bottom:-3px; -======= ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/style/style.css } #navBar {float:clear;position:relative;display:block;width:960px;} #navBar .nav {margin:20px 0px 0px 16px; @@ -166,11 +162,7 @@ blockquote border-right:1px solid #b4b48e; border-bottom:1px solid #b4b48e;*/ background: white;/* #f9f7ed;*/ -<<<<<<< HEAD:templates/content/style/style.css - margin:10px 0 10px 0; -======= /*margin:10px 0 10px 0;*/ ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/style/style.css /*background:url(../images/quest-bg.gif) repeat-x top;*/ } #listA .qstA thumb {float:left; } @@ -212,9 +204,6 @@ blockquote /*border-bottom:1px solid #888a85;*/ } .evenMore {font-size:14px; font-weight:800;} -<<<<<<< HEAD:templates/content/style/style.css -.questions-count{font-size:32px;font-family:sans-serif;font-weight:600;padding:0 0 5px 7px;color:#a40000;} -======= .questions-count{ font-size:32px; font-family:sans-serif; @@ -223,7 +212,6 @@ blockquote color:#a40000; margin-top:3px; } ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/style/style.css /*内容块*/ .boxA {background:#888a85; padding:6px; margin-bottom:8px;border 1px solid #babdb6;} @@ -235,11 +223,7 @@ blockquote .boxB .body {border:1px solid #aaaaaa; padding:8px; background:#FFF; font-size:13px; line-height:160%;} .boxB .more {padding:1px; text-align:right; font-weight:800;} .boxC { -<<<<<<< HEAD:templates/content/style/style.css - background:#babdb6;/*f9f7ed;*/ -======= background: #cacdc6;/*f9f7ed;*/ ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/style/style.css padding:10px; margin-bottom:8px; border-top:1px solid #eeeeec; @@ -247,15 +231,12 @@ blockquote border-right:1px solid #a9aca5; border-bottom:1px solid #babdb6; } -<<<<<<< HEAD:templates/content/style/style.css -======= .boxC p { margin-bottom:8px; } .boxC p.nomargin { margin:0px; } ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/style/style.css .boxC p.info-box-follow-up-links { text-align:right; margin:0; @@ -289,11 +270,7 @@ blockquote border:1px solid #fff; background-color:#fff; color:#777; -<<<<<<< HEAD:templates/content/style/style.css - padding:.3em; -======= padding:2px 4px 3px 4px; ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/style/style.css font:bold 100% sans-serif; } @@ -344,21 +321,14 @@ blockquote /*标签*/ .tag {font-size:13px; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;} .tags {font-family:sans-serif; line-height:200%; display:block; margin-top:5px;} -<<<<<<< HEAD:templates/content/style/style.css -.tags a {font-size:13px; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;} -======= .tags a {white-space: nowrap; font-size:13px; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;} ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/style/style.css .tags a:hover {background-color:#fFF;color:#333;} .tagsbox {line-height:200%;} .tagsbox a {font-size:13px; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;} .tagsbox a:hover {background-color:#fFF;color:#333;} .tag-number {font-weight:700;font-family:sans-serif;} -<<<<<<< HEAD:templates/content/style/style.css -======= .marked-tags { margin-top: 0px;margin-bottom: 5px; } .deletable-tag { margin-right: 3px; white-space:nowrap; } ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/style/style.css /*奖牌*/ a.medal { font-size:14px; line-height:250%; font-weight:800; color:#333; text-decoration:none; background:url(../images/medala.gif) no-repeat; border-left:1px solid #EEE; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:4px 12px 4px 6px;} @@ -716,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; @@ -1188,12 +1163,9 @@ ul.bulleta li {background:url(../images/bullet_green.gif) no-repeat 0px 2px; pad .message p { margin-bottom:0px; } -<<<<<<< HEAD:templates/content/style/style.css -======= -.message p.space-above { +p.space-above { margin-top:10px; } ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/style/style.css .warning{color:red;} .darkred{color:darkred;} @@ -1470,8 +1442,6 @@ ul.form-horizontal-rows li input { text-align:center; font-weight:bold; } -<<<<<<< HEAD:templates/content/style/style.css -======= #tagSelector { padding-bottom: 2px; } @@ -1481,4 +1451,16 @@ ul.form-horizontal-rows li input { #hideIgnoredTagsCb { margin: 0 2px 0 1px; } ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/content/style/style.css +#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 42074763..466659de 100644 --- a/templates/header.html +++ b/templates/header.html @@ -32,12 +32,6 @@ {% endif %} <a id="nav_badges" href="{% url badges %}">{% trans "badges" %}</a> <a id="nav_unanswered" href="{% url unanswered %}">{% trans "unanswered questions" %}</a> - - {% comment %}<!-- i think this needs to be removed -e.f. --> - {% if request.user.is_authenticated %} - <a id="nav_profile" href="{% url user %}{{ request.user.id }}/{{ request.user.username }}/">{% trans "my profile" %}</a> - {% endif %} - {% endcomment %} <div class="focus"> <a id="nav_ask" href="{% url ask %}" class="special">{% trans "ask a question" %}</a> </div> diff --git a/templates/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 e88f7ef1..3955c059 100644 --- a/templates/question.html +++ b/templates/question.html @@ -1,535 +1,510 @@ -{% 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.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 %} -<<<<<<< HEAD:templates/question.html - {% if request.user|can_delete_post:question %} - <span class="action-link"><a id="question-delete-link-{{question.id}}">{% trans "delete" %}</a></span> - {% endif %} - {% separator %} -======= ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/question.html - {% 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 %} -<<<<<<< HEAD:templates/question.html -======= - {% separator %} - {% if request.user|can_delete_post:question %} - <span class="action-link"><a id="question-delete-link-{{question.id}}">{% trans "delete" %}</a></span> - {% endif %} ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/question.html - {% 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 %} -<<<<<<< HEAD:templates/question.html - {% 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 %} - {% separator %} -======= ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/question.html - {% 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 %} -<<<<<<< HEAD:templates/question.html -======= - {% 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 %} ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/question.html - {% 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">×{{ 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 --> +{% 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">×{{ 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 -->
diff --git a/templates/questions.html b/templates/questions.html index 7cbcbd2b..67751996 100644 --- a/templates/questions.html +++ b/templates/questions.html @@ -1,271 +1,235 @@ -{% 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"> -<<<<<<< HEAD:templates/questions.html - <div class="headQuestions">{% if searchtag %}{% trans "Found by tags" %}{% else %}{% if searchtitle %}{% trans "Found by title" %}{% else %}{% trans "All questions" %}{% endif %}{% endif %}</div> -======= - <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> ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/questions.html - <div class="tabsA"> - <a id="latest" href="?sort=latest" class="off" title="{% trans "most recently asked questions" %}">{% trans "newest" %}</a> - <a id="active" href="?sort=active" class="off" title="{% trans "most recently updated questions" %}">{% trans "active" %}</a> - <a id="hottest" href="?sort=hottest" class="off" title="{% trans "hottest questions" %}">{% trans "hottest" %}</a> - <a id="mostvoted" href="?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 %} -<<<<<<< HEAD:templates/questions.html - {% 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 %} - {% 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 %} - {% 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 %} - <p> -======= - {% 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"> ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/questions.html - {% 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 %} -<<<<<<< HEAD:templates/questions.html - <a rel="tag" title="{% trans "see questions tagged" %}'{{ tag.name }}'{% trans "using tags" %}" href="{% url forum.views.tag tag.name|urlencode %}">{{ tag.name }}</a> - <span class="tag-number">× {{ tag.used_count|intcomma }}</span> - <br /> - {% endfor %} - <br /> -======= - <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">× {{ tag.used_count|intcomma }}</span> - <br /> - {% endfor %} ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:templates/questions.html - </div> -</div> - -{% 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">× {{ tag.used_count|intcomma }}</span>
+ <br />
+ {% endfor %}
+ </div>
+</div>
+
+{% endblock %}
+<!-- end 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 -->
@@ -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'] + |