summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--INSTALL2
-rw-r--r--TODO4
-rw-r--r--cnprog.wsgi8
-rw-r--r--context.py35
-rw-r--r--development.log56
-rw-r--r--django_authopenid/forms.py82
-rw-r--r--django_authopenid/middleware.py2
-rw-r--r--django_authopenid/urls.py5
-rw-r--r--django_authopenid/views.py208
-rw-r--r--drop-all-tables.sh4
-rw-r--r--forum/admin.py5
-rw-r--r--forum/auth.py886
-rw-r--r--forum/feed.py86
-rw-r--r--forum/forms.py402
-rw-r--r--forum/management/commands/once_award_badges.py697
-rw-r--r--forum/management/commands/send_email_alerts.py41
-rw-r--r--forum/managers.py518
-rw-r--r--forum/models.py1462
-rw-r--r--forum/templatetags/extra_filters.py164
-rw-r--r--forum/templatetags/extra_tags.py480
-rw-r--r--forum/user.py149
-rw-r--r--forum/views.py4042
-rw-r--r--locale/en/LC_MESSAGES/django.mobin367 -> 13992 bytes
-rw-r--r--locale/en/LC_MESSAGES/django.po1593
-rw-r--r--locale/es/LC_MESSAGES/django.po1430
-rw-r--r--locale/zh_CN/LC_MESSAGES/django.mobin24652 -> 37880 bytes
-rw-r--r--locale/zh_CN/LC_MESSAGES/django.po743
-rw-r--r--manage.py22
-rw-r--r--rmpyc1
-rw-r--r--settings.py206
-rw-r--r--sql_scripts/update_2009_07_05_EF.sql3
-rw-r--r--templates/404.html2
-rw-r--r--templates/500.html4
-rw-r--r--templates/about.html68
-rw-r--r--templates/answer_edit.html2
-rw-r--r--templates/answer_edit_tips.html87
-rw-r--r--templates/ask.html52
-rw-r--r--templates/authopenid/changeemail.html100
-rw-r--r--templates/authopenid/changeopenid.html2
-rw-r--r--templates/authopenid/changepw.html2
-rw-r--r--templates/authopenid/complete.html17
-rw-r--r--templates/authopenid/confirm_email.txt17
-rw-r--r--templates/authopenid/delete.html2
-rw-r--r--templates/authopenid/email_validation.txt15
-rw-r--r--templates/authopenid/failure.html3
-rw-r--r--templates/authopenid/sendpw.html2
-rw-r--r--templates/authopenid/sendpw_email.txt18
-rw-r--r--templates/authopenid/settings.html2
-rw-r--r--templates/authopenid/signin.html122
-rw-r--r--templates/authopenid/signup.html3
-rw-r--r--templates/badge.html2
-rw-r--r--templates/badges.html14
-rw-r--r--templates/base.html29
-rw-r--r--templates/base_content.html27
-rw-r--r--templates/book.html2
-rw-r--r--templates/close.html2
-rw-r--r--templates/content/jquery-openid/images/aol.gifbin0 -> 2205 bytes
-rw-r--r--templates/content/jquery-openid/images/blogger.icobin0 -> 3638 bytes
-rw-r--r--templates/content/jquery-openid/images/claimid.icobin0 -> 3638 bytes
-rw-r--r--templates/content/jquery-openid/images/facebook.gifbin0 -> 2075 bytes
-rw-r--r--templates/content/jquery-openid/images/flickr.icobin0 -> 1150 bytes
-rw-r--r--templates/content/jquery-openid/images/google.gifbin0 -> 1596 bytes
-rw-r--r--templates/content/jquery-openid/images/livejournal.icobin0 -> 5222 bytes
-rw-r--r--templates/content/jquery-openid/images/myopenid.icobin0 -> 2862 bytes
-rw-r--r--templates/content/jquery-openid/images/openid-inputicon.gifbin0 -> 237 bytes
-rw-r--r--templates/content/jquery-openid/images/openid.gifbin0 -> 740 bytes
-rw-r--r--templates/content/jquery-openid/images/openidico.pngbin0 -> 654 bytes
-rw-r--r--templates/content/jquery-openid/images/technorati.icobin0 -> 2294 bytes
-rw-r--r--templates/content/jquery-openid/images/verisign.icobin0 -> 4710 bytes
-rw-r--r--templates/content/jquery-openid/images/vidoop.icobin0 -> 1406 bytes
-rw-r--r--templates/content/jquery-openid/images/wordpress.icobin0 -> 1150 bytes
-rw-r--r--templates/content/jquery-openid/images/yahoo.gifbin0 -> 1682 bytes
-rw-r--r--templates/content/jquery-openid/jquery.openid.js92
-rw-r--r--templates/content/jquery-openid/openid.css33
-rw-r--r--templates/content/js/com.cnprog.editor.js134
-rw-r--r--templates/content/js/com.cnprog.i18n.js8
-rw-r--r--templates/content/js/com.cnprog.post.js1247
-rw-r--r--templates/content/js/com.cnprog.utils.js2
-rw-r--r--templates/content/js/jquery.ajaxfileupload.js390
-rw-r--r--templates/content/style/default.css12
-rw-r--r--templates/content/style/openid.css2
-rw-r--r--templates/content/style/style.css2116
-rw-r--r--templates/faq.html230
-rw-r--r--templates/footer.html61
-rw-r--r--templates/header.html140
-rw-r--r--templates/index.html10
-rw-r--r--templates/logout.html4
-rw-r--r--templates/pagesize.html4
-rw-r--r--templates/paginator.html14
-rw-r--r--templates/privacy.html2
-rw-r--r--templates/question.html21
-rw-r--r--templates/question_edit.html2
-rw-r--r--templates/question_retag.html25
-rw-r--r--templates/questions.html88
-rw-r--r--templates/reopen.html5
-rw-r--r--templates/revisions_answer.html2
-rw-r--r--templates/revisions_question.html3
-rw-r--r--templates/tags.html1
-rw-r--r--templates/unanswered.html2
-rwxr-xr-xtemplates/upfiles/1245715031297631.pngbin0 -> 3863 bytes
-rwxr-xr-xtemplates/upfiles/12457157052552259.pngbin0 -> 3863 bytes
-rw-r--r--templates/user.html64
-rw-r--r--templates/user_edit.html2
-rw-r--r--templates/user_favorites.html2
-rw-r--r--templates/user_info.html2
-rw-r--r--templates/user_preferences.html15
-rw-r--r--templates/user_recent.html2
-rw-r--r--templates/user_reputation.html2
-rw-r--r--templates/user_responses.html2
-rw-r--r--templates/user_stats.html2
-rw-r--r--templates/user_tabs.html2
-rw-r--r--templates/user_votes.html2
-rw-r--r--templates/users.html1
-rw-r--r--urls.py131
-rw-r--r--utils/cache.py184
-rw-r--r--utils/html.py102
-rw-r--r--utils/lists.py172
118 files changed, 10623 insertions, 8647 deletions
diff --git a/.gitignore b/.gitignore
index 02ec314a..b217d5cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
-settings_local.py
*.pyc
*.swp
*.log
diff --git a/INSTALL b/INSTALL
index 59f2dd0b..35683147 100644
--- a/INSTALL
+++ b/INSTALL
@@ -15,6 +15,8 @@ Used for HTML sanitizer
5. Markdown2
http://code.google.com/p/python-markdown2/
+6. Django Debug Toolbar
+http://github.com/robhudson/django-debug-toolbar/tree/master
INSTALL STEPS:
-----------------------------------------------
diff --git a/TODO b/TODO
new file mode 100644
index 00000000..3769fa08
--- /dev/null
+++ b/TODO
@@ -0,0 +1,4 @@
+*check change email function - there is a strange 'password' field in form
+*make reusable question-block template for index questions unanswered - get rid of copy-paste
+*unused votes count in user profile not working - left that commented out in templates/user_info.html
+*badge award notification messages need to be set fixed at place where badges are awarded
diff --git a/cnprog.wsgi b/cnprog.wsgi
new file mode 100644
index 00000000..a1bd8039
--- /dev/null
+++ b/cnprog.wsgi
@@ -0,0 +1,8 @@
+import os
+import sys
+
+sys.path.append('/var/www/vhosts')
+os.environ['DJANGO_SETTINGS_MODULE'] = 'cnprog.settings'
+
+import django.core.handlers.wsgi
+application = django.core.handlers.wsgi.WSGIHandler()
diff --git a/context.py b/context.py
index f420b154..c068332c 100644
--- a/context.py
+++ b/context.py
@@ -1,9 +1,40 @@
from django.conf import settings
def application_settings(context):
- return {
+ my_settings = {
'APP_TITLE' : settings.APP_TITLE,
'APP_URL' : settings.APP_URL,
'APP_KEYWORDS' : settings.APP_KEYWORDS,
'APP_DESCRIPTION' : settings.APP_DESCRIPTION,
- 'APP_INTRO' : settings.APP_INTRO
+ 'APP_INTRO' : settings.APP_INTRO,
+ 'EMAIL_VALIDATION': settings.EMAIL_VALIDATION,
+ 'LANGUAGE_CODE': settings.LANGUAGE_CODE,
+ 'GOOGLE_SITEMAP_CODE':settings.GOOGLE_SITEMAP_CODE,
+ 'GOOGLE_ANALYTICS_KEY':settings.GOOGLE_ANALYTICS_KEY,
}
+ return {'settings':my_settings}
+
+def auth_processor(request):
+ """
+ Returns context variables required by apps that use Django's authentication
+ system.
+
+ If there is no 'user' attribute in the request, uses AnonymousUser (from
+ django.contrib.auth).
+ """
+ if hasattr(request, 'user'):
+ user = request.user
+ if user.is_authenticated():
+ messages = user.message_set.all()
+ else:
+ messages = None
+ else:
+ from django.contrib.auth.models import AnonymousUser
+ user = AnonymousUser()
+ messages = None
+
+ from django.core.context_processors import PermWrapper
+ return {
+ 'user': user,
+ 'messages': messages,
+ 'perms': PermWrapper(user),
+ }
diff --git a/development.log b/development.log
index 7ad467e0..5310f70b 100644
--- a/development.log
+++ b/development.log
@@ -1,27 +1,69 @@
-# development
+==Aug 5, 2009 Evgeny==
+===Interface changes===
+Merged in my code that:
+* allows anonymous posting of Q&A and then login
+* per-question email notifications via 'send_email_alerts' command
+* allows space character in username
+* improves openid login
+* makes notification messages sticky - have to click "x" to dismiss
+* unanswered questions are now those with no accepted answer
+* added following setting parameters:
+
+settings.MIN_USERNAME_LENGTH = 1
+settings.EMAIL_UNIQUE = True|False
+settings.EMAIL_VALIDATION = 'on'|'off' #thought of maybe adding other options so type is string
+settings.GOOGLE_SITEMAP_CODE = <string>
+settings.GOOGLE_ANALYTICS_KEY = <string>
+
+===Fixes===
+* fixed incorrect answer count issue in question.html
+* translated Twittwer stuff in user_preferences.html
+* translated question_retag.html, except one phrase
+* added Adolfo's python2.4 fix
+* fixed template debugging comments so that they don't break page layout
+* reorganized header template so that it takes less vertical space
+
+===Code changes===
+* wrapped template context settings into a single settings dictionary
+* on login anonymous session is recorded so that anonymously posted questions (if any) could be found later
+* added models: AnonymousQuestion, AnonymousAnswer, EmailFeed (for notifications)
+* User model has two new fields email_key - 32 byte hex hash and email_isvalid - boolean
+ file sql_scripts/update_2009_07_05_EF.sql will make upgrade to the live database
+* added auth_processor to context.py which loads notification messages without deleting them
+ NOTE: default django auth processor must be removed!
+* added ajax actions questionSubscribeUpdates/questionUnsubscribeUpdates
+
+==Aug 4 2009, Evgeny==
+===Changes===
+* commented out LocaleMiddleware - language can be now switched with settings.LANGUAGE_CODE
+* added DATABASE_ENGINE line to settings_local
+===Merges===
+* Adolfo's slugification of urls
+* Added Chaitanyas changes for traditional login/signup and INSTALL file
==July 26 2009, Evgeny==
django_authopenid:
considerably changed user interface
+[comment] - sorry - this is not done yet
log/forum/forms.py:
-- added tag input validation using regex
-- fixed bug with date type mismatch near self.fields['birthday'] =
+* added tag input validation using regex
+* fixed bug with date type mismatch near self.fields['birthday'] =
in EditUserForm.__init__()
/forum/templatetags/extra_tags.py:
-- fixed date type mismatch in get_age()
+* fixed date type mismatch in get_age()
/templates/content/js/com.cnprog.post.js:
-- fixed bug with post deletion/recovery
+* fixed bug with post deletion/recovery
javascript:
-- changed to use of non-minified code - better for editing
+* changed to use of non-minified code - better for editing
and debugging
/templates/question.html:
-- fixed display of delete/undelete links
+* fixed display of delete/undelete links
templates:
added comments in the beginning/end of each template
diff --git a/django_authopenid/forms.py b/django_authopenid/forms.py
index 09fa76b1..690a781f 100644
--- a/django_authopenid/forms.py
+++ b/django_authopenid/forms.py
@@ -158,7 +158,7 @@ class OpenidRegisterForm(forms.Form):
raise forms.ValidationError(_('invalid user name'))
if self.cleaned_data['username'] in RESERVED_NAMES:
raise forms.ValidationError(_('sorry, this name can not be used, please try another'))
- if len(self.cleaned_data['username']) < 3:
+ if len(self.cleaned_data['username']) < settings.MIN_USERNAME_LENGTH:
raise forms.ValidationError(_('username too short'))
try:
user = User.objects.get(
@@ -171,19 +171,22 @@ class OpenidRegisterForm(forms.Form):
raise forms.ValidationError(_('this name is already in use - please try anoter'))
def clean_email(self):
- """For security reason one unique email in database"""
+ """Optionally, for security reason one unique email in database"""
if 'email' in self.cleaned_data:
- try:
- user = User.objects.get(email = self.cleaned_data['email'])
- except User.DoesNotExist:
+ if settings.EMAIL_UNIQUE == True:
+ try:
+ user = User.objects.get(email = self.cleaned_data['email'])
+ except User.DoesNotExist:
+ return self.cleaned_data['email']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'There is already more than one \
+ account registered with that e-mail address. Please try \
+ another.')
+ raise forms.ValidationError(_("This email is already \
+ registered in our database. Please choose another."))
+ else:
return self.cleaned_data['email']
- except User.MultipleObjectsReturned:
- raise forms.ValidationError(u'There is already more than one \
- account registered with that e-mail address. Please try \
- another.')
- raise forms.ValidationError(_("This email is already \
- registered in our database. Please choose another."))
-
+ #what if not???
class OpenidVerifyForm(forms.Form):
""" openid verify form (associate an openid with an account) """
@@ -204,8 +207,7 @@ class OpenidVerifyForm(forms.Form):
""" validate username """
if 'username' in self.cleaned_data:
if not username_re.search(self.cleaned_data['username']):
- raise forms.ValidationError(_("Usernames can only contain \
- letters, numbers and underscores"))
+ raise forms.ValidationError(_('invalid user name'))
try:
user = User.objects.get(
username__exact = self.cleaned_data['username']
@@ -241,7 +243,7 @@ class OpenidVerifyForm(forms.Form):
attrs_dict = { 'class': 'required' }
-username_re = re.compile(r'^\w+$')
+username_re = re.compile(r'^[\w ]+$')
class RegistrationForm(forms.Form):
""" legacy registration form """
@@ -286,17 +288,20 @@ class RegistrationForm(forms.Form):
""" validate if email exist in database
:return: raise error if it exist """
if 'email' in self.cleaned_data:
- try:
- user = User.objects.get(email = self.cleaned_data['email'])
- except User.DoesNotExist:
+ if settings.EMAIL_UNIQUE == True:
+ try:
+ user = User.objects.get(email = self.cleaned_data['email'])
+ except User.DoesNotExist:
+ return self.cleaned_data['email']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'There is already more than one \
+ account registered with that e-mail address. Please try \
+ another.')
+ raise forms.ValidationError(u'This email is already registered \
+ in our database. Please choose another.')
+ else:
return self.cleaned_data['email']
- except User.MultipleObjectsReturned:
- raise forms.ValidationError(u'There is already more than one \
- account registered with that e-mail address. Please try \
- another.')
- raise forms.ValidationError(u'This email is already registered \
- in our database. Please choose another.')
- return self.cleaned_data['email']
+ #what if not?
def clean_password2(self):
"""
@@ -361,18 +366,21 @@ class ChangeemailForm(forms.Form):
def clean_email(self):
""" check if email don't exist """
if 'email' in self.cleaned_data:
- if self.user.email != self.cleaned_data['email']:
- try:
- user = User.objects.get(email = self.cleaned_data['email'])
- except User.DoesNotExist:
- return self.cleaned_data['email']
- except User.MultipleObjectsReturned:
- raise forms.ValidationError(u'There is already more than one \
- account registered with that e-mail address. Please try \
- another.')
- raise forms.ValidationError(u'This email is already registered \
- in our database. Please choose another.')
- return self.cleaned_data['email']
+ if settings.EMAIL_UNIQUE == True:
+ if self.user.email != self.cleaned_data['email']:
+ try:
+ user = User.objects.get(email = self.cleaned_data['email'])
+ except User.DoesNotExist:
+ return self.cleaned_data['email']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'There is already more than one \
+ account registered with that e-mail address. Please try \
+ another.')
+ raise forms.ValidationError(u'This email is already registered \
+ in our database. Please choose another.')
+ else:
+ return self.cleaned_data['email']
+ #what if not?
def clean_password(self):
diff --git a/django_authopenid/middleware.py b/django_authopenid/middleware.py
index c0572c6e..2900d54c 100644
--- a/django_authopenid/middleware.py
+++ b/django_authopenid/middleware.py
@@ -21,4 +21,4 @@ class OpenIDMiddleware(object):
mimeparse.best_match(['text/html', 'application/xrds+xml'],
request.META['HTTP_ACCEPT']) == 'application/xrds+xml':
return HttpResponseRedirect(reverse('yadis_xrdf'))
- return response \ No newline at end of file
+ return response
diff --git a/django_authopenid/urls.py b/django_authopenid/urls.py
index 1ab65941..6843e5c0 100644
--- a/django_authopenid/urls.py
+++ b/django_authopenid/urls.py
@@ -7,6 +7,8 @@ urlpatterns = patterns('django_authopenid.views',
url(r'^yadis.xrdf$', 'xrdf', name='yadis_xrdf'),
# manage account registration
url(r'^%s$' % _('signin/'), 'signin', name='user_signin'),
+ url(r'^%s%s$' % (_('signin/'),_('newquestion/')), 'signin', kwargs = {'newquestion':True}),
+ url(r'^%s%s$' % (_('signin/'),_('newanswer/')), 'signin', kwargs = {'newanswer':True}),
url(r'^%s$' % _('signout/'), 'signout', name='user_signout'),
url(r'^%s%s$' % (_('signin/'), _('complete/')), 'complete_signin',
name='user_complete_signin'),
@@ -21,7 +23,8 @@ urlpatterns = patterns('django_authopenid.views',
# manage account settings
#url(r'^$', 'account_settings', name='user_account_settings'),
#url(r'^%s$' % _('password/'), 'changepw', name='user_changepw'),
- #url(r'^%s$' % _('email/'), 'changeemail', name='user_changeemail'),
+ url(r'^%s$' % 'email/', 'changeemail', name='user_changeemail',kwargs = {'action':'change'}),
+ url(r'^%s%s$' % ('email/','validate/'), 'changeemail', name='user_changeemail',kwargs = {'action':'validate'}),
#url(r'^%s$' % _('openid/'), 'changeopenid', name='user_changeopenid'),
url(r'^%s$' % _('delete/'), 'delete', name='user_delete'),
)
diff --git a/django_authopenid/views.py b/django_authopenid/views.py
index a9072c12..1cb8928b 100644
--- a/django_authopenid/views.py
+++ b/django_authopenid/views.py
@@ -30,12 +30,12 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-from django.http import HttpResponseRedirect, get_host
+from django.http import HttpResponseRedirect, get_host, Http404
from django.shortcuts import render_to_response as render
from django.template import RequestContext, loader, Context
from django.conf import settings
from django.contrib.auth.models import User
-from django.contrib.auth import login, logout
+from django.contrib.auth import logout #for login I've added wrapper below - called login
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.utils.encoding import smart_unicode
@@ -44,6 +44,7 @@ from django.utils.translation import ugettext as _
from django.contrib.sites.models import Site
from django.utils.http import urlquote_plus
from django.core.mail import send_mail
+from django.views.defaults import server_error
from openid.consumer.consumer import Consumer, \
SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
@@ -65,6 +66,16 @@ from django_authopenid.forms import OpenidSigninForm, OpenidAuthForm, OpenidRegi
OpenidVerifyForm, RegistrationForm, ChangepwForm, ChangeemailForm, \
ChangeopenidForm, DeleteForm, EmailPasswordForm
+def login(request,user):
+ from django.contrib.auth import login as _login
+ from forum.models import user_logged_in #custom signal
+ #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
+ user_logged_in.send(user=user,session_key=session_key,sender=None)
+
def get_url_host(request):
if request.is_secure():
protocol = 'https'
@@ -76,8 +87,6 @@ def get_url_host(request):
def get_full_url(request):
return get_url_host(request) + request.get_full_path()
-
-
def ask_openid(request, openid_url, redirect_to, on_failure=None,
sreg_request=None):
""" basic function to ask openid and return response """
@@ -96,7 +105,7 @@ def ask_openid(request, openid_url, redirect_to, on_failure=None,
try:
auth_request = consumer.begin(openid_url)
except DiscoveryFailure:
- msg = _(u"非法OpenID地址: %s" % openid_url)
+ msg = _(u"OpenID %(openid_url)s is invalid" % {'openid_url':openid_url})
return on_failure(request, msg)
if sreg_request:
@@ -113,7 +122,6 @@ def complete(request, on_success=None, on_failure=None, return_to=None):
# make sure params are encoded in utf8
params = dict((k,smart_unicode(v)) for k, v in request.GET.items())
openid_response = consumer.complete(params, return_to)
-
if openid_response.status == SUCCESS:
return on_success(request, openid_response.identity_url,
@@ -150,7 +158,7 @@ def not_authenticated(func):
return decorated
@not_authenticated
-def signin(request):
+def signin(request,newquestion=False,newanswer=False):
"""
signin page. It manage the legacy authentification (user/password)
and authentification with openid.
@@ -168,7 +176,7 @@ def signin(request):
if request.POST:
- if 'bsignin' in request.POST.keys():
+ if 'bsignin' in request.POST.keys() or 'openid_username' in request.POST.keys():
form_signin = OpenidSigninForm(request.POST)
if form_signin.is_valid():
@@ -179,7 +187,6 @@ def signin(request):
reverse('user_complete_signin'),
urllib.urlencode({'next':next})
)
-
return ask_openid(request,
form_signin.cleaned_data['openid_url'],
redirect_to,
@@ -195,8 +202,24 @@ def signin(request):
next = clean_next(form_auth.cleaned_data.get('next'))
return HttpResponseRedirect(next)
+ question = None
+ if newquestion == True:
+ from forum.models import AnonymousQuestion as AQ
+ session_key = request.session.session_key
+ qlist = AQ.objects.filter(session_key=session_key).order_by('-added_at')
+ if len(qlist) > 0:
+ question = qlist[0]
+ answer = None
+ if newanswer == True:
+ from forum.models import AnonymousAnswer as AA
+ session_key = request.session.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', {
+ 'question':question,
+ 'answer':answer,
'form1': form_auth,
'form2': form_signin,
'msg': request.GET.get('msg',''),
@@ -220,7 +243,7 @@ def signin_success(request, identity_url, openid_response):
if openid isn't registered user is redirected to register page.
"""
- openid_ = from_openid_response(openid_response)
+ openid_ = from_openid_response(openid_response) #create janrain OpenID object
request.session['openid'] = openid_
try:
rel = UserAssociation.objects.get(openid_url__exact = str(openid_))
@@ -278,7 +301,8 @@ def register(request):
'next': next,
'username': nickname,
})
-
+
+ user_ = None
if request.POST:
just_completed = False
if 'bnewaccount' in request.POST.keys():
@@ -309,14 +333,41 @@ def register(request):
user_id=user_.id)
uassoc.save()
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
- # redirect, can redirect only if forms are valid.
- if is_redirect:
- return HttpResponseRedirect(next)
+ if user_ != None and settings.EMAIL_VALIDATION == 'on':
+ send_new_email_key(user_,nomessage=True)
+ output = validation_email_sent(request)
+ set_email_validation_message(user_) #message set after generating view
+ return output
+ elif user_.is_authenticated():
+ return HttpResponseRedirect('/')
+ else:
+ raise server_error('')
+ openid_str = str(openid_)
+ bits = openid_str.split('/')
+ base_url = bits[2] #assume this is base url
+ url_bits = base_url.split('.')
+ provider_name = url_bits[-2].lower()
+
+ providers = {'yahoo':'<font color="purple">Yahoo!</font>',
+ 'flickr':'<font color="#0063dc">flick</font><font color="#ff0084">r</font>&trade;',
+ 'google':'Google&trade;',
+ 'aol':'<font color="#31658e">AOL</font>',
+ }
+ if provider_name not in providers:
+ provider_logo = provider_name
+ else:
+ provider_logo = providers[provider_name]
+
return render('authopenid/complete.html', {
'form1': form1,
'form2': form2,
+ 'provider':providers[provider_name],
'nickname': nickname,
'email': email
}, context_instance=RequestContext(request))
@@ -464,47 +515,158 @@ def changepw(request):
return render('authopenid/changepw.html', {'form': form },
context_instance=RequestContext(request))
+def find_email_validation_messages(user):
+ msg_text = _('your email needs to be validated')
+ return user.message_set.filter(message__exact=msg_text)
+
+def set_email_validation_message(user):
+ messages = find_email_validation_messages(user)
+ msg_text = _('your email needs to be validated')
+ if len(messages) == 0:
+ user.message_set.create(message=msg_text)
+
+def clear_email_validation_message(user):
+ messages = find_email_validation_messages(user)
+ messages.delete()
+
+def set_new_email(user, new_email, nomessage=False):
+ if new_email != user.email:
+ user.email = new_email
+ user.email_isvalid = False
+ user.save()
+ if settings.EMAIL_VALIDATION == 'on':
+ send_new_email_key(user,nomessage=nomessage)
+
+def _send_email_key(user):
+ """private function. sends email containing validation key
+ to user's email address
+ """
+ subject = _("Welcome")
+ message_template = loader.get_template('authopenid/email_validation.txt')
+ import settings
+ message_context = Context({
+ 'validation_link': '%s/email/verify/%d/%s/' % (settings.APP_URL ,user.id,user.email_key)
+ })
+ message = message_template.render(message_context)
+ send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email])
+
+def send_new_email_key(user,nomessage=False):
+ import random
+ random.seed()
+ user.email_key = '%032x' % random.getrandbits(128)
+ user.save()
+ _send_email_key(user)
+ if nomessage==False:
+ set_email_validation_message(user)
+
+@login_required
+def send_email_key(request):
+ """
+ url = /email/sendkey/
+
+ view that is shown right after sending email key
+ email sending is called internally
+
+ raises 404 if email validation is off
+ if current email is valid shows 'key_not_sent' view of
+ authopenid/changeemail.html template
+ """
+
+ if settings.EMAIL_VALIDATION != 'off':
+ if request.user.email_isvalid:
+ return render('authopenid/changeemail.html',
+ { 'email': request.user.email,
+ 'action_type': 'key_not_sent',
+ 'change_link': reverse('user_changeemail')},
+ context_instance=RequestContext(request)
+ )
+ else:
+ _send_email_key(request.user)
+ return validation_email_sent(request)
+ else:
+ raise Http404
+
+
+#internal server view used as return value by other views
+def validation_email_sent(request):
+ return render('authopenid/changeemail.html',
+ { 'email': request.user.email, 'action_type': 'validate', },
+ context_instance=RequestContext(request))
+
+
+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}}/
+ """
+ if settings.EMAIL_VALIDATION != 'off':
+ user = User.objects.get(id=id)
+ if user:
+ if user.email_key == key:
+ user.email_isvalid = True
+ clear_email_validation_message(user)
+ user.save()
+ return render('authopenid/changeemail.html', {
+ 'action_type': 'validation_complete',
+ }, context_instance=RequestContext(request))
+ raise Http404
+
@login_required
def changeemail(request):
"""
changeemail view. It require password or openid to allow change.
- url: /changeemail/
+ url: /email/*
template : authopenid/changeemail.html
"""
msg = request.GET.get('msg', '')
extension_args = {}
user_ = request.user
-
+
redirect_to = get_url_host(request) + reverse('user_changeemail')
+ action = 'change'
if request.POST:
form = ChangeemailForm(request.POST, user=user_)
if form.is_valid():
if not form.test_openid:
- user_.email = form.cleaned_data['email']
- user_.save()
- msg = _("Email changed.")
- redirect = "%s?msg=%s" % (reverse('user_account_settings'),
- urlquote_plus(msg))
- return HttpResponseRedirect(redirect)
+ new_email = form.cleaned_data['email']
+ if new_email != user_.email:
+ if settings.EMAIL_VALIDATION == 'on':
+ action = 'validate'
+ else:
+ action = 'done_novalidate'
+ set_new_email(user_, new_email,nomessage=True)
+ else:
+ action = 'keep'
else:
+ #what does this branch do?
+ return server_error('')
request.session['new_email'] = form.cleaned_data['email']
return ask_openid(request, form.cleaned_data['password'],
redirect_to, on_failure=emailopenid_failure)
+
elif not request.POST and 'openid.mode' in request.GET:
return complete(request, emailopenid_success,
emailopenid_failure, redirect_to)
else:
form = ChangeemailForm(initial={'email': user_.email},
user=user_)
+
- return render('authopenid/changeemail.html', {
+ output = render('authopenid/changeemail.html', {
'form': form,
+ 'email': user_.email,
+ 'action_type': action,
'msg': msg
}, context_instance=RequestContext(request))
+ if action == 'validate':
+ set_email_validation_message(user_)
+
+ return output
+
def emailopenid_success(request, identity_url, openid_response):
openid_ = from_openid_response(openid_response)
diff --git a/drop-all-tables.sh b/drop-all-tables.sh
new file mode 100644
index 00000000..87783b77
--- /dev/null
+++ b/drop-all-tables.sh
@@ -0,0 +1,4 @@
+mysql_username='cnprog'
+mysql_database='cnprog'
+mysqldump -u $mysql_username -p --add-drop-table --no-data $mysql_database | grep ^DROP
+#| mysql -u[USERNAME] -p[PASSWORD] [DATABASE]
diff --git a/forum/admin.py b/forum/admin.py
index 438a99e7..482da048 100644
--- a/forum/admin.py
+++ b/forum/admin.py
@@ -4,6 +4,9 @@ from django.contrib import admin
from models import *
+class AnonymousQuestionAdmin(admin.ModelAdmin):
+ """AnonymousQuestion admin class"""
+
class QuestionAdmin(admin.ModelAdmin):
"""Question admin class"""
@@ -68,4 +71,4 @@ admin.site.register(Repute, ReputeAdmin)
admin.site.register(Activity, ActivityAdmin)
admin.site.register(Book, BookAdmin)
admin.site.register(BookAuthorInfo, BookAuthorInfoAdmin)
-admin.site.register(BookAuthorRss, BookAuthorRssAdmin) \ No newline at end of file
+admin.site.register(BookAuthorRss, BookAuthorRssAdmin)
diff --git a/forum/auth.py b/forum/auth.py
index 0608031a..36ca54d3 100644
--- a/forum/auth.py
+++ b/forum/auth.py
@@ -1,443 +1,443 @@
-"""
-Authorisation related functions.
-
-The actions a User is authorised to perform are dependent on their reputation
-and superuser status.
-"""
-import datetime
-from django.contrib.contenttypes.models import ContentType
-from django.db import transaction
-from models import Repute
-from models import Question
-from models import Answer
-from const import TYPE_REPUTATION
-question_type = ContentType.objects.get_for_model(Question)
-answer_type = ContentType.objects.get_for_model(Answer)
-
-VOTE_UP = 15
-FLAG_OFFENSIVE = 15
-POST_IMAGES = 15
-LEAVE_COMMENTS = 50
-UPLOAD_FILES = 60
-VOTE_DOWN = 100
-CLOSE_OWN_QUESTIONS = 250
-RETAG_OTHER_QUESTIONS = 500
-REOPEN_OWN_QUESTIONS = 500
-EDIT_COMMUNITY_WIKI_POSTS = 750
-EDIT_OTHER_POSTS = 2000
-DELETE_COMMENTS = 2000
-VIEW_OFFENSIVE_FLAGS = 2000
-DISABLE_URL_NOFOLLOW = 2000
-CLOSE_OTHER_QUESTIONS = 3000
-LOCK_POSTS = 4000
-
-VOTE_RULES = {
- 'scope_votes_per_user_per_day' : 30, # how many votes of one user has everyday
- 'scope_flags_per_user_per_day' : 5, # how many times user can flag posts everyday
- 'scope_warn_votes_left' : 10, # start when to warn user how many votes left
- 'scope_deny_unvote_days' : 1, # if 1 days passed, user can't cancel votes.
- 'scope_flags_invisible_main_page' : 3, # post doesn't show on main page if has more than 3 offensive flags
- 'scope_flags_delete_post' : 5, # post will be deleted if it has more than 5 offensive flags
-}
-
-REPUTATION_RULES = {
- 'initial_score' : 1,
- 'scope_per_day_by_upvotes' : 200,
- 'gain_by_upvoted' : 10,
- 'gain_by_answer_accepted' : 15,
- 'gain_by_accepting_answer' : 2,
- 'gain_by_downvote_canceled' : 2,
- 'gain_by_canceling_downvote' : 1,
- 'lose_by_canceling_accepted_answer' : -2,
- 'lose_by_accepted_answer_cancled' : -15,
- 'lose_by_downvoted' : -2,
- 'lose_by_flagged' : -2,
- 'lose_by_downvoting' : -1,
- 'lose_by_flagged_lastrevision_3_times': -30,
- 'lose_by_flagged_lastrevision_5_times': -100,
- 'lose_by_upvote_canceled' : -10,
-}
-
-def can_vote_up(user):
- """Determines if a User can vote Questions and Answers up."""
- return user.is_authenticated() and (
- user.reputation >= VOTE_UP or
- user.is_superuser)
-
-def can_flag_offensive(user):
- """Determines if a User can flag Questions and Answers as offensive."""
- return user.is_authenticated() and (
- user.reputation >= FLAG_OFFENSIVE or
- user.is_superuser)
-
-def can_add_comments(user):
- """Determines if a User can add comments to Questions and Answers."""
- return user.is_authenticated() and (
- user.reputation >= LEAVE_COMMENTS or
- user.is_superuser)
-
-def can_vote_down(user):
- """Determines if a User can vote Questions and Answers down."""
- return user.is_authenticated() and (
- user.reputation >= VOTE_DOWN or
- user.is_superuser)
-
-def can_retag_questions(user):
- """Determines if a User can retag Questions."""
- return user.is_authenticated() and (
- RETAG_OTHER_QUESTIONS <= user.reputation < EDIT_OTHER_POSTS or
- user.is_superuser)
-
-def can_edit_post(user, post):
- """Determines if a User can edit the given Question or Answer."""
- return user.is_authenticated() and (
- user.id == post.author_id or
- (post.wiki and user.reputation >= EDIT_COMMUNITY_WIKI_POSTS) or
- user.reputation >= EDIT_OTHER_POSTS or
- user.is_superuser)
-
-def can_delete_comment(user, comment):
- """Determines if a User can delete the given Comment."""
- return user.is_authenticated() and (
- user.id == comment.user_id or
- user.reputation >= DELETE_COMMENTS or
- user.is_superuser)
-
-def can_view_offensive_flags(user):
- """Determines if a User can view offensive flag counts."""
- return user.is_authenticated() and (
- user.reputation >= VIEW_OFFENSIVE_FLAGS or
- user.is_superuser)
-
-def can_close_question(user, question):
- """Determines if a User can close the given Question."""
- return user.is_authenticated() and (
- (user.id == question.author_id and
- user.reputation >= CLOSE_OWN_QUESTIONS) or
- user.reputation >= CLOSE_OTHER_QUESTIONS or
- user.is_superuser)
-
-def can_lock_posts(user):
- """Determines if a User can lock Questions or Answers."""
- return user.is_authenticated() and (
- user.reputation >= LOCK_POSTS or
- user.is_superuser)
-
-def can_follow_url(user):
- """Determines if the URL link can be followed by Google search engine."""
- return user.reputation >= DISABLE_URL_NOFOLLOW
-
-def can_accept_answer(user, question, answer):
- return (user.is_authenticated() and
- question.author != answer.author and
- question.author == user) or user.is_superuser
-
-# now only support to reopen own question except superuser
-def can_reopen_question(user, question):
- return (user.is_authenticated() and
- user.id == question.author_id and
- user.reputation >= REOPEN_OWN_QUESTIONS) or user.is_superuser
-
-def can_delete_post(user, post):
- return (user.is_authenticated() and
- user.id == post.author_id) or user.is_superuser
-
-def can_view_deleted_post(user, post):
- return user.is_superuser
-
-# user preferences view permissions
-def is_user_self(request_user, target_user):
- return (request_user.is_authenticated() and request_user == target_user)
-
-def can_view_user_votes(request_user, target_user):
- return (request_user.is_authenticated() and request_user == target_user)
-
-def can_view_user_preferences(request_user, target_user):
- return (request_user.is_authenticated() and request_user == target_user)
-
-def can_view_user_edit(request_user, target_user):
- return (request_user.is_authenticated() and request_user == target_user)
-
-def can_upload_files(request_user):
- return (request_user.is_authenticated() and request_user.reputation >= UPLOAD_FILES) or \
- request_user.is_superuser
-
-###########################################
-## actions and reputation changes event
-###########################################
-def calculate_reputation(origin, offset):
- result = int(origin) + int(offset)
- return result if result > 0 else 1
-
-@transaction.commit_on_success
-def onFlaggedItem(item, post, user):
-
- item.save()
- post.offensive_flag_count = post.offensive_flag_count + 1
- post.save()
-
- post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged']))
- post.author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged']),
- question=question, reputed_at=datetime.datetime.now(),
- reputation_type=-4,
- reputation=post.author.reputation)
- reputation.save()
-
- #todo: These should be updated to work on same revisions.
- if post.offensive_flag_count == VOTE_RULES['scope_flags_invisible_main_page'] :
- post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']))
- post.author.save()
-
- reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-6,
- reputation=post.author.reputation)
- reputation.save()
-
- elif post.offensive_flag_count == VOTE_RULES['scope_flags_delete_post']:
- post.author.reputation = calculate_reputation(post.author.reputation,
- int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']))
- post.author.save()
-
- reputation = Repute(user=post.author,
- negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-7,
- reputation=post.author.reputation)
- reputation.save()
-
- post.deleted = True
- #post.deleted_at = datetime.datetime.now()
- #post.deleted_by = Admin
- post.save()
-
-
-@transaction.commit_on_success
-def onAnswerAccept(answer, user):
- answer.accepted = True
- answer.accepted_at = datetime.datetime.now()
- answer.question.answer_accepted = True
- answer.save()
- answer.question.save()
-
- answer.author.reputation = calculate_reputation(answer.author.reputation,
- int(REPUTATION_RULES['gain_by_answer_accepted']))
- answer.author.save()
- reputation = Repute(user=answer.author,
- positive=int(REPUTATION_RULES['gain_by_answer_accepted']),
- question=answer.question,
- reputed_at=datetime.datetime.now(),
- reputation_type=2,
- reputation=answer.author.reputation)
- reputation.save()
-
- user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['gain_by_accepting_answer']))
- user.save()
- reputation = Repute(user=user,
- positive=int(REPUTATION_RULES['gain_by_accepting_answer']),
- question=answer.question,
- reputed_at=datetime.datetime.now(),
- reputation_type=3,
- reputation=user.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onAnswerAcceptCanceled(answer, user):
- answer.accepted = False
- answer.accepted_at = None
- answer.question.answer_accepted = False
- answer.save()
- answer.question.save()
-
- answer.author.reputation = calculate_reputation(answer.author.reputation,
- int(REPUTATION_RULES['lose_by_accepted_answer_cancled']))
- answer.author.save()
- reputation = Repute(user=answer.author,
- negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']),
- question=answer.question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-2,
- reputation=answer.author.reputation)
- reputation.save()
-
- user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['lose_by_canceling_accepted_answer']))
- user.save()
- reputation = Repute(user=user,
- negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']),
- question=answer.question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-1,
- reputation=user.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onUpVoted(vote, post, user):
- vote.save()
-
- post.vote_up_count = int(post.vote_up_count) + 1
- post.score = int(post.score) + 1
- post.save()
-
- if not post.wiki:
- author = post.author
- if Repute.objects.get_reputation_by_upvoted_today(author) < int(REPUTATION_RULES['scope_per_day_by_upvotes']):
- author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['gain_by_upvoted']))
- author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=author,
- positive=int(REPUTATION_RULES['gain_by_upvoted']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=1,
- reputation=author.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onUpVotedCanceled(vote, post, user):
- vote.delete()
-
- post.vote_up_count = int(post.vote_up_count) - 1
- if post.vote_up_count < 0:
- post.vote_up_count = 0
- post.score = int(post.score) - 1
- post.save()
-
- if not post.wiki:
- author = post.author
- author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['lose_by_upvote_canceled']))
- author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=author,
- negative=int(REPUTATION_RULES['lose_by_upvote_canceled']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-8,
- reputation=author.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onDownVoted(vote, post, user):
- vote.save()
-
- post.vote_down_count = int(post.vote_down_count) + 1
- post.score = int(post.score) - 1
- post.save()
-
- if not post.wiki:
- author = post.author
- author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['lose_by_downvoted']))
- author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=author,
- negative=int(REPUTATION_RULES['lose_by_downvoted']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-3,
- reputation=author.reputation)
- reputation.save()
-
- user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['lose_by_downvoting']))
- user.save()
-
- reputation = Repute(user=user,
- negative=int(REPUTATION_RULES['lose_by_downvoting']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=-5,
- reputation=user.reputation)
- reputation.save()
-
-@transaction.commit_on_success
-def onDownVotedCanceled(vote, post, user):
- vote.delete()
-
- post.vote_down_count = int(post.vote_down_count) - 1
- if post.vote_down_count < 0:
- post.vote_down_count = 0
- post.score = post.score + 1
- post.save()
-
- if not post.wiki:
- author = post.author
- author.reputation = calculate_reputation(author.reputation,
- int(REPUTATION_RULES['gain_by_downvote_canceled']))
- author.save()
-
- question = post
- if ContentType.objects.get_for_model(post) == answer_type:
- question = post.question
-
- reputation = Repute(user=author,
- positive=int(REPUTATION_RULES['gain_by_downvote_canceled']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=4,
- reputation=author.reputation)
- reputation.save()
-
- user.reputation = calculate_reputation(user.reputation,
- int(REPUTATION_RULES['gain_by_canceling_downvote']))
- user.save()
-
- reputation = Repute(user=user,
- positive=int(REPUTATION_RULES['gain_by_canceling_downvote']),
- question=question,
- reputed_at=datetime.datetime.now(),
- reputation_type=5,
- reputation=user.reputation)
- reputation.save()
-
-def onDeleteCanceled(post, user):
- post.deleted = False
- post.deleted_by = None
- post.deleted_at = None
- post.save()
- for tag in list(post.tags.all()):
- if tag.used_count == 1 and tag.deleted:
- tag.deleted = False
- tag.deleted_by = None
- tag.deleted_at = None
- tag.save()
-
-def onDeleted(post, user):
- post.deleted = True
- post.deleted_by = user
- post.deleted_at = datetime.datetime.now()
- post.save()
-
- for tag in list(post.tags.all()):
- if tag.used_count == 1:
- tag.deleted = True
- tag.deleted_by = user
- tag.deleted_at = datetime.datetime.now()
- tag.save()
+"""
+Authorisation related functions.
+
+The actions a User is authorised to perform are dependent on their reputation
+and superuser status.
+"""
+import datetime
+from django.contrib.contenttypes.models import ContentType
+from django.db import transaction
+from models import Repute
+from models import Question
+from models import Answer
+from const import TYPE_REPUTATION
+question_type = ContentType.objects.get_for_model(Question)
+answer_type = ContentType.objects.get_for_model(Answer)
+
+VOTE_UP = 15
+FLAG_OFFENSIVE = 15
+POST_IMAGES = 15
+LEAVE_COMMENTS = 50
+UPLOAD_FILES = 60
+VOTE_DOWN = 100
+CLOSE_OWN_QUESTIONS = 250
+RETAG_OTHER_QUESTIONS = 500
+REOPEN_OWN_QUESTIONS = 500
+EDIT_COMMUNITY_WIKI_POSTS = 750
+EDIT_OTHER_POSTS = 2000
+DELETE_COMMENTS = 2000
+VIEW_OFFENSIVE_FLAGS = 2000
+DISABLE_URL_NOFOLLOW = 2000
+CLOSE_OTHER_QUESTIONS = 3000
+LOCK_POSTS = 4000
+
+VOTE_RULES = {
+ 'scope_votes_per_user_per_day' : 30, # how many votes of one user has everyday
+ 'scope_flags_per_user_per_day' : 5, # how many times user can flag posts everyday
+ 'scope_warn_votes_left' : 10, # start when to warn user how many votes left
+ 'scope_deny_unvote_days' : 1, # if 1 days passed, user can't cancel votes.
+ 'scope_flags_invisible_main_page' : 3, # post doesn't show on main page if has more than 3 offensive flags
+ 'scope_flags_delete_post' : 5, # post will be deleted if it has more than 5 offensive flags
+}
+
+REPUTATION_RULES = {
+ 'initial_score' : 1,
+ 'scope_per_day_by_upvotes' : 200,
+ 'gain_by_upvoted' : 10,
+ 'gain_by_answer_accepted' : 15,
+ 'gain_by_accepting_answer' : 2,
+ 'gain_by_downvote_canceled' : 2,
+ 'gain_by_canceling_downvote' : 1,
+ 'lose_by_canceling_accepted_answer' : -2,
+ 'lose_by_accepted_answer_cancled' : -15,
+ 'lose_by_downvoted' : -2,
+ 'lose_by_flagged' : -2,
+ 'lose_by_downvoting' : -1,
+ 'lose_by_flagged_lastrevision_3_times': -30,
+ 'lose_by_flagged_lastrevision_5_times': -100,
+ 'lose_by_upvote_canceled' : -10,
+}
+
+def can_vote_up(user):
+ """Determines if a User can vote Questions and Answers up."""
+ return user.is_authenticated() and (
+ user.reputation >= VOTE_UP or
+ user.is_superuser)
+
+def can_flag_offensive(user):
+ """Determines if a User can flag Questions and Answers as offensive."""
+ return user.is_authenticated() and (
+ user.reputation >= FLAG_OFFENSIVE or
+ user.is_superuser)
+
+def can_add_comments(user):
+ """Determines if a User can add comments to Questions and Answers."""
+ return user.is_authenticated() and (
+ user.reputation >= LEAVE_COMMENTS or
+ user.is_superuser)
+
+def can_vote_down(user):
+ """Determines if a User can vote Questions and Answers down."""
+ return user.is_authenticated() and (
+ user.reputation >= VOTE_DOWN or
+ user.is_superuser)
+
+def can_retag_questions(user):
+ """Determines if a User can retag Questions."""
+ return user.is_authenticated() and (
+ RETAG_OTHER_QUESTIONS <= user.reputation < EDIT_OTHER_POSTS or
+ user.is_superuser)
+
+def can_edit_post(user, post):
+ """Determines if a User can edit the given Question or Answer."""
+ return user.is_authenticated() and (
+ user.id == post.author_id or
+ (post.wiki and user.reputation >= EDIT_COMMUNITY_WIKI_POSTS) or
+ user.reputation >= EDIT_OTHER_POSTS or
+ user.is_superuser)
+
+def can_delete_comment(user, comment):
+ """Determines if a User can delete the given Comment."""
+ return user.is_authenticated() and (
+ user.id == comment.user_id or
+ user.reputation >= DELETE_COMMENTS or
+ user.is_superuser)
+
+def can_view_offensive_flags(user):
+ """Determines if a User can view offensive flag counts."""
+ return user.is_authenticated() and (
+ user.reputation >= VIEW_OFFENSIVE_FLAGS or
+ user.is_superuser)
+
+def can_close_question(user, question):
+ """Determines if a User can close the given Question."""
+ return user.is_authenticated() and (
+ (user.id == question.author_id and
+ user.reputation >= CLOSE_OWN_QUESTIONS) or
+ user.reputation >= CLOSE_OTHER_QUESTIONS or
+ user.is_superuser)
+
+def can_lock_posts(user):
+ """Determines if a User can lock Questions or Answers."""
+ return user.is_authenticated() and (
+ user.reputation >= LOCK_POSTS or
+ user.is_superuser)
+
+def can_follow_url(user):
+ """Determines if the URL link can be followed by Google search engine."""
+ return user.reputation >= DISABLE_URL_NOFOLLOW
+
+def can_accept_answer(user, question, answer):
+ return (user.is_authenticated() and
+ question.author != answer.author and
+ question.author == user) or user.is_superuser
+
+# now only support to reopen own question except superuser
+def can_reopen_question(user, question):
+ return (user.is_authenticated() and
+ user.id == question.author_id and
+ user.reputation >= REOPEN_OWN_QUESTIONS) or user.is_superuser
+
+def can_delete_post(user, post):
+ return (user.is_authenticated() and
+ user.id == post.author_id) or user.is_superuser
+
+def can_view_deleted_post(user, post):
+ return user.is_superuser
+
+# user preferences view permissions
+def is_user_self(request_user, target_user):
+ return (request_user.is_authenticated() and request_user == target_user)
+
+def can_view_user_votes(request_user, target_user):
+ return (request_user.is_authenticated() and request_user == target_user)
+
+def can_view_user_preferences(request_user, target_user):
+ return (request_user.is_authenticated() and request_user == target_user)
+
+def can_view_user_edit(request_user, target_user):
+ return (request_user.is_authenticated() and request_user == target_user)
+
+def can_upload_files(request_user):
+ return (request_user.is_authenticated() and request_user.reputation >= UPLOAD_FILES) or \
+ request_user.is_superuser
+
+###########################################
+## actions and reputation changes event
+###########################################
+def calculate_reputation(origin, offset):
+ result = int(origin) + int(offset)
+ return result if result > 0 else 1
+
+@transaction.commit_on_success
+def onFlaggedItem(item, post, user):
+
+ item.save()
+ post.offensive_flag_count = post.offensive_flag_count + 1
+ post.save()
+
+ post.author.reputation = calculate_reputation(post.author.reputation,
+ int(REPUTATION_RULES['lose_by_flagged']))
+ post.author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=post.author,
+ negative=int(REPUTATION_RULES['lose_by_flagged']),
+ question=question, reputed_at=datetime.datetime.now(),
+ reputation_type=-4,
+ reputation=post.author.reputation)
+ reputation.save()
+
+ #todo: These should be updated to work on same revisions.
+ if post.offensive_flag_count == VOTE_RULES['scope_flags_invisible_main_page'] :
+ post.author.reputation = calculate_reputation(post.author.reputation,
+ int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']))
+ post.author.save()
+
+ reputation = Repute(user=post.author,
+ negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-6,
+ reputation=post.author.reputation)
+ reputation.save()
+
+ elif post.offensive_flag_count == VOTE_RULES['scope_flags_delete_post']:
+ post.author.reputation = calculate_reputation(post.author.reputation,
+ int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']))
+ post.author.save()
+
+ reputation = Repute(user=post.author,
+ negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-7,
+ reputation=post.author.reputation)
+ reputation.save()
+
+ post.deleted = True
+ #post.deleted_at = datetime.datetime.now()
+ #post.deleted_by = Admin
+ post.save()
+
+
+@transaction.commit_on_success
+def onAnswerAccept(answer, user):
+ answer.accepted = True
+ answer.accepted_at = datetime.datetime.now()
+ answer.question.answer_accepted = True
+ answer.save()
+ answer.question.save()
+
+ answer.author.reputation = calculate_reputation(answer.author.reputation,
+ int(REPUTATION_RULES['gain_by_answer_accepted']))
+ answer.author.save()
+ reputation = Repute(user=answer.author,
+ positive=int(REPUTATION_RULES['gain_by_answer_accepted']),
+ question=answer.question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=2,
+ reputation=answer.author.reputation)
+ reputation.save()
+
+ user.reputation = calculate_reputation(user.reputation,
+ int(REPUTATION_RULES['gain_by_accepting_answer']))
+ user.save()
+ reputation = Repute(user=user,
+ positive=int(REPUTATION_RULES['gain_by_accepting_answer']),
+ question=answer.question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=3,
+ reputation=user.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onAnswerAcceptCanceled(answer, user):
+ answer.accepted = False
+ answer.accepted_at = None
+ answer.question.answer_accepted = False
+ answer.save()
+ answer.question.save()
+
+ answer.author.reputation = calculate_reputation(answer.author.reputation,
+ int(REPUTATION_RULES['lose_by_accepted_answer_cancled']))
+ answer.author.save()
+ reputation = Repute(user=answer.author,
+ negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']),
+ question=answer.question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-2,
+ reputation=answer.author.reputation)
+ reputation.save()
+
+ user.reputation = calculate_reputation(user.reputation,
+ int(REPUTATION_RULES['lose_by_canceling_accepted_answer']))
+ user.save()
+ reputation = Repute(user=user,
+ negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']),
+ question=answer.question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-1,
+ reputation=user.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onUpVoted(vote, post, user):
+ vote.save()
+
+ post.vote_up_count = int(post.vote_up_count) + 1
+ post.score = int(post.score) + 1
+ post.save()
+
+ if not post.wiki:
+ author = post.author
+ if Repute.objects.get_reputation_by_upvoted_today(author) < int(REPUTATION_RULES['scope_per_day_by_upvotes']):
+ author.reputation = calculate_reputation(author.reputation,
+ int(REPUTATION_RULES['gain_by_upvoted']))
+ author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=author,
+ positive=int(REPUTATION_RULES['gain_by_upvoted']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=1,
+ reputation=author.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onUpVotedCanceled(vote, post, user):
+ vote.delete()
+
+ post.vote_up_count = int(post.vote_up_count) - 1
+ if post.vote_up_count < 0:
+ post.vote_up_count = 0
+ post.score = int(post.score) - 1
+ post.save()
+
+ if not post.wiki:
+ author = post.author
+ author.reputation = calculate_reputation(author.reputation,
+ int(REPUTATION_RULES['lose_by_upvote_canceled']))
+ author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=author,
+ negative=int(REPUTATION_RULES['lose_by_upvote_canceled']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-8,
+ reputation=author.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onDownVoted(vote, post, user):
+ vote.save()
+
+ post.vote_down_count = int(post.vote_down_count) + 1
+ post.score = int(post.score) - 1
+ post.save()
+
+ if not post.wiki:
+ author = post.author
+ author.reputation = calculate_reputation(author.reputation,
+ int(REPUTATION_RULES['lose_by_downvoted']))
+ author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=author,
+ negative=int(REPUTATION_RULES['lose_by_downvoted']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-3,
+ reputation=author.reputation)
+ reputation.save()
+
+ user.reputation = calculate_reputation(user.reputation,
+ int(REPUTATION_RULES['lose_by_downvoting']))
+ user.save()
+
+ reputation = Repute(user=user,
+ negative=int(REPUTATION_RULES['lose_by_downvoting']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-5,
+ reputation=user.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onDownVotedCanceled(vote, post, user):
+ vote.delete()
+
+ post.vote_down_count = int(post.vote_down_count) - 1
+ if post.vote_down_count < 0:
+ post.vote_down_count = 0
+ post.score = post.score + 1
+ post.save()
+
+ if not post.wiki:
+ author = post.author
+ author.reputation = calculate_reputation(author.reputation,
+ int(REPUTATION_RULES['gain_by_downvote_canceled']))
+ author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=author,
+ positive=int(REPUTATION_RULES['gain_by_downvote_canceled']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=4,
+ reputation=author.reputation)
+ reputation.save()
+
+ user.reputation = calculate_reputation(user.reputation,
+ int(REPUTATION_RULES['gain_by_canceling_downvote']))
+ user.save()
+
+ reputation = Repute(user=user,
+ positive=int(REPUTATION_RULES['gain_by_canceling_downvote']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=5,
+ reputation=user.reputation)
+ reputation.save()
+
+def onDeleteCanceled(post, user):
+ post.deleted = False
+ post.deleted_by = None
+ post.deleted_at = None
+ post.save()
+ for tag in list(post.tags.all()):
+ if tag.used_count == 1 and tag.deleted:
+ tag.deleted = False
+ tag.deleted_by = None
+ tag.deleted_at = None
+ tag.save()
+
+def onDeleted(post, user):
+ post.deleted = True
+ post.deleted_by = user
+ post.deleted_at = datetime.datetime.now()
+ post.save()
+
+ for tag in list(post.tags.all()):
+ if tag.used_count == 1:
+ tag.deleted = True
+ tag.deleted_by = user
+ tag.deleted_at = datetime.datetime.now()
+ tag.save()
diff --git a/forum/feed.py b/forum/feed.py
index a4218630..373f8a87 100644
--- a/forum/feed.py
+++ b/forum/feed.py
@@ -1,43 +1,43 @@
-#!/usr/bin/env python
-#encoding:utf-8
-#-------------------------------------------------------------------------------
-# Name: Syndication feed class for subsribtion
-# Purpose:
-#
-# Author: Mike
-#
-# Created: 29/01/2009
-# Copyright: (c) CNPROG.COM 2009
-# Licence: GPL V2
-#-------------------------------------------------------------------------------
-from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
-from django.utils.translation import ugettext as _
-from models import Question
-class RssLastestQuestionsFeed(Feed):
- title = _('site title') + _(' - ') + _('site slogan') + _(' - ')+ _('latest questions')
- #EDIT!!!
- link = 'http://where.com/questions/'
- description = _('meta site content')
- #ttl = 10
- copyright = _('copyright message')
-
- def item_link(self, item):
- return '/questions/%s/' % item.id
-
- def item_author_name(self, item):
- return item.author.username
-
- def item_author_link(self, item):
- return item.author.get_profile_url()
-
- def item_pubdate(self, item):
- return item.added_at
-
- def items(self, item):
- return Question.objects.filter(deleted=False).order_by('-added_at')[:30]
-
-def main():
- pass
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+#encoding:utf-8
+#-------------------------------------------------------------------------------
+# Name: Syndication feed class for subsribtion
+# Purpose:
+#
+# Author: Mike
+#
+# Created: 29/01/2009
+# Copyright: (c) CNPROG.COM 2009
+# Licence: GPL V2
+#-------------------------------------------------------------------------------
+from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
+from django.utils.translation import ugettext as _
+from models import Question
+import settings
+class RssLastestQuestionsFeed(Feed):
+ title = settings.APP_TITLE + _(' - ')+ _('latest questions')
+ link = settings.APP_URL + '/' + _('questions/')
+ description = settings.APP_DESCRIPTION
+ #ttl = 10
+ copyright = settings.APP_COPYRIGHT
+
+ def item_link(self, item):
+ return self.link + '%s/' % item.id
+
+ def item_author_name(self, item):
+ return item.author.username
+
+ def item_author_link(self, item):
+ return item.author.get_profile_url()
+
+ def item_pubdate(self, item):
+ return item.added_at
+
+ def items(self, item):
+ return Question.objects.filter(deleted=False).order_by('-added_at')[:30]
+
+def main():
+ pass
+
+if __name__ == '__main__':
+ main()
diff --git a/forum/forms.py b/forum/forms.py
index 1b811ad9..59d0d620 100644
--- a/forum/forms.py
+++ b/forum/forms.py
@@ -1,193 +1,209 @@
-import re
-from datetime import date
-from django import forms
-from models import *
-from const import *
-from django.utils.translation import ugettext as _
-
-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.initial = ''
-
- def clean(self, value):
- value = super(TagNamesField, self).clean(value)
- data = value.strip()
- if len(data) < 1:
- raise forms.ValidationError(_('tags are required'))
- list = data.split(' ')
- 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')
-
-
-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 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}))
- def __init__(self, question, *args, **kwargs):
- super(AnswerForm, self).__init__(*args, **kwargs)
- if question.wiki:
- self.fields['wiki'].initial = True
-
-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=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- 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['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']:
- 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'))
- else:
- return self.cleaned_data['email']
+import re
+from datetime import date
+from django import forms
+from models import *
+from const import *
+from django.utils.translation import ugettext as _
+
+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'))
+ list = data.split(' ')
+ 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')
+
+class EmailNotifyField(forms.BooleanField):
+ def __init__(self, *args, **kwargs):
+ super(EmailNotifyField, self).__init__(*args, **kwargs)
+ self.required = False
+
+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 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:
+ self.fields['wiki'].initial = True
+ if user.is_authenticated():
+ try:
+ feed = EmailFeed.objects.get(feed_id=question.id, subscriber_id=user.id)
+ if feed.subscriber == user and feed.content == question:
+ self.fields['email_notify'].initial = True
+ return
+ except EmailFeed.DoesNotExist:
+ pass
+ 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=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ 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['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']
diff --git a/forum/management/commands/once_award_badges.py b/forum/management/commands/once_award_badges.py
index 7074e3db..03982c79 100644
--- a/forum/management/commands/once_award_badges.py
+++ b/forum/management/commands/once_award_badges.py
@@ -1,348 +1,349 @@
-#!/usr/bin/env python
-#encoding:utf-8
-#-------------------------------------------------------------------------------
-# Name: Award badges command
-# Purpose: This is a command file croning in background process regularly to
-# query database and award badges for user's special acitivities.
-#
-# Author: Mike, Sailing
-#
-# Created: 18/01/2009
-# Copyright: (c) Mike 2009
-# Licence: GPL V2
-#-------------------------------------------------------------------------------
-
-from django.db import connection
-from django.shortcuts import get_object_or_404
-from django.contrib.contenttypes.models import ContentType
-
-from forum.models import *
-from forum.const import *
-from base_command import BaseCommand
-"""
-(1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0),
-(2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0),
-(3, '优秀回答', 3, '优秀回答', '回答好评10次以上', 1, 0),
-(4, '优秀问题', 3, '优秀问题', '问题好评10次以上', 1, 0),
-(5, '评论家', 3, '评论家', '评论10次以上', 0, 0),
-(6, '流行问题', 3, '流行问题', '问题的浏览量超过1000人次', 1, 0),
-(7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0),
-(8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0),
-(9, '批评家', 3, '批评家', '第一次反对票', 0, 0),
-(10, '小编', 3, '小编', '第一次编辑更新', 0, 0),
-(11, '村长', 3, '村长', '第一次重新标签', 0, 0),
-(12, '学者', 3, '学者', '第一次标记答案', 0, 0),
-(13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0),
-(14, '支持者', 3, '支持者', '第一次赞成票', 0, 0),
-(15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0),
-(16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0),
-(17, '自学成才', 3, '自学成才', '回答自己的问题并且有3个以上赞成票', 1, 0),
-(18, '最有价值回答', 1, '最有价值回答', '回答超过100次赞成票', 1, 0),
-(19, '最有价值问题', 1, '最有价值问题', '问题超过100次赞成票', 1, 0),
-(20, '万人迷', 1, '万人迷', '问题被100人以上收藏', 1, 0),
-(21, '著名问题', 1, '著名问题', '问题的浏览量超过10000人次', 1, 0),
-(22, 'alpha用户', 2, 'alpha用户', '内测期间的活跃用户', 0, 0),
-(23, '极好回答', 2, '极好回答', '回答超过25次赞成票', 1, 0),
-(24, '极好问题', 2, '极好问题', '问题超过25次赞成票', 1, 0),
-(25, '受欢迎问题', 2, '受欢迎问题', '问题被25人以上收藏', 1, 0),
-(26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0),
-(27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0),
-(28, '通才', 2, '通才', '在多个标签领域活跃', 0, 0),
-(29, '专家', 2, '专家', '在一个标签领域活跃出众', 0, 0),
-(30, '老鸟', 2, '老鸟', '活跃超过一年的用户', 0, 0),
-(31, '最受关注问题', 2, '最受关注问题', '问题的浏览量超过2500人次', 1, 0),
-(32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0),
-(33, 'beta用户', 2, 'beta用户', 'beta期间活跃参与', 0, 0),
-(34, '导师', 2, '导师', '被指定为最佳答案并且赞成票40以上', 1, 0),
-(35, '巫师', 2, '巫师', '在提问60天之后回答并且赞成票5次以上', 1, 0),
-(36, '分类专家', 2, '分类专家', '创建的标签被50个以上问题使用', 1, 0);
-
-
-TYPE_ACTIVITY_ASK_QUESTION=1
-TYPE_ACTIVITY_ANSWER=2
-TYPE_ACTIVITY_COMMENT_QUESTION=3
-TYPE_ACTIVITY_COMMENT_ANSWER=4
-TYPE_ACTIVITY_UPDATE_QUESTION=5
-TYPE_ACTIVITY_UPDATE_ANSWER=6
-TYPE_ACTIVITY_PRIZE=7
-TYPE_ACTIVITY_MARK_ANSWER=8
-TYPE_ACTIVITY_VOTE_UP=9
-TYPE_ACTIVITY_VOTE_DOWN=10
-TYPE_ACTIVITY_CANCEL_VOTE=11
-TYPE_ACTIVITY_DELETE_QUESTION=12
-TYPE_ACTIVITY_DELETE_ANSWER=13
-TYPE_ACTIVITY_MARK_OFFENSIVE=14
-TYPE_ACTIVITY_UPDATE_TAGS=15
-TYPE_ACTIVITY_FAVORITE=16
-TYPE_ACTIVITY_USER_FULL_UPDATED = 17
-"""
-
-BADGE_AWARD_TYPE_FIRST = {
- TYPE_ACTIVITY_MARK_OFFENSIVE : 7,
- TYPE_ACTIVITY_CANCEL_VOTE: 8,
- TYPE_ACTIVITY_VOTE_DOWN : 9,
- TYPE_ACTIVITY_UPDATE_QUESTION : 10,
- TYPE_ACTIVITY_UPDATE_ANSWER : 10,
- TYPE_ACTIVITY_UPDATE_TAGS : 11,
- TYPE_ACTIVITY_MARK_ANSWER : 12,
- TYPE_ACTIVITY_VOTE_UP : 14,
- TYPE_ACTIVITY_USER_FULL_UPDATED: 16
-
-}
-
-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
- finally:
- connection.close()
-
- def alpha_user(self):
- """
- Before Jan 25, 2009(Chinese New Year Eve and enter into Beta for CNProg), every registered user
- will be awarded the "Alpha" badge if he has any activities.
- """
- alpha_end_date = date(2009, 1, 25)
- if date.today() < alpha_end_date:
- badge = get_object_or_404(Badge, id=22)
- for user in User.objects.all():
- award = Award.objects.filter(user=user, badge=badge)
- if award and not badge.multiple:
- continue
- activities = Activity.objects.filter(user=user)
- if len(activities) > 0:
- new_award = Award(user=user, badge=badge)
- new_award.save()
-
- def beta_user(self):
- """
- Before Feb 25, 2009, every registered user
- will be awarded the "Beta" badge if he has any activities.
- """
- beta_end_date = date(2009, 2, 25)
- if date.today() < beta_end_date:
- badge = get_object_or_404(Badge, id=33)
- for user in User.objects.all():
- award = Award.objects.filter(user=user, badge=badge)
- if award and not badge.multiple:
- continue
- activities = Activity.objects.filter(user=user)
- if len(activities) > 0:
- new_award = Award(user=user, badge=badge)
- new_award.save()
-
- def first_type_award(self):
- """
- This will award below badges for users first behaviors:
-
- (7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0),
- (8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0),
- (9, '批评家', 3, '批评家', '第一次反对票', 0, 0),
- (10, '小编', 3, '小编', '第一次编辑更新', 0, 0),
- (11, '村长', 3, '村长', '第一次重新标签', 0, 0),
- (12, '学者', 3, '学者', '第一次标记答案', 0, 0),
- (14, '支持者', 3, '支持者', '第一次赞成票', 0, 0),
- (16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0),
- """
- activity_types = ','.join('%s' % item for item in BADGE_AWARD_TYPE_FIRST.keys())
- # ORDER BY user_id, activity_type
- query = "SELECT id, user_id, activity_type, content_type_id, object_id FROM activity WHERE is_auditted = 0 AND activity_type IN (%s) ORDER BY user_id, activity_type" % activity_types
-
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
- # collect activity_id in current process
- activity_ids = []
- last_user_id = 0
- last_activity_type = 0
- for row in rows:
- activity_ids.append(row[0])
- user_id = row[1]
- activity_type = row[2]
- content_type_id = row[3]
- object_id = row[4]
-
- # if the user and activity are same as the last, continue
- if user_id == last_user_id and activity_type == last_activity_type:
- continue;
-
- user = get_object_or_404(User, id=user_id)
- badge = get_object_or_404(Badge, id=BADGE_AWARD_TYPE_FIRST[activity_type])
- content_type = get_object_or_404(ContentType, id=content_type_id)
-
- count = Award.objects.filter(user=user, badge=badge).count()
- if count and not badge.multiple:
- continue
- else:
- # new award
- award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
- award.save()
-
- # set the current user_id and activity_type to last
- last_user_id = user_id
- last_activity_type = activity_type
-
- # update processed rows to auditted
- self.update_activities_auditted(cursor, activity_ids)
- finally:
- cursor.close()
-
- def first_ask_be_voted(self):
- """
- For user asked question and got first upvote, we award him following badge:
-
- (13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0),
- """
- query = "SELECT act.user_id, q.vote_up_count, act.object_id FROM " \
- "activity act, question q WHERE act.activity_type = %s AND " \
- "act.object_id = q.id AND " \
- "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 13)
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
-
- badge = get_object_or_404(Badge, id=13)
- content_type = ContentType.objects.get_for_model(Question)
- awarded_users = []
- for row in rows:
- user_id = row[0]
- vote_up_count = row[1]
- object_id = row[2]
- if vote_up_count > 0 and user_id not in awarded_users:
- user = get_object_or_404(User, id=user_id)
- award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
- award.save()
- awarded_users.append(user_id)
- finally:
- cursor.close()
-
- def first_answer_be_voted(self):
- """
- When user answerd questions and got first upvote, we award him following badge:
-
- (15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0),
- """
- query = "SELECT act.user_id, a.vote_up_count, act.object_id FROM " \
- "activity act, answer a WHERE act.activity_type = %s AND " \
- "act.object_id = a.id AND " \
- "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 15)
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
-
- awarded_users = []
- badge = get_object_or_404(Badge, id=15)
- content_type = ContentType.objects.get_for_model(Answer)
- for row in rows:
- user_id = row[0]
- vote_up_count = row[1]
- object_id = row[2]
- if vote_up_count > 0 and user_id not in awarded_users:
- user = get_object_or_404(User, id=user_id)
- award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
- award.save()
- awarded_users.append(user_id)
- finally:
- cursor.close()
-
- def first_answer_be_voted_10(self):
- """
- (32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0)
- """
- query = "SELECT act.user_id, act.object_id FROM " \
- "activity act, answer a WHERE act.object_id = a.id AND " \
- "act.activity_type = %s AND " \
- "a.vote_up_count >= 10 AND " \
- "act.user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 32)
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
-
- awarded_users = []
- badge = get_object_or_404(Badge, id=32)
- content_type = ContentType.objects.get_for_model(Answer)
- for row in rows:
- user_id = row[0]
- if user_id not in awarded_users:
- user = get_object_or_404(User, id=user_id)
- object_id = row[1]
- award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
- award.save()
- awarded_users.append(user_id)
- finally:
- cursor.close()
-
- def vote_count_300(self):
- """
- (26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0)
- """
- query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \
- "activity_type = %s OR " \
- "activity_type = %s AND " \
- "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \
- "GROUP BY user_id HAVING vote_count >= 300" % (TYPE_ACTIVITY_VOTE_UP, TYPE_ACTIVITY_VOTE_DOWN, 26)
-
- self.__award_for_count_num(query, 26)
-
- def edit_count_100(self):
- """
- (27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0)
- """
- query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \
- "activity_type = %s OR " \
- "activity_type = %s AND " \
- "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \
- "GROUP BY user_id HAVING vote_count >= 100" % (TYPE_ACTIVITY_UPDATE_QUESTION, TYPE_ACTIVITY_UPDATE_ANSWER, 27)
-
- self.__award_for_count_num(query, 27)
-
- def comment_count_10(self):
- """
- (5, '评论家', 3, '评论家', '评论10次以上', 0, 0),
- """
- query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \
- "activity_type = %s OR " \
- "activity_type = %s AND " \
- "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \
- "GROUP BY user_id HAVING vote_count >= 10" % (TYPE_ACTIVITY_COMMENT_QUESTION, TYPE_ACTIVITY_COMMENT_ANSWER, 5)
- self.__award_for_count_num(query, 5)
-
- def __award_for_count_num(self, query, badge):
- cursor = connection.cursor()
- try:
- cursor.execute(query)
- rows = cursor.fetchall()
-
- awarded_users = []
- badge = get_object_or_404(Badge, id=badge)
- for row in rows:
- vote_count = row[0]
- user_id = row[1]
-
- if user_id not in awarded_users:
- user = get_object_or_404(User, id=user_id)
- award = Award(user=user, badge=badge)
- award.save()
- awarded_users.append(user_id)
- finally:
- cursor.close()
-
-def main():
- pass
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+#encoding:utf-8
+#-------------------------------------------------------------------------------
+# Name: Award badges command
+# Purpose: This is a command file croning in background process regularly to
+# query database and award badges for user's special acitivities.
+#
+# Author: Mike, Sailing
+#
+# Created: 18/01/2009
+# Copyright: (c) Mike 2009
+# Licence: GPL V2
+#-------------------------------------------------------------------------------
+
+from datetime import datetime, date
+from django.db import connection
+from django.shortcuts import get_object_or_404
+from django.contrib.contenttypes.models import ContentType
+
+from forum.models import *
+from forum.const import *
+from base_command import BaseCommand
+"""
+(1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0),
+(2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0),
+(3, '优秀回答', 3, '优秀回答', '回答好评10次以上', 1, 0),
+(4, '优秀问题', 3, '优秀问题', '问题好评10次以上', 1, 0),
+(5, '评论家', 3, '评论家', '评论10次以上', 0, 0),
+(6, '流行问题', 3, '流行问题', '问题的浏览量超过1000人次', 1, 0),
+(7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0),
+(8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0),
+(9, '批评家', 3, '批评家', '第一次反对票', 0, 0),
+(10, '小编', 3, '小编', '第一次编辑更新', 0, 0),
+(11, '村长', 3, '村长', '第一次重新标签', 0, 0),
+(12, '学者', 3, '学者', '第一次标记答案', 0, 0),
+(13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0),
+(14, '支持者', 3, '支持者', '第一次赞成票', 0, 0),
+(15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0),
+(16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0),
+(17, '自学成才', 3, '自学成才', '回答自己的问题并且有3个以上赞成票', 1, 0),
+(18, '最有价值回答', 1, '最有价值回答', '回答超过100次赞成票', 1, 0),
+(19, '最有价值问题', 1, '最有价值问题', '问题超过100次赞成票', 1, 0),
+(20, '万人迷', 1, '万人迷', '问题被100人以上收藏', 1, 0),
+(21, '著名问题', 1, '著名问题', '问题的浏览量超过10000人次', 1, 0),
+(22, 'alpha用户', 2, 'alpha用户', '内测期间的活跃用户', 0, 0),
+(23, '极好回答', 2, '极好回答', '回答超过25次赞成票', 1, 0),
+(24, '极好问题', 2, '极好问题', '问题超过25次赞成票', 1, 0),
+(25, '受欢迎问题', 2, '受欢迎问题', '问题被25人以上收藏', 1, 0),
+(26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0),
+(27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0),
+(28, '通才', 2, '通才', '在多个标签领域活跃', 0, 0),
+(29, '专家', 2, '专家', '在一个标签领域活跃出众', 0, 0),
+(30, '老鸟', 2, '老鸟', '活跃超过一年的用户', 0, 0),
+(31, '最受关注问题', 2, '最受关注问题', '问题的浏览量超过2500人次', 1, 0),
+(32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0),
+(33, 'beta用户', 2, 'beta用户', 'beta期间活跃参与', 0, 0),
+(34, '导师', 2, '导师', '被指定为最佳答案并且赞成票40以上', 1, 0),
+(35, '巫师', 2, '巫师', '在提问60天之后回答并且赞成票5次以上', 1, 0),
+(36, '分类专家', 2, '分类专家', '创建的标签被50个以上问题使用', 1, 0);
+
+
+TYPE_ACTIVITY_ASK_QUESTION=1
+TYPE_ACTIVITY_ANSWER=2
+TYPE_ACTIVITY_COMMENT_QUESTION=3
+TYPE_ACTIVITY_COMMENT_ANSWER=4
+TYPE_ACTIVITY_UPDATE_QUESTION=5
+TYPE_ACTIVITY_UPDATE_ANSWER=6
+TYPE_ACTIVITY_PRIZE=7
+TYPE_ACTIVITY_MARK_ANSWER=8
+TYPE_ACTIVITY_VOTE_UP=9
+TYPE_ACTIVITY_VOTE_DOWN=10
+TYPE_ACTIVITY_CANCEL_VOTE=11
+TYPE_ACTIVITY_DELETE_QUESTION=12
+TYPE_ACTIVITY_DELETE_ANSWER=13
+TYPE_ACTIVITY_MARK_OFFENSIVE=14
+TYPE_ACTIVITY_UPDATE_TAGS=15
+TYPE_ACTIVITY_FAVORITE=16
+TYPE_ACTIVITY_USER_FULL_UPDATED = 17
+"""
+
+BADGE_AWARD_TYPE_FIRST = {
+ TYPE_ACTIVITY_MARK_OFFENSIVE : 7,
+ TYPE_ACTIVITY_CANCEL_VOTE: 8,
+ TYPE_ACTIVITY_VOTE_DOWN : 9,
+ TYPE_ACTIVITY_UPDATE_QUESTION : 10,
+ TYPE_ACTIVITY_UPDATE_ANSWER : 10,
+ TYPE_ACTIVITY_UPDATE_TAGS : 11,
+ TYPE_ACTIVITY_MARK_ANSWER : 12,
+ TYPE_ACTIVITY_VOTE_UP : 14,
+ TYPE_ACTIVITY_USER_FULL_UPDATED: 16
+
+}
+
+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
+ finally:
+ connection.close()
+
+ def alpha_user(self):
+ """
+ Before Jan 25, 2009(Chinese New Year Eve and enter into Beta for CNProg), every registered user
+ will be awarded the "Alpha" badge if he has any activities.
+ """
+ alpha_end_date = date(2009, 1, 25)
+ if date.today() < alpha_end_date:
+ badge = get_object_or_404(Badge, id=22)
+ for user in User.objects.all():
+ award = Award.objects.filter(user=user, badge=badge)
+ if award and not badge.multiple:
+ continue
+ activities = Activity.objects.filter(user=user)
+ if len(activities) > 0:
+ new_award = Award(user=user, badge=badge)
+ new_award.save()
+
+ def beta_user(self):
+ """
+ Before Feb 25, 2009, every registered user
+ will be awarded the "Beta" badge if he has any activities.
+ """
+ beta_end_date = date(2009, 2, 25)
+ if date.today() < beta_end_date:
+ badge = get_object_or_404(Badge, id=33)
+ for user in User.objects.all():
+ award = Award.objects.filter(user=user, badge=badge)
+ if award and not badge.multiple:
+ continue
+ activities = Activity.objects.filter(user=user)
+ if len(activities) > 0:
+ new_award = Award(user=user, badge=badge)
+ new_award.save()
+
+ def first_type_award(self):
+ """
+ This will award below badges for users first behaviors:
+
+ (7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0),
+ (8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0),
+ (9, '批评家', 3, '批评家', '第一次反对票', 0, 0),
+ (10, '小编', 3, '小编', '第一次编辑更新', 0, 0),
+ (11, '村长', 3, '村长', '第一次重新标签', 0, 0),
+ (12, '学者', 3, '学者', '第一次标记答案', 0, 0),
+ (14, '支持者', 3, '支持者', '第一次赞成票', 0, 0),
+ (16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0),
+ """
+ activity_types = ','.join('%s' % item for item in BADGE_AWARD_TYPE_FIRST.keys())
+ # ORDER BY user_id, activity_type
+ query = "SELECT id, user_id, activity_type, content_type_id, object_id FROM activity WHERE is_auditted = 0 AND activity_type IN (%s) ORDER BY user_id, activity_type" % activity_types
+
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+ # collect activity_id in current process
+ activity_ids = []
+ last_user_id = 0
+ last_activity_type = 0
+ for row in rows:
+ activity_ids.append(row[0])
+ user_id = row[1]
+ activity_type = row[2]
+ content_type_id = row[3]
+ object_id = row[4]
+
+ # if the user and activity are same as the last, continue
+ if user_id == last_user_id and activity_type == last_activity_type:
+ continue;
+
+ user = get_object_or_404(User, id=user_id)
+ badge = get_object_or_404(Badge, id=BADGE_AWARD_TYPE_FIRST[activity_type])
+ content_type = get_object_or_404(ContentType, id=content_type_id)
+
+ count = Award.objects.filter(user=user, badge=badge).count()
+ if count and not badge.multiple:
+ continue
+ else:
+ # new award
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+
+ # set the current user_id and activity_type to last
+ last_user_id = user_id
+ last_activity_type = activity_type
+
+ # update processed rows to auditted
+ self.update_activities_auditted(cursor, activity_ids)
+ finally:
+ cursor.close()
+
+ def first_ask_be_voted(self):
+ """
+ For user asked question and got first upvote, we award him following badge:
+
+ (13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0),
+ """
+ query = "SELECT act.user_id, q.vote_up_count, act.object_id FROM " \
+ "activity act, question q WHERE act.activity_type = %s AND " \
+ "act.object_id = q.id AND " \
+ "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 13)
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ badge = get_object_or_404(Badge, id=13)
+ content_type = ContentType.objects.get_for_model(Question)
+ awarded_users = []
+ for row in rows:
+ user_id = row[0]
+ vote_up_count = row[1]
+ object_id = row[2]
+ if vote_up_count > 0 and user_id not in awarded_users:
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+ awarded_users.append(user_id)
+ finally:
+ cursor.close()
+
+ def first_answer_be_voted(self):
+ """
+ When user answerd questions and got first upvote, we award him following badge:
+
+ (15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0),
+ """
+ query = "SELECT act.user_id, a.vote_up_count, act.object_id FROM " \
+ "activity act, answer a WHERE act.activity_type = %s AND " \
+ "act.object_id = a.id AND " \
+ "act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 15)
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ awarded_users = []
+ badge = get_object_or_404(Badge, id=15)
+ content_type = ContentType.objects.get_for_model(Answer)
+ for row in rows:
+ user_id = row[0]
+ vote_up_count = row[1]
+ object_id = row[2]
+ if vote_up_count > 0 and user_id not in awarded_users:
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+ awarded_users.append(user_id)
+ finally:
+ cursor.close()
+
+ def first_answer_be_voted_10(self):
+ """
+ (32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0)
+ """
+ query = "SELECT act.user_id, act.object_id FROM " \
+ "activity act, answer a WHERE act.object_id = a.id AND " \
+ "act.activity_type = %s AND " \
+ "a.vote_up_count >= 10 AND " \
+ "act.user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 32)
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ awarded_users = []
+ badge = get_object_or_404(Badge, id=32)
+ content_type = ContentType.objects.get_for_model(Answer)
+ for row in rows:
+ user_id = row[0]
+ if user_id not in awarded_users:
+ user = get_object_or_404(User, id=user_id)
+ object_id = row[1]
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+ awarded_users.append(user_id)
+ finally:
+ cursor.close()
+
+ def vote_count_300(self):
+ """
+ (26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0)
+ """
+ query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \
+ "activity_type = %s OR " \
+ "activity_type = %s AND " \
+ "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \
+ "GROUP BY user_id HAVING vote_count >= 300" % (TYPE_ACTIVITY_VOTE_UP, TYPE_ACTIVITY_VOTE_DOWN, 26)
+
+ self.__award_for_count_num(query, 26)
+
+ def edit_count_100(self):
+ """
+ (27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0)
+ """
+ query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \
+ "activity_type = %s OR " \
+ "activity_type = %s AND " \
+ "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \
+ "GROUP BY user_id HAVING vote_count >= 100" % (TYPE_ACTIVITY_UPDATE_QUESTION, TYPE_ACTIVITY_UPDATE_ANSWER, 27)
+
+ self.__award_for_count_num(query, 27)
+
+ def comment_count_10(self):
+ """
+ (5, '评论家', 3, '评论家', '评论10次以上', 0, 0),
+ """
+ query = "SELECT count(*) vote_count, user_id FROM activity WHERE " \
+ "activity_type = %s OR " \
+ "activity_type = %s AND " \
+ "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \
+ "GROUP BY user_id HAVING vote_count >= 10" % (TYPE_ACTIVITY_COMMENT_QUESTION, TYPE_ACTIVITY_COMMENT_ANSWER, 5)
+ self.__award_for_count_num(query, 5)
+
+ def __award_for_count_num(self, query, badge):
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ awarded_users = []
+ badge = get_object_or_404(Badge, id=badge)
+ for row in rows:
+ vote_count = row[0]
+ user_id = row[1]
+
+ if user_id not in awarded_users:
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge)
+ award.save()
+ awarded_users.append(user_id)
+ finally:
+ cursor.close()
+
+def main():
+ pass
+
+if __name__ == '__main__':
+ main()
diff --git a/forum/management/commands/send_email_alerts.py b/forum/management/commands/send_email_alerts.py
new file mode 100644
index 00000000..3c37aaa3
--- /dev/null
+++ b/forum/management/commands/send_email_alerts.py
@@ -0,0 +1,41 @@
+from django.core.management.base import NoArgsCommand
+from django.db import connection
+from forum.models import *
+import collections
+from django.core.mail import EmailMessage
+from django.utils.translation import ugettext as _
+import settings
+
+class Command(NoArgsCommand):
+ def handle_noargs(self,**options):
+ try:
+ self.send_email_alerts()
+ except Exception, e:
+ print e
+ finally:
+ connection.close()
+
+ def send_email_alerts(self):
+ report_time = datetime.datetime.now()
+ feeds = EmailFeed.objects.all()
+ user_ctype = ContentType.objects.get_for_model(User)
+
+ #lists of update messages keyed by email address
+ update_collection = collections.defaultdict(list)
+ for feed in feeds:
+ update_summary = feed.get_update_summary()
+ if update_summary != None:
+ email = feed.get_email()
+ update_collection[email].append(update_summary)
+ feed.reported_at = report_time
+ feed.save()
+
+ for email, updates in update_collection.items():
+ text = '\n'.join(updates)
+ subject = _('updates from website')
+ print 'sent %s to %s' % (updates,email)
+ msg = EmailMessage(subject, text, settings.DEFAULT_FROM_EMAIL, [email])
+ msg.content_subtype = 'html'
+ msg.send()
+
+
diff --git a/forum/managers.py b/forum/managers.py
index 0f22c59c..2e3e4186 100644
--- a/forum/managers.py
+++ b/forum/managers.py
@@ -1,259 +1,259 @@
-import datetime
-import logging
-from django.contrib.auth.models import User, UserManager
-from django.db import connection, models, transaction
-from django.db.models import Q
-from forum.models import *
-from urllib import quote, unquote
-
-class QuestionManager(models.Manager):
- def get_translation_questions(self, orderby, page_size):
- questions = self.filter(deleted=False, author__id__in=[28,29]).order_by(orderby)[:page_size]
- return questions
-
- def get_questions_by_pagesize(self, orderby, page_size):
- questions = self.filter(deleted=False).order_by(orderby)[:page_size]
- return questions
-
- def get_questions_by_tag(self, tagname, orderby):
- questions = self.filter(deleted=False, tags__name = unquote(tagname)).order_by(orderby)
- return questions
-
- def get_unanswered_questions(self, orderby):
- questions = self.filter(deleted=False, answer_count=0).order_by(orderby)
- return questions
-
- def get_questions(self, orderby):
- questions = self.filter(deleted=False).order_by(orderby)
- return questions
-
- def update_tags(self, question, tagnames, user):
- """
- Updates Tag associations for a question to match the given
- tagname string.
-
- Returns ``True`` if tag usage counts were updated as a result,
- ``False`` otherwise.
- """
- from forum.models import Tag
- current_tags = list(question.tags.all())
- current_tagnames = set(t.name for t in current_tags)
- updated_tagnames = set(t for t in tagnames.split(' ') if t)
- modified_tags = []
-
- removed_tags = [t for t in current_tags
- if t.name not in updated_tagnames]
- if removed_tags:
- modified_tags.extend(removed_tags)
- question.tags.remove(*removed_tags)
-
- added_tagnames = updated_tagnames - current_tagnames
- if added_tagnames:
- added_tags = Tag.objects.get_or_create_multiple(added_tagnames,
- user)
- modified_tags.extend(added_tags)
- question.tags.add(*added_tags)
-
- if modified_tags:
- Tag.objects.update_use_counts(modified_tags)
- return True
-
- return False
-
- def update_answer_count(self, question):
- """
- Executes an UPDATE query to update denormalised data with the
- number of answers the given question has.
- """
-
- # for some reasons, this Answer class failed to be imported,
- # although we have imported all classes from models on top.
- from forum.models import Answer
- self.filter(id=question.id).update(
- answer_count=Answer.objects.get_answers_from_question(question).count())
-
- def update_view_count(self, question):
- """
- update counter+1 when user browse question page
- """
- self.filter(id=question.id).update(view_count = question.view_count + 1)
-
- def update_favorite_count(self, question):
- """
- update favourite_count for given question
- """
- from forum.models import FavoriteQuestion
- self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count())
-
- def get_similar_questions(self, question):
- """
- Get 10 similar questions for given one.
- This will search the same tag list for give question(by exactly same string) first.
- Questions with the individual tags will be added to list if above questions are not full.
- """
- #print datetime.datetime.now()
- from forum.models import Question
- questions = list(Question.objects.filter(tagnames = question.tagnames).all())
-
- tags_list = question.tags.all()
- for tag in tags_list:
- extend_questions = Question.objects.filter(tags__id = tag.id)[:50]
- for item in extend_questions:
- if item not in questions and len(questions) < 10:
- questions.append(item)
-
- #print datetime.datetime.now()
- return questions
-
-class TagManager(models.Manager):
- UPDATE_USED_COUNTS_QUERY = (
- 'UPDATE tag '
- 'SET used_count = ('
- 'SELECT COUNT(*) FROM question_tags '
- 'WHERE tag_id = tag.id'
- ') '
- 'WHERE id IN (%s)')
-
- def get_valid_tags(self, page_size):
- from forum.models import Tag
- tags = Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-id")[:page_size]
- return tags
-
- def get_or_create_multiple(self, names, user):
- """
- Fetches a list of Tags with the given names, creating any Tags
- which don't exist when necesssary.
- """
- tags = list(self.filter(name__in=names))
- #Set all these tag visible
- for tag in tags:
- if tag.deleted:
- tag.deleted = False
- tag.deleted_by = None
- tag.deleted_at = None
- tag.save()
-
- if len(tags) < len(names):
- existing_names = set(tag.name for tag in tags)
- new_names = [name for name in names if name not in existing_names]
- tags.extend([self.create(name=name, created_by=user)
- for name in new_names if self.filter(name=name).count() == 0 and len(name.strip()) > 0])
-
- return tags
-
- def update_use_counts(self, tags):
- """Updates the given Tags with their current use counts."""
- if not tags:
- return
- cursor = connection.cursor()
- query = self.UPDATE_USED_COUNTS_QUERY % ','.join(['%s'] * len(tags))
- cursor.execute(query, [tag.id for tag in tags])
- transaction.commit_unless_managed()
-
- def get_tags_by_questions(self, questions):
- question_ids = []
- for question in questions:
- question_ids.append(question.id)
-
- question_ids_str = ','.join([str(id) for id in question_ids])
- related_tags = self.extra(
- tables=['tag', 'question_tags'],
- where=["tag.id = question_tags.tag_id AND question_tags.question_id IN (" + question_ids_str + ")"]
- ).distinct()
-
- return related_tags
-
-class AnswerManager(models.Manager):
- GET_ANSWERS_FROM_USER_QUESTIONS = u'SELECT answer.* FROM answer INNER JOIN question ON answer.question_id = question.id WHERE question.author_id =%s AND answer.author_id <> %s'
- def get_answers_from_question(self, question, user=None):
- """
- Retrieves visibile answers for the given question. Delete answers
- are only visibile to the person who deleted them.
- """
-
- if user is None or not user.is_authenticated():
- return self.filter(question=question, deleted=False)
- else:
- return self.filter(Q(question=question),
- Q(deleted=False) | Q(deleted_by=user))
-
- def get_answers_from_questions(self, user_id):
- """
- Retrieves visibile answers for the given question. Which are not included own answers
- """
- cursor = connection.cursor()
- cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id])
- return cursor.fetchall()
-
-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())"
- def get_up_vote_count_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_UP_VOTE_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
- else:
- return 0
-
- def get_down_vote_count_from_user(self, user):
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_DOWN_VOTE_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
- else:
- return 0
-
- 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])
- row = cursor.fetchone()
- return row[0]
-
- else:
- 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())"
- 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])
- row = cursor.fetchone()
- return row[0]
-
- else:
- 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())"
- def get_reputation_by_upvoted_today(self, user):
- """
- For one user in one day, he can only earn rep till certain score (ep. +200)
- by upvoted(also substracted from upvoted canceled). This is because we need
- to prohibit gaming system by upvoting/cancel again and again.
- """
- if user is not None:
- cursor = connection.cursor()
- cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id])
- row = cursor.fetchone()
- return row[0]
-
- else:
- return 0
-class AwardManager(models.Manager):
- def get_recent_awards(self):
- awards = super(AwardManager, self).extra(
- select={'badge_id': 'badge.id', 'badge_name':'badge.name',
- 'badge_description': 'badge.description', 'badge_type': 'badge.type',
- 'user_id': 'auth_user.id', 'user_name': 'auth_user.username'
- },
- tables=['award', 'badge', 'auth_user'],
- order_by=['-awarded_at'],
- where=['auth_user.id=award.user_id AND badge_id=badge.id'],
- ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name')
- return awards
+import datetime
+import logging
+from django.contrib.auth.models import User, UserManager
+from django.db import connection, models, transaction
+from django.db.models import Q
+from forum.models import *
+from urllib import quote, unquote
+
+class QuestionManager(models.Manager):
+ def get_translation_questions(self, orderby, page_size):
+ questions = self.filter(deleted=False, author__id__in=[28,29]).order_by(orderby)[:page_size]
+ return questions
+
+ def get_questions_by_pagesize(self, orderby, page_size):
+ questions = self.filter(deleted=False).order_by(orderby)[:page_size]
+ return questions
+
+ def get_questions_by_tag(self, tagname, orderby):
+ questions = self.filter(deleted=False, tags__name = unquote(tagname)).order_by(orderby)
+ return questions
+
+ def get_unanswered_questions(self, orderby):
+ questions = self.filter(deleted=False, answer_count=0).order_by(orderby)
+ return questions
+
+ def get_questions(self, orderby):
+ questions = self.filter(deleted=False).order_by(orderby)
+ return questions
+
+ def update_tags(self, question, tagnames, user):
+ """
+ Updates Tag associations for a question to match the given
+ tagname string.
+
+ Returns ``True`` if tag usage counts were updated as a result,
+ ``False`` otherwise.
+ """
+ from forum.models import Tag
+ current_tags = list(question.tags.all())
+ current_tagnames = set(t.name for t in current_tags)
+ updated_tagnames = set(t for t in tagnames.split(' ') if t)
+ modified_tags = []
+
+ removed_tags = [t for t in current_tags
+ if t.name not in updated_tagnames]
+ if removed_tags:
+ modified_tags.extend(removed_tags)
+ question.tags.remove(*removed_tags)
+
+ added_tagnames = updated_tagnames - current_tagnames
+ if added_tagnames:
+ added_tags = Tag.objects.get_or_create_multiple(added_tagnames,
+ user)
+ modified_tags.extend(added_tags)
+ question.tags.add(*added_tags)
+
+ if modified_tags:
+ Tag.objects.update_use_counts(modified_tags)
+ return True
+
+ return False
+
+ def update_answer_count(self, question):
+ """
+ Executes an UPDATE query to update denormalised data with the
+ number of answers the given question has.
+ """
+
+ # for some reasons, this Answer class failed to be imported,
+ # although we have imported all classes from models on top.
+ from forum.models import Answer
+ self.filter(id=question.id).update(
+ answer_count=Answer.objects.get_answers_from_question(question).count())
+
+ def update_view_count(self, question):
+ """
+ update counter+1 when user browse question page
+ """
+ self.filter(id=question.id).update(view_count = question.view_count + 1)
+
+ def update_favorite_count(self, question):
+ """
+ update favourite_count for given question
+ """
+ from forum.models import FavoriteQuestion
+ self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count())
+
+ def get_similar_questions(self, question):
+ """
+ Get 10 similar questions for given one.
+ This will search the same tag list for give question(by exactly same string) first.
+ Questions with the individual tags will be added to list if above questions are not full.
+ """
+ #print datetime.datetime.now()
+ from forum.models import Question
+ questions = list(Question.objects.filter(tagnames = question.tagnames).all())
+
+ tags_list = question.tags.all()
+ for tag in tags_list:
+ extend_questions = Question.objects.filter(tags__id = tag.id)[:50]
+ for item in extend_questions:
+ if item not in questions and len(questions) < 10:
+ questions.append(item)
+
+ #print datetime.datetime.now()
+ return questions
+
+class TagManager(models.Manager):
+ UPDATE_USED_COUNTS_QUERY = (
+ 'UPDATE tag '
+ 'SET used_count = ('
+ 'SELECT COUNT(*) FROM question_tags '
+ 'WHERE tag_id = tag.id'
+ ') '
+ 'WHERE id IN (%s)')
+
+ def get_valid_tags(self, page_size):
+ from forum.models import Tag
+ tags = Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-id")[:page_size]
+ return tags
+
+ def get_or_create_multiple(self, names, user):
+ """
+ Fetches a list of Tags with the given names, creating any Tags
+ which don't exist when necesssary.
+ """
+ tags = list(self.filter(name__in=names))
+ #Set all these tag visible
+ for tag in tags:
+ if tag.deleted:
+ tag.deleted = False
+ tag.deleted_by = None
+ tag.deleted_at = None
+ tag.save()
+
+ if len(tags) < len(names):
+ existing_names = set(tag.name for tag in tags)
+ new_names = [name for name in names if name not in existing_names]
+ tags.extend([self.create(name=name, created_by=user)
+ for name in new_names if self.filter(name=name).count() == 0 and len(name.strip()) > 0])
+
+ return tags
+
+ def update_use_counts(self, tags):
+ """Updates the given Tags with their current use counts."""
+ if not tags:
+ return
+ cursor = connection.cursor()
+ query = self.UPDATE_USED_COUNTS_QUERY % ','.join(['%s'] * len(tags))
+ cursor.execute(query, [tag.id for tag in tags])
+ transaction.commit_unless_managed()
+
+ def get_tags_by_questions(self, questions):
+ question_ids = []
+ for question in questions:
+ question_ids.append(question.id)
+
+ question_ids_str = ','.join([str(id) for id in question_ids])
+ related_tags = self.extra(
+ tables=['tag', 'question_tags'],
+ where=["tag.id = question_tags.tag_id AND question_tags.question_id IN (" + question_ids_str + ")"]
+ ).distinct()
+
+ return related_tags
+
+class AnswerManager(models.Manager):
+ GET_ANSWERS_FROM_USER_QUESTIONS = u'SELECT answer.* FROM answer INNER JOIN question ON answer.question_id = question.id WHERE question.author_id =%s AND answer.author_id <> %s'
+ def get_answers_from_question(self, question, user=None):
+ """
+ Retrieves visibile answers for the given question. Delete answers
+ are only visibile to the person who deleted them.
+ """
+
+ if user is None or not user.is_authenticated():
+ return self.filter(question=question, deleted=False)
+ else:
+ return self.filter(Q(question=question),
+ Q(deleted=False) | Q(deleted_by=user))
+
+ def get_answers_from_questions(self, user_id):
+ """
+ Retrieves visibile answers for the given question. Which are not included own answers
+ """
+ cursor = connection.cursor()
+ cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id])
+ return cursor.fetchall()
+
+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())"
+ def get_up_vote_count_from_user(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_UP_VOTE_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+ else:
+ return 0
+
+ def get_down_vote_count_from_user(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_DOWN_VOTE_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+ else:
+ return 0
+
+ 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])
+ row = cursor.fetchone()
+ return row[0]
+
+ else:
+ 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())"
+ 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])
+ row = cursor.fetchone()
+ return row[0]
+
+ else:
+ 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())"
+ def get_reputation_by_upvoted_today(self, user):
+ """
+ For one user in one day, he can only earn rep till certain score (ep. +200)
+ by upvoted(also substracted from upvoted canceled). This is because we need
+ to prohibit gaming system by upvoting/cancel again and again.
+ """
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+
+ else:
+ return 0
+class AwardManager(models.Manager):
+ def get_recent_awards(self):
+ awards = super(AwardManager, self).extra(
+ select={'badge_id': 'badge.id', 'badge_name':'badge.name',
+ 'badge_description': 'badge.description', 'badge_type': 'badge.type',
+ 'user_id': 'auth_user.id', 'user_name': 'auth_user.username'
+ },
+ tables=['award', 'badge', 'auth_user'],
+ order_by=['-awarded_at'],
+ where=['auth_user.id=award.user_id AND badge_id=badge.id'],
+ ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name')
+ return awards
diff --git a/forum/models.py b/forum/models.py
index 570db274..255eb21f 100644
--- a/forum/models.py
+++ b/forum/models.py
@@ -1,654 +1,808 @@
-# encoding:utf-8
-import datetime
-import hashlib
-from urllib import quote_plus, urlencode
-from django.db import models
-from django.utils.html import strip_tags
-from django.core.urlresolvers import reverse
-from django.contrib.auth.models import User
-from django.contrib.contenttypes import generic
-from django.contrib.contenttypes.models import ContentType
-from django.template.defaultfilters import slugify
-from django.db.models.signals import post_delete, post_save, pre_save
-from django.utils.translation import ugettext as _
-import django.dispatch
-
-from forum.managers import *
-from const import *
-
-class Tag(models.Model):
- name = models.CharField(max_length=255, unique=True)
- created_by = models.ForeignKey(User, related_name='created_tags')
- deleted = models.BooleanField(default=False)
- deleted_at = models.DateTimeField(null=True, blank=True)
- deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_tags')
- # Denormalised data
- used_count = models.PositiveIntegerField(default=0)
-
- objects = TagManager()
-
- class Meta:
- db_table = u'tag'
- ordering = ('-used_count', 'name')
-
- def __unicode__(self):
- return self.name
-
-class Comment(models.Model):
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- user = models.ForeignKey(User, related_name='comments')
- comment = models.CharField(max_length=300)
- added_at = models.DateTimeField(default=datetime.datetime.now)
-
- class Meta:
- ordering = ('-added_at',)
- db_table = u'comment'
- def __unicode__(self):
- return self.comment
-
-class Vote(models.Model):
- VOTE_UP = +1
- VOTE_DOWN = -1
- VOTE_CHOICES = (
- (VOTE_UP, u'Up'),
- (VOTE_DOWN, u'Down'),
- )
-
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- user = models.ForeignKey(User, related_name='votes')
- vote = models.SmallIntegerField(choices=VOTE_CHOICES)
- voted_at = models.DateTimeField(default=datetime.datetime.now)
-
- objects = VoteManager()
-
- class Meta:
- unique_together = ('content_type', 'object_id', 'user')
- db_table = u'vote'
- def __unicode__(self):
- return '[%s] voted at %s: %s' %(self.user, self.voted_at, self.vote)
-
- def is_upvote(self):
- return self.vote == self.VOTE_UP
-
- def is_downvote(self):
- return self.vote == self.VOTE_DOWN
-
-class FlaggedItem(models.Model):
- """A flag on a Question or Answer indicating offensive content."""
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- user = models.ForeignKey(User, related_name='flagged_items')
- flagged_at = models.DateTimeField(default=datetime.datetime.now)
-
- objects = FlaggedItemManager()
-
- class Meta:
- unique_together = ('content_type', 'object_id', 'user')
- db_table = u'flagged_item'
- def __unicode__(self):
- return '[%s] flagged at %s' %(self.user, self.flagged_at)
-
-class Question(models.Model):
- title = models.CharField(max_length=300)
- author = models.ForeignKey(User, related_name='questions')
- added_at = models.DateTimeField(default=datetime.datetime.now)
- tags = models.ManyToManyField(Tag, related_name='questions')
- # Status
- wiki = models.BooleanField(default=False)
- wikified_at = models.DateTimeField(null=True, blank=True)
- answer_accepted = models.BooleanField(default=False)
- closed = models.BooleanField(default=False)
- closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions')
- closed_at = models.DateTimeField(null=True, blank=True)
- close_reason = models.SmallIntegerField(choices=CLOSE_REASONS, null=True, blank=True)
- deleted = models.BooleanField(default=False)
- deleted_at = models.DateTimeField(null=True, blank=True)
- deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_questions')
- locked = models.BooleanField(default=False)
- locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_questions')
- locked_at = models.DateTimeField(null=True, blank=True)
- # Denormalised data
- score = models.IntegerField(default=0)
- vote_up_count = models.IntegerField(default=0)
- vote_down_count = models.IntegerField(default=0)
- answer_count = models.PositiveIntegerField(default=0)
- comment_count = models.PositiveIntegerField(default=0)
- view_count = models.PositiveIntegerField(default=0)
- offensive_flag_count = models.SmallIntegerField(default=0)
- favourite_count = models.PositiveIntegerField(default=0)
- last_edited_at = models.DateTimeField(null=True, blank=True)
- last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_questions')
- last_activity_at = models.DateTimeField(default=datetime.datetime.now)
- last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions')
- tagnames = models.CharField(max_length=125)
- summary = models.CharField(max_length=180)
- html = models.TextField()
- comments = generic.GenericRelation(Comment)
- votes = generic.GenericRelation(Vote)
- flagged_items = generic.GenericRelation(FlaggedItem)
-
- objects = QuestionManager()
-
- def save(self, **kwargs):
- """
- Overridden to manually manage addition of tags when the object
- is first saved.
-
- This is required as we're using ``tagnames`` as the sole means of
- adding and editing tags.
- """
- initial_addition = (self.id is None)
- super(Question, self).save(**kwargs)
- if initial_addition:
- tags = Tag.objects.get_or_create_multiple(self.tagname_list(),
- self.author)
- self.tags.add(*tags)
- Tag.objects.update_use_counts(tags)
-
- def tagname_list(self):
- """Creates a list of Tag names from the ``tagnames`` attribute."""
- return [name for name in self.tagnames.split(u' ')]
-
- def get_absolute_url(self):
- return '%s%s' % (reverse('question', args=[self.id]), self.title)
-
- def has_favorite_by_user(self, user):
- if not user.is_authenticated():
- return False
- return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0
-
- def get_answer_count_by_user(self, user_id):
- query_set = Answer.objects.filter(author__id=user_id)
- return query_set.filter(question=self).count()
-
- def get_question_title(self):
- if self.closed:
- attr = CONST['closed']
- elif self.deleted:
- attr = CONST['deleted']
- else:
- attr = None
- return u'%s %s' % (self.title, attr) if attr is not None else self.title
-
- def get_revision_url(self):
- return reverse('question_revisions', args=[self.id])
-
- def get_latest_revision(self):
- return self.revisions.all()[0]
-
- def __unicode__(self):
- return self.title
-
- class Meta:
- db_table = u'question'
-
-class QuestionRevision(models.Model):
- """A revision of a Question."""
- question = models.ForeignKey(Question, related_name='revisions')
- revision = models.PositiveIntegerField(blank=True)
- title = models.CharField(max_length=300)
- author = models.ForeignKey(User, related_name='question_revisions')
- revised_at = models.DateTimeField()
- tagnames = models.CharField(max_length=125)
- summary = models.CharField(max_length=300, blank=True)
- text = models.TextField()
-
- class Meta:
- db_table = u'question_revision'
- ordering = ('-revision',)
-
- def get_question_title(self):
- return self.question.title
-
- def get_absolute_url(self):
- return '/questions/%s/revisions' % (self.question.id)
-
- def save(self, **kwargs):
- """Looks up the next available revision number."""
- if not self.revision:
- self.revision = QuestionRevision.objects.filter(
- question=self.question).values_list('revision',
- flat=True)[0] + 1
- super(QuestionRevision, self).save(**kwargs)
-
- def __unicode__(self):
- return u'revision %s of %s' % (self.revision, self.title)
-
-class Answer(models.Model):
- question = models.ForeignKey(Question, related_name='answers')
- author = models.ForeignKey(User, related_name='answers')
- added_at = models.DateTimeField(default=datetime.datetime.now)
- # Status
- wiki = models.BooleanField(default=False)
- wikified_at = models.DateTimeField(null=True, blank=True)
- accepted = models.BooleanField(default=False)
- accepted_at = models.DateTimeField(null=True, blank=True)
- deleted = models.BooleanField(default=False)
- deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_answers')
- locked = models.BooleanField(default=False)
- locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_answers')
- locked_at = models.DateTimeField(null=True, blank=True)
- # Denormalised data
- score = models.IntegerField(default=0)
- vote_up_count = models.IntegerField(default=0)
- vote_down_count = models.IntegerField(default=0)
- comment_count = models.PositiveIntegerField(default=0)
- offensive_flag_count = models.SmallIntegerField(default=0)
- last_edited_at = models.DateTimeField(null=True, blank=True)
- last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_answers')
- html = models.TextField()
- comments = generic.GenericRelation(Comment)
- votes = generic.GenericRelation(Vote)
- flagged_items = generic.GenericRelation(FlaggedItem)
-
- objects = AnswerManager()
-
- def get_user_vote(self, user):
- votes = self.votes.filter(user=user)
- if votes.count() > 0:
- return votes[0]
- else:
- return None
-
- def get_latest_revision(self):
- return self.revisions.all()[0]
-
- def get_question_title(self):
- return self.question.title
-
- def get_absolute_url(self):
- return '%s%s#%s' % (reverse('question', args=[self.question.id]), self.question.title, self.id)
-
- class Meta:
- db_table = u'answer'
-
- def __unicode__(self):
- return self.html
-
-class AnswerRevision(models.Model):
- """A revision of an Answer."""
- answer = models.ForeignKey(Answer, related_name='revisions')
- revision = models.PositiveIntegerField()
- author = models.ForeignKey(User, related_name='answer_revisions')
- revised_at = models.DateTimeField()
- summary = models.CharField(max_length=300, blank=True)
- text = models.TextField()
-
- def get_absolute_url(self):
- return '/answers/%s/revisions' % (self.answer.id)
-
- def get_question_title(self):
- return self.answer.question.title
-
- class Meta:
- db_table = u'answer_revision'
- ordering = ('-revision',)
-
- def save(self, **kwargs):
- """Looks up the next available revision number if not set."""
- if not self.revision:
- self.revision = AnswerRevision.objects.filter(
- answer=self.answer).values_list('revision',
- flat=True)[0] + 1
- super(AnswerRevision, self).save(**kwargs)
-
-class FavoriteQuestion(models.Model):
- """A favorite Question of a User."""
- question = models.ForeignKey(Question)
- user = models.ForeignKey(User, related_name='user_favorite_questions')
- added_at = models.DateTimeField(default=datetime.datetime.now)
- class Meta:
- db_table = u'favorite_question'
- def __unicode__(self):
- return '[%s] favorited at %s' %(self.user, self.added_at)
-
-class Badge(models.Model):
- """Awarded for notable actions performed on the site by Users."""
- GOLD = 1
- SILVER = 2
- BRONZE = 3
- TYPE_CHOICES = (
- (GOLD, _('gold')),
- (SILVER, _('silver')),
- (BRONZE, _('bronze')),
- )
-
- name = models.CharField(max_length=50)
- type = models.SmallIntegerField(choices=TYPE_CHOICES)
- slug = models.SlugField(max_length=50, blank=True)
- description = models.CharField(max_length=300)
- multiple = models.BooleanField(default=False)
- # Denormalised data
- awarded_count = models.PositiveIntegerField(default=0)
-
- class Meta:
- db_table = u'badge'
- ordering = ('name',)
- unique_together = ('name', 'type')
-
- def __unicode__(self):
- return u'%s: %s' % (self.get_type_display(), self.name)
-
- def save(self, **kwargs):
- if not self.slug:
- self.slug = self.name#slugify(self.name)
- super(Badge, self).save(**kwargs)
-
- def get_absolute_url(self):
- return '%s%s/' % (reverse('badge', args=[self.id]), self.slug)
-
-class Award(models.Model):
- """The awarding of a Badge to a User."""
- user = models.ForeignKey(User, related_name='award_user')
- badge = models.ForeignKey(Badge, related_name='award_badge')
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- awarded_at = models.DateTimeField(default=datetime.datetime.now)
- notified = models.BooleanField(default=False)
- objects = AwardManager()
-
- def __unicode__(self):
- return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.name, self.awarded_at)
-
- class Meta:
- db_table = u'award'
-
-class Repute(models.Model):
- """The reputation histories for user"""
- user = models.ForeignKey(User)
- positive = models.SmallIntegerField(default=0)
- negative = models.SmallIntegerField(default=0)
- question = models.ForeignKey(Question)
- reputed_at = models.DateTimeField(default=datetime.datetime.now)
- reputation_type = models.SmallIntegerField(choices=TYPE_REPUTATION)
- reputation = models.IntegerField(default=1)
- objects = ReputeManager()
-
- def __unicode__(self):
- return u'[%s]\' reputation changed at %s' % (self.user.username, self.reputed_at)
-
- class Meta:
- db_table = u'repute'
-
-class Activity(models.Model):
- """
- We keep some history data for user activities
- """
- user = models.ForeignKey(User)
- activity_type = models.SmallIntegerField(choices=TYPE_ACTIVITY)
- active_at = models.DateTimeField(default=datetime.datetime.now)
- content_type = models.ForeignKey(ContentType)
- object_id = models.PositiveIntegerField()
- content_object = generic.GenericForeignKey('content_type', 'object_id')
- is_auditted = models.BooleanField(default=False)
-
- def __unicode__(self):
- return u'[%s] was active at %s' % (self.user.username, self.active_at)
-
- class Meta:
- db_table = u'activity'
-
-class Book(models.Model):
- """
- Model for book info
- """
- user = models.ForeignKey(User)
- title = models.CharField(max_length=255)
- short_name = models.CharField(max_length=255)
- author = models.CharField(max_length=255)
- price = models.DecimalField(max_digits=6, decimal_places=2)
- pages = models.SmallIntegerField()
- published_at = models.DateTimeField()
- publication = models.CharField(max_length=255)
- cover_img = models.CharField(max_length=255)
- tagnames = models.CharField(max_length=125)
- added_at = models.DateTimeField()
- last_edited_at = models.DateTimeField()
- questions = models.ManyToManyField(Question, related_name='book', db_table='book_question')
-
- def get_absolute_url(self):
- return '%s' % reverse('book', args=[self.short_name])
-
- def __unicode__(self):
- return self.title
- class Meta:
- db_table = u'book'
-
-class BookAuthorInfo(models.Model):
- """
- Model for book author info
- """
- user = models.ForeignKey(User)
- book = models.ForeignKey(Book)
- blog_url = models.CharField(max_length=255)
- added_at = models.DateTimeField()
- last_edited_at = models.DateTimeField()
-
- class Meta:
- db_table = u'book_author_info'
-
-class BookAuthorRss(models.Model):
- """
- Model for book author blog rss
- """
- user = models.ForeignKey(User)
- book = models.ForeignKey(Book)
- title = models.CharField(max_length=255)
- url = models.CharField(max_length=255)
- rss_created_at = models.DateTimeField()
- added_at = models.DateTimeField()
-
- class Meta:
- db_table = u'book_author_rss'
-
-# User extend properties
-QUESTIONS_PER_PAGE_CHOICES = (
- (10, u'10'),
- (30, u'30'),
- (50, u'50'),
-)
-
-User.add_to_class('reputation', models.PositiveIntegerField(default=1))
-User.add_to_class('gravatar', models.CharField(max_length=32))
-User.add_to_class('favorite_questions',
- models.ManyToManyField(Question, through=FavoriteQuestion,
- related_name='favorited_by'))
-User.add_to_class('badges', models.ManyToManyField(Badge, through=Award,
- related_name='awarded_to'))
-User.add_to_class('gold', models.SmallIntegerField(default=0))
-User.add_to_class('silver', models.SmallIntegerField(default=0))
-User.add_to_class('bronze', models.SmallIntegerField(default=0))
-User.add_to_class('questions_per_page',
- models.SmallIntegerField(choices=QUESTIONS_PER_PAGE_CHOICES, default=10))
-User.add_to_class('last_seen',
- models.DateTimeField(default=datetime.datetime.now))
-User.add_to_class('real_name', models.CharField(max_length=100, blank=True))
-User.add_to_class('website', models.URLField(max_length=200, blank=True))
-User.add_to_class('location', models.CharField(max_length=100, blank=True))
-User.add_to_class('date_of_birth', models.DateField(null=True, blank=True))
-User.add_to_class('about', models.TextField(blank=True))
-
-# custom signal
-tags_updated = django.dispatch.Signal(providing_args=["question"])
-edit_question_or_answer = django.dispatch.Signal(providing_args=["instance", "modified_by"])
-delete_post_or_answer = django.dispatch.Signal(providing_args=["instance", "deleted_by"])
-mark_offensive = django.dispatch.Signal(providing_args=["instance", "mark_by"])
-user_updated = django.dispatch.Signal(providing_args=["instance", "updated_by"])
-def get_messages(self):
- messages = []
- for m in self.message_set.all():
- messages.append(m.message)
- return messages
-
-def delete_messages(self):
- self.message_set.all().delete()
-
-def get_profile_url(self):
- """Returns the URL for this User's profile."""
- return '%s%s/' % (reverse('user', args=[self.id]), self.username)
-User.add_to_class('get_profile_url', get_profile_url)
-User.add_to_class('get_messages', get_messages)
-User.add_to_class('delete_messages', delete_messages)
-
-def calculate_gravatar_hash(instance, **kwargs):
- """Calculates a User's gravatar hash from their email address."""
- if kwargs.get('raw', False):
- return
- instance.gravatar = hashlib.md5(instance.email).hexdigest()
-
-def record_ask_event(instance, created, **kwargs):
- if created:
- activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ASK_QUESTION)
- activity.save()
-
-def record_answer_event(instance, created, **kwargs):
- if created:
- activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ANSWER)
- activity.save()
-
-def record_comment_event(instance, created, **kwargs):
- if created:
- from django.contrib.contenttypes.models import ContentType
- question_type = ContentType.objects.get_for_model(Question)
- question_type_id = question_type.id
- type = TYPE_ACTIVITY_COMMENT_QUESTION if instance.content_type_id == question_type_id else TYPE_ACTIVITY_COMMENT_ANSWER
- activity = Activity(user=instance.user, active_at=instance.added_at, content_object=instance, activity_type=type)
- activity.save()
-
-def record_revision_question_event(instance, created, **kwargs):
- if created and instance.revision <> 1:
- activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_QUESTION)
- activity.save()
-
-def record_revision_answer_event(instance, created, **kwargs):
- if created and instance.revision <> 1:
- activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_ANSWER)
- activity.save()
-
-def record_award_event(instance, created, **kwargs):
- """
- After we awarded a badge to user, we need to record this activity and notify user.
- We also recaculate awarded_count of this badge and user information.
- """
- if created:
- activity = Activity(user=instance.user, active_at=instance.awarded_at, content_object=instance,
- activity_type=TYPE_ACTIVITY_PRIZE)
- activity.save()
-
- instance.badge.awarded_count += 1
- instance.badge.save()
-
- if instance.badge.type == Badge.GOLD:
- instance.user.gold += 1
- if instance.badge.type == Badge.SILVER:
- instance.user.silver += 1
- if instance.badge.type == Badge.BRONZE:
- instance.user.bronze += 1
- instance.user.save()
-
-def notify_award_message(instance, created, **kwargs):
- """
- Notify users when they have been awarded badges by using Django message.
- """
- if created:
- user = instance.user
- user.message_set.create(message=u"%s" % instance.badge.name)
-
-def record_answer_accepted(instance, created, **kwargs):
- """
- when answer is accepted, we record this for question author - who accepted it.
- """
- if not created and instance.accepted:
- activity = Activity(user=instance.question.author, active_at=datetime.datetime.now(), \
- content_object=instance, activity_type=TYPE_ACTIVITY_MARK_ANSWER)
- activity.save()
-
-def update_last_seen(instance, created, **kwargs):
- """
- when user has activities, we update 'last_seen' time stamp for him
- """
- user = instance.user
- user.last_seen = datetime.datetime.now()
- user.save()
-
-def record_vote(instance, created, **kwargs):
- """
- when user have voted
- """
- if created:
- if instance.vote == 1:
- vote_type = TYPE_ACTIVITY_VOTE_UP
- else:
- vote_type = TYPE_ACTIVITY_VOTE_DOWN
-
- activity = Activity(user=instance.user, active_at=instance.voted_at, content_object=instance, activity_type=vote_type)
- activity.save()
-
-def record_cancel_vote(instance, **kwargs):
- """
- when user canceled vote, the vote will be deleted.
- """
- activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_CANCEL_VOTE)
- activity.save()
-
-def record_delete_question(instance, delete_by, **kwargs):
- """
- when user deleted the question
- """
- if instance.__class__ == "Question":
- activity_type = TYPE_ACTIVITY_DELETE_QUESTION
- else:
- activity_type = TYPE_ACTIVITY_DELETE_ANSWER
-
- activity = Activity(user=delete_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=activity_type)
- activity.save()
-
-def record_mark_offensive(instance, mark_by, **kwargs):
- activity = Activity(user=mark_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_MARK_OFFENSIVE)
- activity.save()
-
-def record_update_tags(question, **kwargs):
- """
- when user updated tags of the question
- """
- activity = Activity(user=question.author, active_at=datetime.datetime.now(), content_object=question, activity_type=TYPE_ACTIVITY_UPDATE_TAGS)
- activity.save()
-
-def record_favorite_question(instance, created, **kwargs):
- """
- when user add the question in him favorite questions list.
- """
- if created:
- activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_FAVORITE)
- activity.save()
-
-def record_user_full_updated(instance, **kwargs):
- activity = Activity(user=instance, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_USER_FULL_UPDATED)
- activity.save()
-
-#signal for User modle save changes
-pre_save.connect(calculate_gravatar_hash, sender=User)
-post_save.connect(record_ask_event, sender=Question)
-post_save.connect(record_answer_event, sender=Answer)
-post_save.connect(record_comment_event, sender=Comment)
-post_save.connect(record_revision_question_event, sender=QuestionRevision)
-post_save.connect(record_revision_answer_event, sender=AnswerRevision)
-post_save.connect(record_award_event, sender=Award)
-post_save.connect(notify_award_message, sender=Award)
-post_save.connect(record_answer_accepted, sender=Answer)
-post_save.connect(update_last_seen, sender=Activity)
-post_save.connect(record_vote, sender=Vote)
-post_delete.connect(record_cancel_vote, sender=Vote)
-delete_post_or_answer.connect(record_delete_question, sender=Question)
-delete_post_or_answer.connect(record_delete_question, sender=Answer)
-mark_offensive.connect(record_mark_offensive, sender=Question)
-mark_offensive.connect(record_mark_offensive, sender=Answer)
-tags_updated.connect(record_update_tags, sender=Question)
-post_save.connect(record_favorite_question, sender=FavoriteQuestion)
-user_updated.connect(record_user_full_updated, sender=User)
+# encoding:utf-8
+import datetime
+import hashlib
+from urllib import quote_plus, urlencode
+from django.db import models
+from django.utils.html import strip_tags
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+from django.template.defaultfilters import slugify
+from django.db.models.signals import post_delete, post_save, pre_save
+from django.utils.translation import ugettext as _
+import django.dispatch
+import settings
+
+from forum.managers import *
+from const import *
+
+class EmailFeed(models.Model):
+ #subscription key for unsubscribe by visiting emailed link
+ key = models.CharField(max_length=32)
+ #generic relation with feed content (i.e. question or tags)
+ feed_content_type = models.ForeignKey(ContentType,related_name='content_emailfeed')
+ feed_id = models.PositiveIntegerField()
+ content = generic.GenericForeignKey('feed_content_type','feed_id')
+ #generic relation with owner - either nameless email or User
+ subscriber_content_type = models.ForeignKey(ContentType,related_name='subscriber_emailfeed')
+ subscriber_id = models.PositiveIntegerField()
+ subscriber = generic.GenericForeignKey('subscriber_content_type','subscriber_id')
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ reported_at = models.DateTimeField(default=datetime.datetime.now)
+
+ #getter functions rely on implementations of similar functions in content
+ #of subscriber objects
+ def get_update_summary(self):
+ return self.content.get_update_summary(last_reported_at = self.reported_at,recipient_email = self.get_email())
+
+ def get_email(self):
+ return self.subscriber.email
+
+class Tag(models.Model):
+ name = models.CharField(max_length=255, unique=True)
+ created_by = models.ForeignKey(User, related_name='created_tags')
+ deleted = models.BooleanField(default=False)
+ deleted_at = models.DateTimeField(null=True, blank=True)
+ deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_tags')
+ email_feeds = generic.GenericRelation(EmailFeed)
+ # Denormalised data
+ used_count = models.PositiveIntegerField(default=0)
+
+ objects = TagManager()
+
+ class Meta:
+ db_table = u'tag'
+ ordering = ('-used_count', 'name')
+
+ def __unicode__(self):
+ return self.name
+
+class Comment(models.Model):
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ user = models.ForeignKey(User, related_name='comments')
+ comment = models.CharField(max_length=300)
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+
+ class Meta:
+ ordering = ('-added_at',)
+ db_table = u'comment'
+ def __unicode__(self):
+ return self.comment
+
+class Vote(models.Model):
+ VOTE_UP = +1
+ VOTE_DOWN = -1
+ VOTE_CHOICES = (
+ (VOTE_UP, u'Up'),
+ (VOTE_DOWN, u'Down'),
+ )
+
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ user = models.ForeignKey(User, related_name='votes')
+ vote = models.SmallIntegerField(choices=VOTE_CHOICES)
+ voted_at = models.DateTimeField(default=datetime.datetime.now)
+
+ objects = VoteManager()
+
+ class Meta:
+ unique_together = ('content_type', 'object_id', 'user')
+ db_table = u'vote'
+ def __unicode__(self):
+ return '[%s] voted at %s: %s' %(self.user, self.voted_at, self.vote)
+
+ def is_upvote(self):
+ return self.vote == self.VOTE_UP
+
+ def is_downvote(self):
+ return self.vote == self.VOTE_DOWN
+
+class FlaggedItem(models.Model):
+ """A flag on a Question or Answer indicating offensive content."""
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ user = models.ForeignKey(User, related_name='flagged_items')
+ flagged_at = models.DateTimeField(default=datetime.datetime.now)
+
+ objects = FlaggedItemManager()
+
+ class Meta:
+ unique_together = ('content_type', 'object_id', 'user')
+ db_table = u'flagged_item'
+ def __unicode__(self):
+ return '[%s] flagged at %s' %(self.user, self.flagged_at)
+
+class Question(models.Model):
+ title = models.CharField(max_length=300)
+ author = models.ForeignKey(User, related_name='questions')
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ tags = models.ManyToManyField(Tag, related_name='questions')
+ # Status
+ wiki = models.BooleanField(default=False)
+ wikified_at = models.DateTimeField(null=True, blank=True)
+ answer_accepted = models.BooleanField(default=False)
+ closed = models.BooleanField(default=False)
+ closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions')
+ closed_at = models.DateTimeField(null=True, blank=True)
+ close_reason = models.SmallIntegerField(choices=CLOSE_REASONS, null=True, blank=True)
+ deleted = models.BooleanField(default=False)
+ deleted_at = models.DateTimeField(null=True, blank=True)
+ deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_questions')
+ locked = models.BooleanField(default=False)
+ locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_questions')
+ locked_at = models.DateTimeField(null=True, blank=True)
+ # Denormalised data
+ score = models.IntegerField(default=0)
+ vote_up_count = models.IntegerField(default=0)
+ vote_down_count = models.IntegerField(default=0)
+ answer_count = models.PositiveIntegerField(default=0)
+ comment_count = models.PositiveIntegerField(default=0)
+ view_count = models.PositiveIntegerField(default=0)
+ offensive_flag_count = models.SmallIntegerField(default=0)
+ favourite_count = models.PositiveIntegerField(default=0)
+ last_edited_at = models.DateTimeField(null=True, blank=True)
+ last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_questions')
+ last_activity_at = models.DateTimeField(default=datetime.datetime.now)
+ last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions')
+ tagnames = models.CharField(max_length=125)
+ summary = models.CharField(max_length=180)
+ html = models.TextField()
+ comments = generic.GenericRelation(Comment)
+ votes = generic.GenericRelation(Vote)
+ flagged_items = generic.GenericRelation(FlaggedItem)
+ email_feeds = generic.GenericRelation(EmailFeed)
+
+ objects = QuestionManager()
+
+ def save(self, **kwargs):
+ """
+ Overridden to manually manage addition of tags when the object
+ is first saved.
+
+ This is required as we're using ``tagnames`` as the sole means of
+ adding and editing tags.
+ """
+ initial_addition = (self.id is None)
+ super(Question, self).save(**kwargs)
+ if initial_addition:
+ tags = Tag.objects.get_or_create_multiple(self.tagname_list(),
+ self.author)
+ self.tags.add(*tags)
+ Tag.objects.update_use_counts(tags)
+
+ def tagname_list(self):
+ """Creates a list of Tag names from the ``tagnames`` attribute."""
+ return [name for name in self.tagnames.split(u' ')]
+
+ def get_absolute_url(self):
+ return '%s%s' % (reverse('question', args=[self.id]), self.title.replace(' ', '-'))
+
+ def has_favorite_by_user(self, user):
+ if not user.is_authenticated():
+ return False
+ return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0
+
+ def get_answer_count_by_user(self, user_id):
+ query_set = Answer.objects.filter(author__id=user_id)
+ return query_set.filter(question=self).count()
+
+ def get_question_title(self):
+ if self.closed:
+ attr = CONST['closed']
+ elif self.deleted:
+ attr = CONST['deleted']
+ else:
+ attr = None
+ if attr is not None:
+ return u'%s %s' % (self.title, attr)
+ else:
+ return self.title
+
+ def get_revision_url(self):
+ return reverse('question_revisions', args=[self.id])
+
+ def get_latest_revision(self):
+ return self.revisions.all()[0]
+
+ def get_update_summary(self,last_reported_at=None,recipient_email=''):
+ edited = False
+ if self.last_edited_at and self.last_edited_at > last_reported_at:
+ if self.last_edited_by.email != recipient_email:
+ edited = True
+ comments = []
+ for comment in self.comments.all():
+ if comment.added_at > last_reported_at and comment.user.email != recipient_email:
+ comments.append(comment)
+ new_answers = []
+ answer_comments = []
+ modified_answers = []
+ commented_answers = []
+ import sets
+ commented_answers = sets.Set([])
+ for answer in self.answers.all():
+ if (answer.added_at > last_reported_at and answer.author.email != recipient_email):
+ new_answers.append(answer)
+ if (answer.last_edited_at
+ and answer.last_edited_at > last_reported_at
+ and answer.last_edited_by.email != recipient_email):
+ modified_answers.append(answer)
+ for comment in answer.comments.all():
+ if comment.added_at > last_reported_at and comment.user.email != recipient_email:
+ commented_answers.add(answer)
+ answer_comments.append(comment)
+
+ #create the report
+ if edited or comments or new_answers or modified_answers or answer_comments:
+ out = []
+ if edited:
+ out.append(_('%(author)s modified the question') % {'author':self.last_edited_by.username})
+ if new_answers:
+ names = sets.Set(map(lambda x: x.author.username,new_answers))
+ people = ', '.join(names)
+ out.append(_('%(people)s posted %(new_answer_count)s new answers') \
+ % {'new_answer_count':len(new_answers),'people':people})
+ if comments:
+ names = sets.Set(map(lambda x: x.user.username,comments))
+ people = ', '.join(names)
+ out.append(_('%(people)s commented the question') % {'people':people})
+ if answer_comments:
+ names = sets.Set(map(lambda x: x.user.username,answer_comments))
+ people = ', '.join(names)
+ if len(commented_answers) > 1:
+ out.append(_('%(people)s commented answers') % {'people':people})
+ else:
+ out.append(_('%(people)s commented an answer') % {'people':people})
+ url = settings.APP_URL + self.get_absolute_url()
+ retval = '<a href="%s">%s</a>:<br>\n' % (url,self.title)
+ out = map(lambda x: '<li>' + x + '</li>',out)
+ retval += '<ul>' + '\n'.join(out) + '</ul><br>\n'
+ return retval
+ else:
+ return None
+
+ def __unicode__(self):
+ return self.title
+
+ class Meta:
+ db_table = u'question'
+
+class QuestionRevision(models.Model):
+ """A revision of a Question."""
+ question = models.ForeignKey(Question, related_name='revisions')
+ revision = models.PositiveIntegerField(blank=True)
+ title = models.CharField(max_length=300)
+ author = models.ForeignKey(User, related_name='question_revisions')
+ revised_at = models.DateTimeField()
+ tagnames = models.CharField(max_length=125)
+ summary = models.CharField(max_length=300, blank=True)
+ text = models.TextField()
+
+ class Meta:
+ db_table = u'question_revision'
+ ordering = ('-revision',)
+
+ def get_question_title(self):
+ return self.question.title
+
+ def get_absolute_url(self):
+ return '/questions/%s/revisions' % (self.question.id)
+
+ def save(self, **kwargs):
+ """Looks up the next available revision number."""
+ if not self.revision:
+ self.revision = QuestionRevision.objects.filter(
+ question=self.question).values_list('revision',
+ flat=True)[0] + 1
+ super(QuestionRevision, self).save(**kwargs)
+
+ def __unicode__(self):
+ return u'revision %s of %s' % (self.revision, self.title)
+
+class AnonymousAnswer(models.Model):
+ question = models.ForeignKey(Question, related_name='anonymous_answers')
+ session_key = models.CharField(max_length=40) #session id for anonymous questions
+ wiki = models.BooleanField(default=False)
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ ip_addr = models.IPAddressField(max_length=21) #allow high port numbers
+ author = models.ForeignKey(User,null=True)
+ text = models.TextField()
+ summary = models.CharField(max_length=180)
+
+ def publish(self,user):
+ from forum.views import create_new_answer
+ added_at = datetime.datetime.now()
+ print user.id
+ create_new_answer(question=self.question,wiki=self.wiki,
+ added_at=added_at,text=self.text,
+ author=user)
+ self.delete()
+
+class AnonymousQuestion(models.Model):
+ title = models.CharField(max_length=300)
+ session_key = models.CharField(max_length=40) #session id for anonymous questions
+ text = models.TextField()
+ summary = models.CharField(max_length=180)
+ tagnames = models.CharField(max_length=125)
+ wiki = models.BooleanField(default=False)
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ ip_addr = models.IPAddressField(max_length=21) #allow high port numbers
+ author = models.ForeignKey(User,null=True)
+
+ def publish(self,user):
+ from forum.views import create_new_question
+ added_at = datetime.datetime.now()
+ create_new_question(title=self.title, author=user, added_at=added_at,
+ wiki=self.wiki, tagnames=self.tagnames,
+ summary=self.summary, text=self.text)
+ self.delete()
+
+class Answer(models.Model):
+ question = models.ForeignKey(Question, related_name='answers')
+ author = models.ForeignKey(User, related_name='answers')
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ # Status
+ wiki = models.BooleanField(default=False)
+ wikified_at = models.DateTimeField(null=True, blank=True)
+ accepted = models.BooleanField(default=False)
+ accepted_at = models.DateTimeField(null=True, blank=True)
+ deleted = models.BooleanField(default=False)
+ deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_answers')
+ locked = models.BooleanField(default=False)
+ locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_answers')
+ locked_at = models.DateTimeField(null=True, blank=True)
+ # Denormalised data
+ score = models.IntegerField(default=0)
+ vote_up_count = models.IntegerField(default=0)
+ vote_down_count = models.IntegerField(default=0)
+ comment_count = models.PositiveIntegerField(default=0)
+ offensive_flag_count = models.SmallIntegerField(default=0)
+ last_edited_at = models.DateTimeField(null=True, blank=True)
+ last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_answers')
+ html = models.TextField()
+ comments = generic.GenericRelation(Comment)
+ votes = generic.GenericRelation(Vote)
+ flagged_items = generic.GenericRelation(FlaggedItem)
+
+ objects = AnswerManager()
+
+ def get_user_vote(self, user):
+ votes = self.votes.filter(user=user)
+ if votes.count() > 0:
+ return votes[0]
+ else:
+ return None
+
+ def get_latest_revision(self):
+ return self.revisions.all()[0]
+
+ def get_question_title(self):
+ return self.question.title
+
+ def get_absolute_url(self):
+ return '%s%s#%s' % (reverse('question', args=[self.question.id]), self.question.title, self.id)
+
+ class Meta:
+ db_table = u'answer'
+
+ def __unicode__(self):
+ return self.html
+
+class AnswerRevision(models.Model):
+ """A revision of an Answer."""
+ answer = models.ForeignKey(Answer, related_name='revisions')
+ revision = models.PositiveIntegerField()
+ author = models.ForeignKey(User, related_name='answer_revisions')
+ revised_at = models.DateTimeField()
+ summary = models.CharField(max_length=300, blank=True)
+ text = models.TextField()
+
+ def get_absolute_url(self):
+ return '/answers/%s/revisions' % (self.answer.id)
+
+ def get_question_title(self):
+ return self.answer.question.title
+
+ class Meta:
+ db_table = u'answer_revision'
+ ordering = ('-revision',)
+
+ def save(self, **kwargs):
+ """Looks up the next available revision number if not set."""
+ if not self.revision:
+ self.revision = AnswerRevision.objects.filter(
+ answer=self.answer).values_list('revision',
+ flat=True)[0] + 1
+ super(AnswerRevision, self).save(**kwargs)
+
+class FavoriteQuestion(models.Model):
+ """A favorite Question of a User."""
+ question = models.ForeignKey(Question)
+ user = models.ForeignKey(User, related_name='user_favorite_questions')
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ class Meta:
+ db_table = u'favorite_question'
+ def __unicode__(self):
+ return '[%s] favorited at %s' %(self.user, self.added_at)
+
+class Badge(models.Model):
+ """Awarded for notable actions performed on the site by Users."""
+ GOLD = 1
+ SILVER = 2
+ BRONZE = 3
+ TYPE_CHOICES = (
+ (GOLD, _('gold')),
+ (SILVER, _('silver')),
+ (BRONZE, _('bronze')),
+ )
+
+ name = models.CharField(max_length=50)
+ type = models.SmallIntegerField(choices=TYPE_CHOICES)
+ slug = models.SlugField(max_length=50, blank=True)
+ description = models.CharField(max_length=300)
+ multiple = models.BooleanField(default=False)
+ # Denormalised data
+ awarded_count = models.PositiveIntegerField(default=0)
+
+ class Meta:
+ db_table = u'badge'
+ ordering = ('name',)
+ unique_together = ('name', 'type')
+
+ def __unicode__(self):
+ return u'%s: %s' % (self.get_type_display(), self.name)
+
+ def save(self, **kwargs):
+ if not self.slug:
+ self.slug = self.name#slugify(self.name)
+ super(Badge, self).save(**kwargs)
+
+ def get_absolute_url(self):
+ return '%s%s/' % (reverse('badge', args=[self.id]), self.slug)
+
+class Award(models.Model):
+ """The awarding of a Badge to a User."""
+ user = models.ForeignKey(User, related_name='award_user')
+ badge = models.ForeignKey(Badge, related_name='award_badge')
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ awarded_at = models.DateTimeField(default=datetime.datetime.now)
+ notified = models.BooleanField(default=False)
+ objects = AwardManager()
+
+ def __unicode__(self):
+ return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.name, self.awarded_at)
+
+ class Meta:
+ db_table = u'award'
+
+class Repute(models.Model):
+ """The reputation histories for user"""
+ user = models.ForeignKey(User)
+ positive = models.SmallIntegerField(default=0)
+ negative = models.SmallIntegerField(default=0)
+ question = models.ForeignKey(Question)
+ reputed_at = models.DateTimeField(default=datetime.datetime.now)
+ reputation_type = models.SmallIntegerField(choices=TYPE_REPUTATION)
+ reputation = models.IntegerField(default=1)
+ objects = ReputeManager()
+
+ def __unicode__(self):
+ return u'[%s]\' reputation changed at %s' % (self.user.username, self.reputed_at)
+
+ class Meta:
+ db_table = u'repute'
+
+class Activity(models.Model):
+ """
+ We keep some history data for user activities
+ """
+ user = models.ForeignKey(User)
+ activity_type = models.SmallIntegerField(choices=TYPE_ACTIVITY)
+ active_at = models.DateTimeField(default=datetime.datetime.now)
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ is_auditted = models.BooleanField(default=False)
+
+ def __unicode__(self):
+ return u'[%s] was active at %s' % (self.user.username, self.active_at)
+
+ class Meta:
+ db_table = u'activity'
+
+class Book(models.Model):
+ """
+ Model for book info
+ """
+ user = models.ForeignKey(User)
+ title = models.CharField(max_length=255)
+ short_name = models.CharField(max_length=255)
+ author = models.CharField(max_length=255)
+ price = models.DecimalField(max_digits=6, decimal_places=2)
+ pages = models.SmallIntegerField()
+ published_at = models.DateTimeField()
+ publication = models.CharField(max_length=255)
+ cover_img = models.CharField(max_length=255)
+ tagnames = models.CharField(max_length=125)
+ added_at = models.DateTimeField()
+ last_edited_at = models.DateTimeField()
+ questions = models.ManyToManyField(Question, related_name='book', db_table='book_question')
+
+ def get_absolute_url(self):
+ return '%s' % reverse('book', args=[self.short_name])
+
+ def __unicode__(self):
+ return self.title
+ class Meta:
+ db_table = u'book'
+
+class BookAuthorInfo(models.Model):
+ """
+ Model for book author info
+ """
+ user = models.ForeignKey(User)
+ book = models.ForeignKey(Book)
+ blog_url = models.CharField(max_length=255)
+ added_at = models.DateTimeField()
+ last_edited_at = models.DateTimeField()
+
+ class Meta:
+ db_table = u'book_author_info'
+
+class BookAuthorRss(models.Model):
+ """
+ Model for book author blog rss
+ """
+ user = models.ForeignKey(User)
+ book = models.ForeignKey(Book)
+ title = models.CharField(max_length=255)
+ url = models.CharField(max_length=255)
+ rss_created_at = models.DateTimeField()
+ added_at = models.DateTimeField()
+
+ class Meta:
+ db_table = u'book_author_rss'
+
+class AnonymousEmail(models.Model):
+ #validation key, if used
+ key = models.CharField(max_length=32)
+ email = models.EmailField(null=False,unique=True)
+ isvalid = models.BooleanField(default=False)
+ feeds = generic.GenericRelation(EmailFeed)
+
+# User extend properties
+QUESTIONS_PER_PAGE_CHOICES = (
+ (10, u'10'),
+ (30, u'30'),
+ (50, u'50'),
+)
+
+User.add_to_class('email_isvalid', models.BooleanField(default=False))
+User.add_to_class('email_key', models.CharField(max_length=16, null=True))
+User.add_to_class('reputation', models.PositiveIntegerField(default=1))
+User.add_to_class('gravatar', models.CharField(max_length=32))
+User.add_to_class('email_feeds', generic.GenericRelation(EmailFeed))
+User.add_to_class('favorite_questions',
+ models.ManyToManyField(Question, through=FavoriteQuestion,
+ related_name='favorited_by'))
+User.add_to_class('badges', models.ManyToManyField(Badge, through=Award,
+ related_name='awarded_to'))
+User.add_to_class('gold', models.SmallIntegerField(default=0))
+User.add_to_class('silver', models.SmallIntegerField(default=0))
+User.add_to_class('bronze', models.SmallIntegerField(default=0))
+User.add_to_class('questions_per_page',
+ models.SmallIntegerField(choices=QUESTIONS_PER_PAGE_CHOICES, default=10))
+User.add_to_class('last_seen',
+ models.DateTimeField(default=datetime.datetime.now))
+User.add_to_class('real_name', models.CharField(max_length=100, blank=True))
+User.add_to_class('website', models.URLField(max_length=200, blank=True))
+User.add_to_class('location', models.CharField(max_length=100, blank=True))
+User.add_to_class('date_of_birth', models.DateField(null=True, blank=True))
+User.add_to_class('about', models.TextField(blank=True))
+
+# custom signal
+tags_updated = django.dispatch.Signal(providing_args=["question"])
+edit_question_or_answer = django.dispatch.Signal(providing_args=["instance", "modified_by"])
+delete_post_or_answer = django.dispatch.Signal(providing_args=["instance", "deleted_by"])
+mark_offensive = django.dispatch.Signal(providing_args=["instance", "mark_by"])
+user_updated = django.dispatch.Signal(providing_args=["instance", "updated_by"])
+user_logged_in = django.dispatch.Signal(providing_args=["session"])
+
+
+def get_messages(self):
+ messages = []
+ for m in self.message_set.all():
+ messages.append(m.message)
+ return messages
+
+def delete_messages(self):
+ self.message_set.all().delete()
+
+def get_profile_url(self):
+ """Returns the URL for this User's profile."""
+ return '%s%s/' % (reverse('user', args=[self.id]), self.username)
+User.add_to_class('get_profile_url', get_profile_url)
+User.add_to_class('get_messages', get_messages)
+User.add_to_class('delete_messages', delete_messages)
+
+def calculate_gravatar_hash(instance, **kwargs):
+ """Calculates a User's gravatar hash from their email address."""
+ if kwargs.get('raw', False):
+ return
+ instance.gravatar = hashlib.md5(instance.email).hexdigest()
+
+def record_ask_event(instance, created, **kwargs):
+ if created:
+ activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ASK_QUESTION)
+ activity.save()
+
+def record_answer_event(instance, created, **kwargs):
+ if created:
+ activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ANSWER)
+ activity.save()
+
+def record_comment_event(instance, created, **kwargs):
+ if created:
+ from django.contrib.contenttypes.models import ContentType
+ question_type = ContentType.objects.get_for_model(Question)
+ question_type_id = question_type.id
+ type = TYPE_ACTIVITY_COMMENT_QUESTION if instance.content_type_id == question_type_id else TYPE_ACTIVITY_COMMENT_ANSWER
+ activity = Activity(user=instance.user, active_at=instance.added_at, content_object=instance, activity_type=type)
+ activity.save()
+
+def record_revision_question_event(instance, created, **kwargs):
+ if created and instance.revision <> 1:
+ activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_QUESTION)
+ activity.save()
+
+def record_revision_answer_event(instance, created, **kwargs):
+ if created and instance.revision <> 1:
+ activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_ANSWER)
+ activity.save()
+
+def record_award_event(instance, created, **kwargs):
+ """
+ After we awarded a badge to user, we need to record this activity and notify user.
+ We also recaculate awarded_count of this badge and user information.
+ """
+ if created:
+ activity = Activity(user=instance.user, active_at=instance.awarded_at, content_object=instance,
+ activity_type=TYPE_ACTIVITY_PRIZE)
+ activity.save()
+
+ instance.badge.awarded_count += 1
+ instance.badge.save()
+
+ if instance.badge.type == Badge.GOLD:
+ instance.user.gold += 1
+ if instance.badge.type == Badge.SILVER:
+ instance.user.silver += 1
+ if instance.badge.type == Badge.BRONZE:
+ instance.user.bronze += 1
+ instance.user.save()
+
+def notify_award_message(instance, created, **kwargs):
+ """
+ Notify users when they have been awarded badges by using Django message.
+ """
+ if created:
+ user = instance.user
+ user.message_set.create(message=u"%s" % instance.badge.name)
+
+def record_answer_accepted(instance, created, **kwargs):
+ """
+ when answer is accepted, we record this for question author - who accepted it.
+ """
+ if not created and instance.accepted:
+ activity = Activity(user=instance.question.author, active_at=datetime.datetime.now(), \
+ content_object=instance, activity_type=TYPE_ACTIVITY_MARK_ANSWER)
+ activity.save()
+
+def update_last_seen(instance, created, **kwargs):
+ """
+ when user has activities, we update 'last_seen' time stamp for him
+ """
+ user = instance.user
+ user.last_seen = datetime.datetime.now()
+ user.save()
+
+def record_vote(instance, created, **kwargs):
+ """
+ when user have voted
+ """
+ if created:
+ if instance.vote == 1:
+ vote_type = TYPE_ACTIVITY_VOTE_UP
+ else:
+ vote_type = TYPE_ACTIVITY_VOTE_DOWN
+
+ activity = Activity(user=instance.user, active_at=instance.voted_at, content_object=instance, activity_type=vote_type)
+ activity.save()
+
+def record_cancel_vote(instance, **kwargs):
+ """
+ when user canceled vote, the vote will be deleted.
+ """
+ activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_CANCEL_VOTE)
+ activity.save()
+
+def record_delete_question(instance, delete_by, **kwargs):
+ """
+ when user deleted the question
+ """
+ if instance.__class__ == "Question":
+ activity_type = TYPE_ACTIVITY_DELETE_QUESTION
+ else:
+ activity_type = TYPE_ACTIVITY_DELETE_ANSWER
+
+ activity = Activity(user=delete_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=activity_type)
+ activity.save()
+
+def record_mark_offensive(instance, mark_by, **kwargs):
+ activity = Activity(user=mark_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_MARK_OFFENSIVE)
+ activity.save()
+
+def record_update_tags(question, **kwargs):
+ """
+ when user updated tags of the question
+ """
+ activity = Activity(user=question.author, active_at=datetime.datetime.now(), content_object=question, activity_type=TYPE_ACTIVITY_UPDATE_TAGS)
+ activity.save()
+
+def record_favorite_question(instance, created, **kwargs):
+ """
+ when user add the question in him favorite questions list.
+ """
+ if created:
+ activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_FAVORITE)
+ activity.save()
+
+def record_user_full_updated(instance, **kwargs):
+ activity = Activity(user=instance, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_USER_FULL_UPDATED)
+ activity.save()
+
+def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs):
+ aq_list = AnonymousQuestion.objects.filter(session_key = session_key)
+ aa_list = AnonymousAnswer.objects.filter(session_key = session_key)
+ import settings
+ if settings.EMAIL_VALIDATION == 'on':#add user to the record
+ for aq in aq_list:
+ aq.author = user
+ aq.save()
+ for aa in aa_list:
+ aa.author = user
+ aa.save()
+ #maybe add pending posts message?
+ else: #just publish the questions
+ for aq in aq_list:
+ aq.publish(user)
+ for aa in aa_list:
+ aa.publish(user)
+
+#signal for User modle save changes
+pre_save.connect(calculate_gravatar_hash, sender=User)
+post_save.connect(record_ask_event, sender=Question)
+post_save.connect(record_answer_event, sender=Answer)
+post_save.connect(record_comment_event, sender=Comment)
+post_save.connect(record_revision_question_event, sender=QuestionRevision)
+post_save.connect(record_revision_answer_event, sender=AnswerRevision)
+post_save.connect(record_award_event, sender=Award)
+post_save.connect(notify_award_message, sender=Award)
+post_save.connect(record_answer_accepted, sender=Answer)
+post_save.connect(update_last_seen, sender=Activity)
+post_save.connect(record_vote, sender=Vote)
+post_delete.connect(record_cancel_vote, sender=Vote)
+delete_post_or_answer.connect(record_delete_question, sender=Question)
+delete_post_or_answer.connect(record_delete_question, sender=Answer)
+mark_offensive.connect(record_mark_offensive, sender=Question)
+mark_offensive.connect(record_mark_offensive, sender=Answer)
+tags_updated.connect(record_update_tags, sender=Question)
+post_save.connect(record_favorite_question, sender=FavoriteQuestion)
+user_updated.connect(record_user_full_updated, sender=User)
+user_logged_in.connect(post_stored_anonymous_content)
diff --git a/forum/templatetags/extra_filters.py b/forum/templatetags/extra_filters.py
index 744fa762..cec97920 100644
--- a/forum/templatetags/extra_filters.py
+++ b/forum/templatetags/extra_filters.py
@@ -1,83 +1,83 @@
-from django import template
-from forum import auth
-
-register = template.Library()
-
-@register.filter
-def can_vote_up(user):
- return auth.can_vote_up(user)
-
-@register.filter
-def can_flag_offensive(user):
- return auth.can_flag_offensive(user)
-
-@register.filter
-def can_add_comments(user):
- return auth.can_add_comments(user)
-
-@register.filter
-def can_vote_down(user):
- return auth.can_vote_down(user)
-
-@register.filter
-def can_retag_questions(user):
- return auth.can_retag_questions(user)
-
-@register.filter
-def can_edit_post(user, post):
- return auth.can_edit_post(user, post)
-
-@register.filter
-def can_delete_comment(user, comment):
- return auth.can_delete_comment(user, comment)
-
-@register.filter
-def can_view_offensive_flags(user):
- return auth.can_view_offensive_flags(user)
-
-@register.filter
-def can_close_question(user, question):
- return auth.can_close_question(user, question)
-
-@register.filter
-def can_lock_posts(user):
- return auth.can_lock_posts(user)
-
-@register.filter
-def can_accept_answer(user, question, answer):
- return auth.can_accept_answer(user, question, answer)
-
-@register.filter
-def can_reopen_question(user, question):
- return auth.can_reopen_question(user, question)
-
-@register.filter
-def can_delete_post(user, post):
- return auth.can_delete_post(user, post)
-
-@register.filter
-def can_view_user_edit(request_user, target_user):
- return auth.can_view_user_edit(request_user, target_user)
-
-@register.filter
-def can_view_user_votes(request_user, target_user):
- return auth.can_view_user_votes(request_user, target_user)
-
-@register.filter
-def can_view_user_preferences(request_user, target_user):
- return auth.can_view_user_preferences(request_user, target_user)
-
-@register.filter
-def is_user_self(request_user, target_user):
- return auth.is_user_self(request_user, target_user)
-
-@register.filter
-def cnprog_intword(number):
- try:
- if 1000 <= number < 10000:
- string = str(number)[0:1]
- return "<span class=""thousand"">%sk</span>" % string
- else:
- return number
- except:
+from django import template
+from forum import auth
+
+register = template.Library()
+
+@register.filter
+def can_vote_up(user):
+ return auth.can_vote_up(user)
+
+@register.filter
+def can_flag_offensive(user):
+ return auth.can_flag_offensive(user)
+
+@register.filter
+def can_add_comments(user):
+ return auth.can_add_comments(user)
+
+@register.filter
+def can_vote_down(user):
+ return auth.can_vote_down(user)
+
+@register.filter
+def can_retag_questions(user):
+ return auth.can_retag_questions(user)
+
+@register.filter
+def can_edit_post(user, post):
+ return auth.can_edit_post(user, post)
+
+@register.filter
+def can_delete_comment(user, comment):
+ return auth.can_delete_comment(user, comment)
+
+@register.filter
+def can_view_offensive_flags(user):
+ return auth.can_view_offensive_flags(user)
+
+@register.filter
+def can_close_question(user, question):
+ return auth.can_close_question(user, question)
+
+@register.filter
+def can_lock_posts(user):
+ return auth.can_lock_posts(user)
+
+@register.filter
+def can_accept_answer(user, question, answer):
+ return auth.can_accept_answer(user, question, answer)
+
+@register.filter
+def can_reopen_question(user, question):
+ return auth.can_reopen_question(user, question)
+
+@register.filter
+def can_delete_post(user, post):
+ return auth.can_delete_post(user, post)
+
+@register.filter
+def can_view_user_edit(request_user, target_user):
+ return auth.can_view_user_edit(request_user, target_user)
+
+@register.filter
+def can_view_user_votes(request_user, target_user):
+ return auth.can_view_user_votes(request_user, target_user)
+
+@register.filter
+def can_view_user_preferences(request_user, target_user):
+ return auth.can_view_user_preferences(request_user, target_user)
+
+@register.filter
+def is_user_self(request_user, target_user):
+ return auth.is_user_self(request_user, target_user)
+
+@register.filter
+def cnprog_intword(number):
+ try:
+ if 1000 <= number < 10000:
+ string = str(number)[0:1]
+ return "<span class=""thousand"">%sk</span>" % string
+ else:
+ return number
+ except:
return number \ No newline at end of file
diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py
index 1a4d3641..ac4e6ca3 100644
--- a/forum/templatetags/extra_tags.py
+++ b/forum/templatetags/extra_tags.py
@@ -1,240 +1,240 @@
-import time
-import datetime
-import math
-import re
-import logging
-from django import template
-from django.utils.encoding import smart_unicode
-from django.utils.safestring import mark_safe
-from django.utils.timesince import timesince
-from forum.const import *
-from django.utils.translation import ugettext as _
-
-register = template.Library()
-
-GRAVATAR_TEMPLATE = ('<img width="%(size)s" height="%(size)s" '
- 'src="http://www.gravatar.com/avatar/%(gravatar_hash)s'
- '?s=%(size)s&d=identicon&r=PG">')
-
-@register.simple_tag
-def gravatar(user, size):
- """
- Creates an ``<img>`` for a user's Gravatar with a given size.
-
- This tag can accept a User object, or a dict containing the
- appropriate values.
- """
- try:
- gravatar = user['gravatar']
- except (TypeError, AttributeError, KeyError):
- gravatar = user.gravatar
- return mark_safe(GRAVATAR_TEMPLATE % {
- 'size': size,
- 'gravatar_hash': gravatar,
- })
-
-MAX_FONTSIZE = 18
-MIN_FONTSIZE = 12
-@register.simple_tag
-def tag_font_size(max_size, min_size, current_size):
- """
- do a logarithmic mapping calcuation for a proper size for tagging cloud
- Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/
- """
- #avoid invalid calculation
- if current_size == 0:
- current_size = 1
- try:
- weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size))
- except:
- weight = 0
- return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight)
-
-
-LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5
-LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 4
-NUM_PAGES_OUTSIDE_RANGE = 1
-ADJACENT_PAGES = 2
-@register.inclusion_tag("paginator.html")
-def cnprog_paginator(context):
- """
- custom paginator tag
- Inspired from http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/
- """
- if (context["is_paginated"]):
- " Initialize variables "
- in_leading_range = in_trailing_range = False
- pages_outside_leading_range = pages_outside_trailing_range = range(0)
-
- if (context["pages"] <= LEADING_PAGE_RANGE_DISPLAYED):
- in_leading_range = in_trailing_range = True
- page_numbers = [n for n in range(1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
- elif (context["page"] <= LEADING_PAGE_RANGE):
- in_leading_range = True
- page_numbers = [n for n in range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) if n > 0 and n <= context["pages"]]
- pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
- elif (context["page"] > context["pages"] - TRAILING_PAGE_RANGE):
- in_trailing_range = True
- page_numbers = [n for n in range(context["pages"] - TRAILING_PAGE_RANGE_DISPLAYED + 1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
- pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
- else:
- page_numbers = [n for n in range(context["page"] - ADJACENT_PAGES, context["page"] + ADJACENT_PAGES + 1) if n > 0 and n <= context["pages"]]
- pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
- pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
-
- extend_url = context.get('extend_url', '')
- return {
- "base_url": context["base_url"],
- "is_paginated": context["is_paginated"],
- "previous": context["previous"],
- "has_previous": context["has_previous"],
- "next": context["next"],
- "has_next": context["has_next"],
- "page": context["page"],
- "pages": context["pages"],
- "page_numbers": page_numbers,
- "in_leading_range" : in_leading_range,
- "in_trailing_range" : in_trailing_range,
- "pages_outside_leading_range": pages_outside_leading_range,
- "pages_outside_trailing_range": pages_outside_trailing_range,
- "extend_url" : extend_url
- }
-
-@register.inclusion_tag("pagesize.html")
-def cnprog_pagesize(context):
- """
- display the pagesize selection boxes for paginator
- """
- if (context["is_paginated"]):
- return {
- "base_url": context["base_url"],
- "pagesize" : context["pagesize"],
- "is_paginated": context["is_paginated"]
- }
-
-@register.simple_tag
-def get_score_badge(user):
- BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(reputation)s</span>'
- if user.gold > 0 :
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">'
- '<span class="badge1">&#9679;</span>'
- '<span class="badgecount">%(gold)s</span>'
- '</span>')
- if user.silver > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">'
- '<span class="silver">&#9679;</span>'
- '<span class="badgecount">%(silver)s</span>'
- '</span>')
- if user.bronze > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">'
- '<span class="bronze">&#9679;</span>'
- '<span class="badgecount">%(bronze)s</span>'
- '</span>')
- BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
- return mark_safe(BADGE_TEMPLATE % {
- 'reputation' : user.reputation,
- 'gold' : user.gold,
- 'silver' : user.silver,
- 'bronze' : user.bronze,
- 'badgesword' : _('badges'),
- 'reputationword' : _('reputation points'),
- })
-
-@register.simple_tag
-def get_score_badge_by_details(rep, gold, silver, bronze):
- BADGE_TEMPLATE = '<span class="reputation-score" title="%(reputation)s %(repword)s">%(reputation)s</span>'
- if gold > 0 :
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgeword)s">'
- '<span class="badge1">&#9679;</span>'
- '<span class="badgecount">%(gold)s</span>'
- '</span>')
- if silver > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgeword)s">'
- '<span class="badge2">&#9679;</span>'
- '<span class="badgecount">%(silver)s</span>'
- '</span>')
- if bronze > 0:
- BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgeword)s">'
- '<span class="badge3">&#9679;</span>'
- '<span class="badgecount">%(bronze)s</span>'
- '</span>')
- BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
- return mark_safe(BADGE_TEMPLATE % {
- 'reputation' : rep,
- 'gold' : gold,
- 'silver' : silver,
- 'bronze' : bronze,
- 'repword' : _('reputation points'),
- 'badgeword' : _('badges'),
- })
-
-@register.simple_tag
-def get_user_vote_image(dic, key, arrow):
- if dic.has_key(key):
- if int(dic[key]) == int(arrow):
- return '-on'
- return ''
-
-@register.simple_tag
-def get_age(birthday):
- current_time = datetime.datetime(*time.localtime()[0:6])
- year = birthday.year
- month = birthday.month
- day = birthday.day
- diff = current_time - datetime.datetime(year,month,day,0,0,0)
- return diff.days / 365
-
-@register.simple_tag
-def get_total_count(up_count, down_count):
- return up_count + down_count
-
-@register.simple_tag
-def format_number(value):
- strValue = str(value)
- if len(strValue) <= 3:
- return strValue
- result = ''
- first = ''
- pattern = re.compile('(-?\d+)(\d{3})')
- m = re.match(pattern, strValue)
- while m != None:
- first = m.group(1)
- second = m.group(2)
- result = ',' + second + result
- strValue = first + ',' + second
- m = re.match(pattern, strValue)
- return first + result
-
-@register.simple_tag
-def convert2tagname_list(question):
- question['tagnames'] = [name for name in question['tagnames'].split(u' ')]
- return ''
-
-@register.simple_tag
-def diff_date(date, limen=2):
- current_time = datetime.datetime(*time.localtime()[0:6])
- diff = current_time - date
- diff_days = diff.days
- if diff_days > limen:
- return date
- else:
- return timesince(date) + _(' ago')
-
-@register.simple_tag
-def get_latest_changed_timestamp():
- try:
- from time import localtime, strftime
- from os import path
- from django.conf import settings
- root = settings.SITE_SRC_ROOT
- dir = (
- root,
- '%s/forum' % root,
- '%s/templates' % root,
- )
- stamp = (path.getmtime(d) for d in dir)
- latest = max(stamp)
- timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest))
- except:
- timestr = ''
- return timestr \ No newline at end of file
+import time
+import datetime
+import math
+import re
+import logging
+from django import template
+from django.utils.encoding import smart_unicode
+from django.utils.safestring import mark_safe
+from django.utils.timesince import timesince
+from forum.const import *
+from django.utils.translation import ugettext as _
+
+register = template.Library()
+
+GRAVATAR_TEMPLATE = ('<img width="%(size)s" height="%(size)s" '
+ 'src="http://www.gravatar.com/avatar/%(gravatar_hash)s'
+ '?s=%(size)s&d=identicon&r=PG">')
+
+@register.simple_tag
+def gravatar(user, size):
+ """
+ Creates an ``<img>`` for a user's Gravatar with a given size.
+
+ This tag can accept a User object, or a dict containing the
+ appropriate values.
+ """
+ try:
+ gravatar = user['gravatar']
+ except (TypeError, AttributeError, KeyError):
+ gravatar = user.gravatar
+ return mark_safe(GRAVATAR_TEMPLATE % {
+ 'size': size,
+ 'gravatar_hash': gravatar,
+ })
+
+MAX_FONTSIZE = 18
+MIN_FONTSIZE = 12
+@register.simple_tag
+def tag_font_size(max_size, min_size, current_size):
+ """
+ do a logarithmic mapping calcuation for a proper size for tagging cloud
+ Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/
+ """
+ #avoid invalid calculation
+ if current_size == 0:
+ current_size = 1
+ try:
+ weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size))
+ except:
+ weight = 0
+ return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight)
+
+
+LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5
+LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 4
+NUM_PAGES_OUTSIDE_RANGE = 1
+ADJACENT_PAGES = 2
+@register.inclusion_tag("paginator.html")
+def cnprog_paginator(context):
+ """
+ custom paginator tag
+ Inspired from http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/
+ """
+ if (context["is_paginated"]):
+ " Initialize variables "
+ in_leading_range = in_trailing_range = False
+ pages_outside_leading_range = pages_outside_trailing_range = range(0)
+
+ if (context["pages"] <= LEADING_PAGE_RANGE_DISPLAYED):
+ in_leading_range = in_trailing_range = True
+ page_numbers = [n for n in range(1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
+ elif (context["page"] <= LEADING_PAGE_RANGE):
+ in_leading_range = True
+ page_numbers = [n for n in range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) if n > 0 and n <= context["pages"]]
+ pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
+ elif (context["page"] > context["pages"] - TRAILING_PAGE_RANGE):
+ in_trailing_range = True
+ page_numbers = [n for n in range(context["pages"] - TRAILING_PAGE_RANGE_DISPLAYED + 1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
+ pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
+ else:
+ page_numbers = [n for n in range(context["page"] - ADJACENT_PAGES, context["page"] + ADJACENT_PAGES + 1) if n > 0 and n <= context["pages"]]
+ pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
+ pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
+
+ extend_url = context.get('extend_url', '')
+ return {
+ "base_url": context["base_url"],
+ "is_paginated": context["is_paginated"],
+ "previous": context["previous"],
+ "has_previous": context["has_previous"],
+ "next": context["next"],
+ "has_next": context["has_next"],
+ "page": context["page"],
+ "pages": context["pages"],
+ "page_numbers": page_numbers,
+ "in_leading_range" : in_leading_range,
+ "in_trailing_range" : in_trailing_range,
+ "pages_outside_leading_range": pages_outside_leading_range,
+ "pages_outside_trailing_range": pages_outside_trailing_range,
+ "extend_url" : extend_url
+ }
+
+@register.inclusion_tag("pagesize.html")
+def cnprog_pagesize(context):
+ """
+ display the pagesize selection boxes for paginator
+ """
+ if (context["is_paginated"]):
+ return {
+ "base_url": context["base_url"],
+ "pagesize" : context["pagesize"],
+ "is_paginated": context["is_paginated"]
+ }
+
+@register.simple_tag
+def get_score_badge(user):
+ BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(reputation)s</span>'
+ if user.gold > 0 :
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">'
+ '<span class="badge1">&#9679;</span>'
+ '<span class="badgecount">%(gold)s</span>'
+ '</span>')
+ if user.silver > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">'
+ '<span class="silver">&#9679;</span>'
+ '<span class="badgecount">%(silver)s</span>'
+ '</span>')
+ if user.bronze > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">'
+ '<span class="bronze">&#9679;</span>'
+ '<span class="badgecount">%(bronze)s</span>'
+ '</span>')
+ BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
+ return mark_safe(BADGE_TEMPLATE % {
+ 'reputation' : user.reputation,
+ 'gold' : user.gold,
+ 'silver' : user.silver,
+ 'bronze' : user.bronze,
+ 'badgesword' : _('badges'),
+ 'reputationword' : _('reputation points'),
+ })
+
+@register.simple_tag
+def get_score_badge_by_details(rep, gold, silver, bronze):
+ BADGE_TEMPLATE = '<span class="reputation-score" title="%(reputation)s %(repword)s">%(reputation)s</span>'
+ if gold > 0 :
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgeword)s">'
+ '<span class="badge1">&#9679;</span>'
+ '<span class="badgecount">%(gold)s</span>'
+ '</span>')
+ if silver > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgeword)s">'
+ '<span class="badge2">&#9679;</span>'
+ '<span class="badgecount">%(silver)s</span>'
+ '</span>')
+ if bronze > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgeword)s">'
+ '<span class="badge3">&#9679;</span>'
+ '<span class="badgecount">%(bronze)s</span>'
+ '</span>')
+ BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
+ return mark_safe(BADGE_TEMPLATE % {
+ 'reputation' : rep,
+ 'gold' : gold,
+ 'silver' : silver,
+ 'bronze' : bronze,
+ 'repword' : _('reputation points'),
+ 'badgeword' : _('badges'),
+ })
+
+@register.simple_tag
+def get_user_vote_image(dic, key, arrow):
+ if dic.has_key(key):
+ if int(dic[key]) == int(arrow):
+ return '-on'
+ return ''
+
+@register.simple_tag
+def get_age(birthday):
+ current_time = datetime.datetime(*time.localtime()[0:6])
+ year = birthday.year
+ month = birthday.month
+ day = birthday.day
+ diff = current_time - datetime.datetime(year,month,day,0,0,0)
+ return diff.days / 365
+
+@register.simple_tag
+def get_total_count(up_count, down_count):
+ return up_count + down_count
+
+@register.simple_tag
+def format_number(value):
+ strValue = str(value)
+ if len(strValue) <= 3:
+ return strValue
+ result = ''
+ first = ''
+ pattern = re.compile('(-?\d+)(\d{3})')
+ m = re.match(pattern, strValue)
+ while m != None:
+ first = m.group(1)
+ second = m.group(2)
+ result = ',' + second + result
+ strValue = first + ',' + second
+ m = re.match(pattern, strValue)
+ return first + result
+
+@register.simple_tag
+def convert2tagname_list(question):
+ question['tagnames'] = [name for name in question['tagnames'].split(u' ')]
+ return ''
+
+@register.simple_tag
+def diff_date(date, limen=2):
+ current_time = datetime.datetime(*time.localtime()[0:6])
+ diff = current_time - date
+ diff_days = diff.days
+ if diff_days > limen:
+ return date
+ else:
+ return timesince(date) + _(' ago')
+
+@register.simple_tag
+def get_latest_changed_timestamp():
+ try:
+ from time import localtime, strftime
+ from os import path
+ from django.conf import settings
+ root = settings.SITE_SRC_ROOT
+ dir = (
+ root,
+ '%s/forum' % root,
+ '%s/templates' % root,
+ )
+ stamp = (path.getmtime(d) for d in dir)
+ latest = max(stamp)
+ timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest))
+ except:
+ timestr = ''
+ return timestr
diff --git a/forum/user.py b/forum/user.py
index 233baf0c..41811db9 100644
--- a/forum/user.py
+++ b/forum/user.py
@@ -1,75 +1,74 @@
-# coding=utf-8
-from django.utils.translation import ugettext as _
-class UserView:
- def __init__(self, id, tab_title, tab_description, page_title, view_name, template_file, data_size=0):
- self.id = id
- self.tab_title = tab_title
- self.tab_description = tab_description
- self.page_title = page_title
- self.view_name = view_name
- self.template_file = template_file
- self.data_size = data_size
-
-
-USER_TEMPLATE_VIEWS = (
- UserView(
- id = 'stats',
- tab_title = _('overview'),
- tab_description = _('user profile'),
- page_title = _('user profile overview'),
- view_name = 'user_stats',
- template_file = 'user_stats.html'
- ),
- UserView(
- id = 'recent',
- tab_title = _('recent activity'),
- tab_description = _('recent user activity'),
- page_title = _('profile - recent activity'),
- view_name = 'user_recent',
- template_file = 'user_recent.html',
- data_size = 50
- ),
- UserView(
- id = 'responses',
- tab_title = _('responses'),
- tab_description = _('comments and answers to others questions'),
- page_title = _('profile - responses'),
- view_name = 'user_responses',
- template_file = 'user_responses.html',
- data_size = 50
- ),
- UserView(
- id = 'reputation',
- tab_title = _('reputation'),
- tab_description = _('user reputation in the community'),
- page_title = _('profile - user reputation'),
- view_name = 'user_reputation',
- template_file = 'user_reputation.html'
- ),
- UserView(
- id = 'favorites',
- tab_title = _('favorite questions'),
- tab_description = _('users favorite questions'),
- page_title = _('profile - favorite questions'),
- view_name = 'user_favorites',
- template_file = 'user_favorites.html',
- data_size = 50
- ),
- UserView(
- id = 'votes',
- tab_title = _('casted votes'),
- tab_description = _('user vote record'),
- page_title = _('profile - votes'),
- view_name = 'user_votes',
- template_file = 'user_votes.html',
- data_size = 50
- ),
- UserView(
- id = 'preferences',
- tab_title = _('preferences'),
- tab_description = _('user preference settings'),
- page_title = _('profile - user preferences'),
- view_name = 'user_preferences',
- template_file = 'user_preferences.html'
- )
-)
+from django.utils.translation import ugettext as _
+class UserView:
+ def __init__(self, id, tab_title, tab_description, page_title, view_name, template_file, data_size=0):
+ self.id = id
+ self.tab_title = tab_title
+ self.tab_description = tab_description
+ self.page_title = page_title
+ self.view_name = view_name
+ self.template_file = template_file
+ self.data_size = data_size
+
+
+USER_TEMPLATE_VIEWS = (
+ UserView(
+ id = 'stats',
+ tab_title = _('overview'),
+ tab_description = _('user profile'),
+ page_title = _('user profile overview'),
+ view_name = 'user_stats',
+ template_file = 'user_stats.html'
+ ),
+ UserView(
+ id = 'recent',
+ tab_title = _('recent activity'),
+ tab_description = _('recent user activity'),
+ page_title = _('profile - recent activity'),
+ view_name = 'user_recent',
+ template_file = 'user_recent.html',
+ data_size = 50
+ ),
+ UserView(
+ id = 'responses',
+ tab_title = _('responses'),
+ tab_description = _('comments and answers to others questions'),
+ page_title = _('profile - responses'),
+ view_name = 'user_responses',
+ template_file = 'user_responses.html',
+ data_size = 50
+ ),
+ UserView(
+ id = 'reputation',
+ tab_title = _('reputation'),
+ tab_description = _('user reputation in the community'),
+ page_title = _('profile - user reputation'),
+ view_name = 'user_reputation',
+ template_file = 'user_reputation.html'
+ ),
+ UserView(
+ id = 'favorites',
+ tab_title = _('favorite questions'),
+ tab_description = _('users favorite questions'),
+ page_title = _('profile - favorite questions'),
+ view_name = 'user_favorites',
+ template_file = 'user_favorites.html',
+ data_size = 50
+ ),
+ UserView(
+ id = 'votes',
+ tab_title = _('casted votes'),
+ tab_description = _('user vote record'),
+ page_title = _('profile - votes'),
+ view_name = 'user_votes',
+ template_file = 'user_votes.html',
+ data_size = 50
+ ),
+ UserView(
+ id = 'preferences',
+ tab_title = _('preferences'),
+ tab_description = _('user preference settings'),
+ page_title = _('profile - user preferences'),
+ view_name = 'user_preferences',
+ template_file = 'user_preferences.html'
+ )
+)
diff --git a/forum/views.py b/forum/views.py
index 08a0e958..6ef7bd6d 100644
--- a/forum/views.py
+++ b/forum/views.py
@@ -1,1958 +1,2084 @@
-# encoding:utf-8
-import os.path
-import time, datetime, calendar, random
-import logging
-from urllib import quote, unquote
-from django.conf import settings
-from django.core.files.storage import default_storage
-from django.shortcuts import render_to_response, get_object_or_404
-from django.contrib.auth.decorators import login_required
-from django.http import HttpResponseRedirect, HttpResponse,Http404
-from django.core.paginator import Paginator, EmptyPage, InvalidPage
-from django.template import RequestContext
-from django.utils.html import *
-from django.utils import simplejson
-from django.core import serializers
-from django.db import transaction
-from django.contrib.contenttypes.models import ContentType
-from django.utils.translation import ugettext as _
-
-from utils.html import sanitize_html
-from markdown2 import Markdown
-#from lxml.html.diff import htmldiff
-from forum.diff import textDiff as htmldiff
-from forum.forms import *
-from forum.models import *
-from forum.auth import *
-from forum.const import *
-from forum.user import *
-from forum import auth
-
-# used in index page
-INDEX_PAGE_SIZE = 20
-INDEX_AWARD_SIZE = 15
-INDEX_TAGS_SIZE = 100
-# used in tags list
-DEFAULT_PAGE_SIZE = 60
-# used in questions
-QUESTIONS_PAGE_SIZE = 10
-# used in users
-USERS_PAGE_SIZE = 35
-# used in answers
-ANSWERS_PAGE_SIZE = 10
-markdowner = Markdown(html4tags=True)
-question_type = ContentType.objects.get_for_model(Question)
-answer_type = ContentType.objects.get_for_model(Answer)
-comment_type = ContentType.objects.get_for_model(Comment)
-question_revision_type = ContentType.objects.get_for_model(QuestionRevision)
-answer_revision_type = ContentType.objects.get_for_model(AnswerRevision)
-repute_type =ContentType.objects.get_for_model(Repute)
-question_type_id = question_type.id
-answer_type_id = answer_type.id
-comment_type_id = comment_type.id
-question_revision_type_id = question_revision_type.id
-answer_revision_type_id = answer_revision_type.id
-repute_type_id = repute_type.id
-def _get_tags_cache_json():
- tags = Tag.objects.filter(deleted=False).all()
- tags_list = []
- for tag in tags:
- dic = {'n': tag.name, 'c': tag.used_count }
- tags_list.append(dic)
- tags = simplejson.dumps(tags_list)
- return tags
-
-def index(request):
- view_id = request.GET.get('sort', None)
- view_dic = {
- "latest":"-last_activity_at",
- "hottest":"-answer_count",
- "mostvoted":"-score",
- "trans": "-last_activity_at"
- }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- view_id = "latest"
- orderby = "-last_activity_at"
- # group questions by author_id of 28,29
- if view_id == 'trans':
- questions = Question.objects.get_translation_questions(orderby, INDEX_PAGE_SIZE)
- else:
- questions = Question.objects.get_questions_by_pagesize(orderby, INDEX_PAGE_SIZE)
- # RISK - inner join queries
- questions = questions.select_related()
- tags = Tag.objects.get_valid_tags(INDEX_TAGS_SIZE)
-
- awards = Award.objects.get_recent_awards()
-
- return render_to_response('index.html', {
- "questions" : questions,
- "tab_id" : view_id,
- "tags" : tags,
- "awards" : awards[:INDEX_AWARD_SIZE],
- }, context_instance=RequestContext(request))
-
-def about(request):
- return render_to_response('about.html', context_instance=RequestContext(request))
-
-def faq(request):
- return render_to_response('faq.html', context_instance=RequestContext(request))
-
-def privacy(request):
- return render_to_response('privacy.html', context_instance=RequestContext(request))
-
-def unanswered(request):
- return questions(request, unanswered=True)
-
-def questions(request, tagname=None, unanswered=False):
- """
- List of Questions, Tagged questions, and Unanswered questions.
- """
- # template file
- # "questions.html" or "unanswered.html"
- template_file = "questions.html"
- # Set flag to False by default. If it is equal to True, then need to be saved.
- pagesize_changed = False
- # get pagesize from session, if failed then get default value
- pagesize = request.session.get("pagesize")
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- view_id = "latest"
- orderby = "-added_at"
-
- # check if request is from tagged questions
- if tagname is not None:
- objects = Question.objects.get_questions_by_tag(tagname, orderby)
- elif unanswered:
- #check if request is from unanswered questions
- template_file = "unanswered.html"
- objects = Question.objects.get_unanswered_questions(orderby)
- else:
- objects = Question.objects.get_questions(orderby)
-
- # RISK - inner join queries
- objects = objects.select_related(depth=1);
- objects_list = Paginator(objects, pagesize)
- questions = objects_list.page(page)
-
- # Get related tags from this page objects
- if questions.object_list.count() > 0:
- related_tags = Tag.objects.get_tags_by_questions(questions.object_list)
- else:
- related_tags = None
- return render_to_response(template_file, {
- "questions" : questions,
- "tab_id" : view_id,
- "questions_count" : objects_list.count,
- "tags" : related_tags,
- "searchtag" : tagname,
- "is_unanswered" : unanswered,
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url' : request.path + '?sort=%s&' % view_id,
- 'pagesize' : pagesize
- }}, context_instance=RequestContext(request))
-
-#TODO: allow anynomus user to ask question by providing email and username.
-@login_required
-def ask(request):
- if request.method == "POST":
- form = AskForm(request.POST)
- if form.is_valid():
- added_at = datetime.datetime.now()
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- question = Question(
- title = strip_tags(form.cleaned_data['title']),
- author = request.user,
- added_at = added_at,
- last_activity_at = added_at,
- last_activity_by = request.user,
- wiki = form.cleaned_data['wiki'],
- tagnames = form.cleaned_data['tags'].strip(),
- html = html,
- summary = strip_tags(html)[:120]
- )
- if question.wiki:
- question.last_edited_by = question.author
- question.last_edited_at = added_at
- question.wikified_at = added_at
-
- question.save()
-
- # create the first revision
- QuestionRevision.objects.create(
- question = question,
- revision = 1,
- title = question.title,
- author = request.user,
- revised_at = added_at,
- tagnames = question.tagnames,
- summary = CONST['default_version'],
- text = form.cleaned_data['text']
- )
-
- return HttpResponseRedirect(question.get_absolute_url())
-
- else:
- form = AskForm()
-
- tags = _get_tags_cache_json()
- return render_to_response('ask.html', {
- 'form' : form,
- 'tags' : tags,
- }, context_instance=RequestContext(request))
-
-def question(request, id):
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
- view_id = request.GET.get('sort', 'votes')
- view_dic = {"latest":"-added_at", "oldest":"added_at", "votes":"-score" }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- view_id = "votes"
- orderby = "-score"
-
- question = get_object_or_404(Question, id=id)
- if question.deleted and not can_view_deleted_post(request.user, question):
- raise Http404
- answer_form = AnswerForm(question)
- answers = Answer.objects.get_answers_from_question(question, request.user)
- answers = answers.select_related(depth=1)
-
- favorited = question.has_favorite_by_user(request.user)
- question_vote = question.votes.select_related().filter(user=request.user)
- if question_vote is not None and question_vote.count() > 0:
- question_vote = question_vote[0]
-
- user_answer_votes = {}
- for answer in answers:
- vote = answer.get_user_vote(request.user)
- if vote is not None and not user_answer_votes.has_key(answer.id):
- vote_value = -1
- if vote.is_upvote():
- vote_value = 1
- user_answer_votes[answer.id] = vote_value
-
-
- if answers is not None:
- answers = answers.order_by("-accepted", orderby)
- objects_list = Paginator(answers, ANSWERS_PAGE_SIZE)
- page_objects = objects_list.page(page)
- # update view count
- Question.objects.update_view_count(question)
- return render_to_response('question.html', {
- "question" : question,
- "question_vote" : question_vote,
- "question_comment_count":question.comments.count(),
- "answer" : answer_form,
- "answers" : page_objects.object_list,
- "user_answer_votes": user_answer_votes,
- "tags" : question.tags.all(),
- "tab_id" : view_id,
- "favorited" : favorited,
- "similar_questions" : Question.objects.get_similar_questions(question),
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': page_objects.has_previous(),
- 'has_next': page_objects.has_next(),
- 'previous': page_objects.previous_page_number(),
- 'next': page_objects.next_page_number(),
- 'base_url' : request.path + '?sort=%s&' % view_id,
- 'extend_url' : "#sort-top"
- }
- }, context_instance=RequestContext(request))
-
-@login_required
-def close(request, id):
- question = get_object_or_404(Question, id=id)
- if not can_close_question(request.user, question):
- return HttpResponse('Permission denied.')
- if request.method == 'POST':
- form = CloseForm(request.POST)
- if form.is_valid():
- reason = form.cleaned_data['reason']
- question.closed = True
- question.closed_by = request.user
- question.closed_at = datetime.datetime.now()
- question.close_reason = reason
- question.save()
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- form = CloseForm()
- return render_to_response('close.html', {
- 'form' : form,
- 'question' : question,
- }, context_instance=RequestContext(request))
-
-@login_required
-def reopen(request, id):
- question = get_object_or_404(Question, id=id)
- # open question
- if not can_reopen_question(request.user, question):
- return HttpResponse('Permission denied.')
- if request.method == 'POST' :
- Question.objects.filter(id=question.id).update(closed=False,
- closed_by=None, closed_at=None, close_reason=None)
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- return render_to_response('reopen.html', {
- 'question' : question,
- }, context_instance=RequestContext(request))
-
-@login_required
-def edit_question(request, id):
- question = get_object_or_404(Question, id=id)
- if question.deleted and not can_view_deleted_post(request.user, question):
- raise Http404
- if can_edit_post(request.user, question):
- return _edit_question(request, question)
- elif can_retag_questions(request.user):
- return _retag_question(request, question)
- else:
- raise Http404
-
-def _retag_question(request, question):
- if request.method == 'POST':
- form = RetagQuestionForm(question, request.POST)
- if form.is_valid():
- if form.has_changed():
- latest_revision = question.get_latest_revision()
- retagged_at = datetime.datetime.now()
- # Update the Question itself
- Question.objects.filter(id=question.id).update(
- tagnames = form.cleaned_data['tags'],
- last_edited_at = retagged_at,
- last_edited_by = request.user,
- last_activity_at = retagged_at,
- last_activity_by = request.user
- )
- # Update the Question's tag associations
- tags_updated = Question.objects.update_tags(question,
- form.cleaned_data['tags'], request.user)
- # Create a new revision
- QuestionRevision.objects.create(
- question = question,
- title = latest_revision.title,
- author = request.user,
- revised_at = retagged_at,
- tagnames = form.cleaned_data['tags'],
- summary = CONST['retagged'],
- text = latest_revision.text
- )
- # send tags updated singal
- tags_updated.send(sender=question.__class__, question=question)
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- form = RetagQuestionForm(question)
- return render_to_response('question_retag.html', {
- 'question': question,
- 'form' : form,
- 'tags' : _get_tags_cache_json(),
- }, context_instance=RequestContext(request))
-
-
-def _edit_question(request, question):
- latest_revision = question.get_latest_revision()
- revision_form = None
- if request.method == 'POST':
- if 'select_revision' in request.POST:
- # user has changed revistion number
- revision_form = RevisionForm(question, latest_revision, request.POST)
- if revision_form.is_valid():
- # Replace with those from the selected revision
- form = EditQuestionForm(question,
- QuestionRevision.objects.get(question=question,
- revision=revision_form.cleaned_data['revision']))
- else:
- form = EditQuestionForm(question, latest_revision, request.POST)
- else:
- # Always check modifications against the latest revision
- form = EditQuestionForm(question, latest_revision, request.POST)
- if form.is_valid():
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- if form.has_changed():
- edited_at = datetime.datetime.now()
- tags_changed = (latest_revision.tagnames !=
- form.cleaned_data['tags'])
- tags_updated = False
- # Update the Question itself
- updated_fields = {
- 'title': form.cleaned_data['title'],
- 'last_edited_at': edited_at,
- 'last_edited_by': request.user,
- 'last_activity_at': edited_at,
- 'last_activity_by': request.user,
- 'tagnames': form.cleaned_data['tags'],
- 'summary': strip_tags(html)[:120],
- 'html': html,
- }
-
- # only save when it's checked
- # because wiki doesn't allow to be edited if last version has been enabled already
- # and we make sure this in forms.
- if ('wiki' in form.cleaned_data and
- form.cleaned_data['wiki']):
- updated_fields['wiki'] = True
- updated_fields['wikified_at'] = edited_at
-
- Question.objects.filter(
- id=question.id).update(**updated_fields)
- # Update the Question's tag associations
- if tags_changed:
- tags_updated = Question.objects.update_tags(
- question, form.cleaned_data['tags'], request.user)
- # Create a new revision
- revision = QuestionRevision(
- question = question,
- title = form.cleaned_data['title'],
- author = request.user,
- revised_at = edited_at,
- tagnames = form.cleaned_data['tags'],
- text = form.cleaned_data['text'],
- )
- if form.cleaned_data['summary']:
- revision.summary = form.cleaned_data['summary']
- else:
- revision.summary = 'No.%s Revision' % latest_revision.revision
- revision.save()
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
-
- revision_form = RevisionForm(question, latest_revision)
- form = EditQuestionForm(question, latest_revision)
- return render_to_response('question_edit.html', {
- 'question': question,
- 'revision_form': revision_form,
- 'form' : form,
- 'tags' : _get_tags_cache_json()
- }, context_instance=RequestContext(request))
-
-
-@login_required
-def edit_answer(request, id):
- answer = get_object_or_404(Answer, id=id)
- if answer.deleted and not can_view_deleted_post(request.user, answer):
- raise Http404
- elif not can_edit_post(request.user, answer):
- raise Http404
- else:
- latest_revision = answer.get_latest_revision()
- if request.method == "POST":
- if 'select_revision' in request.POST:
- # user has changed revistion number
- revision_form = RevisionForm(answer, latest_revision, request.POST)
- if revision_form.is_valid():
- # Replace with those from the selected revision
- form = EditAnswerForm(answer,
- AnswerRevision.objects.get(answer=answer,
- revision=revision_form.cleaned_data['revision']))
- else:
- form = EditAnswerForm(answer, latest_revision, request.POST)
- else:
- form = EditAnswerForm(answer, latest_revision, request.POST)
- if form.is_valid():
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- if form.has_changed():
- edited_at = datetime.datetime.now()
- updated_fields = {
- 'last_edited_at': edited_at,
- 'last_edited_by': request.user,
- 'html': html,
- }
- Answer.objects.filter(id=answer.id).update(**updated_fields)
-
- revision = AnswerRevision(
- answer = answer,
- author = request.user,
- revised_at = edited_at,
- text = form.cleaned_data['text']
- )
-
- if form.cleaned_data['summary']:
- revision.summary = form.cleaned_data['summary']
- else:
- revision.summary = 'No.%s Revision' % latest_revision.revision
- revision.save()
-
- answer.question.last_activity_at = edited_at
- answer.question.last_activity_by = request.user
- answer.question.save()
-
- return HttpResponseRedirect(answer.get_absolute_url())
- else:
- revision_form = RevisionForm(answer, latest_revision)
- form = EditAnswerForm(answer, latest_revision)
- return render_to_response('answer_edit.html', {
- 'answer': answer,
- 'revision_form': revision_form,
- 'form' : form,
- }, context_instance=RequestContext(request))
-
-QUESTION_REVISION_TEMPLATE = ('<h1>%(title)s</h1>\n'
- '<div class="text">%(html)s</div>\n'
- '<div class="tags">%(tags)s</div>')
-def question_revisions(request, id):
- post = get_object_or_404(Question, id=id)
- revisions = list(post.revisions.all())
- for i, revision in enumerate(revisions):
- revision.html = QUESTION_REVISION_TEMPLATE % {
- 'title': revision.title,
- 'html': sanitize_html(markdowner.convert(revision.text)),
- 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
- for tag in revision.tagnames.split(' ')]),
- }
- if i > 0:
- revisions[i - 1].diff = htmldiff(revision.html,
- revisions[i - 1].html)
- else:
- revisions[i - 1].diff = QUESTION_REVISION_TEMPLATE % {
- 'title': revisions[0].title,
- 'html': sanitize_html(markdowner.convert(revisions[0].text)),
- 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
- for tag in revisions[0].tagnames.split(' ')]),
- }
- revisions[i - 1].summary = None
- return render_to_response('revisions_question.html', {
- 'post': post,
- 'revisions': revisions,
- }, context_instance=RequestContext(request))
-
-ANSWER_REVISION_TEMPLATE = ('<div class="text">%(html)s</div>')
-def answer_revisions(request, id):
- post = get_object_or_404(Answer, id=id)
- revisions = list(post.revisions.all())
- for i, revision in enumerate(revisions):
- revision.html = ANSWER_REVISION_TEMPLATE % {
- 'html': sanitize_html(markdowner.convert(revision.text))
- }
- if i > 0:
- revisions[i - 1].diff = htmldiff(revision.html,
- revisions[i - 1].html)
- else:
- revisions[i - 1].diff = revisions[i-1].text
- revisions[i - 1].summary = None
- return render_to_response('revisions_answer.html', {
- 'post': post,
- 'revisions': revisions,
- }, context_instance=RequestContext(request))
-
-#TODO: allow anynomus
-@login_required
-def answer(request, id):
- question = get_object_or_404(Question, id=id)
- if request.method == "POST":
- form = AnswerForm(question, request.POST)
- if form.is_valid():
- update_time = datetime.datetime.now()
- answer = Answer(
- question = question,
- author = request.user,
- added_at = update_time,
- wiki = form.cleaned_data['wiki'],
- html = sanitize_html(markdowner.convert(form.cleaned_data['text'])),
- )
- if answer.wiki:
- answer.last_edited_by = answer.author
- answer.last_edited_at = update_time
- answer.wikified_at = update_time
-
- answer.save()
- Question.objects.update_answer_count(question)
-
- question = get_object_or_404(Question, id=id)
- question.last_activity_at = update_time
- question.last_activity_by = request.user
- question.save()
-
- AnswerRevision.objects.create(
- answer = answer,
- revision = 1,
- author = request.user,
- revised_at = update_time,
- summary = CONST['default_version'],
- text = form.cleaned_data['text']
- )
-
- return HttpResponseRedirect(question.get_absolute_url())
-
-def tags(request):
- stag = ""
- is_paginated = True
- sortby = request.GET.get('sort', 'used')
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- if request.method == "GET":
- stag = request.GET.get("q", "").strip()
- if stag is not None:
- objects_list = Paginator(Tag.objects.filter(deleted=False).exclude(used_count=0).extra(where=['name like %s'], params=['%' + stag + '%']), DEFAULT_PAGE_SIZE)
- else:
- if sortby == "name":
- objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("name"), DEFAULT_PAGE_SIZE)
- else:
- objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-used_count"), DEFAULT_PAGE_SIZE)
-
- try:
- tags = objects_list.page(page)
- except (EmptyPage, InvalidPage):
- tags = objects_list.page(objects_list.num_pages)
-
- return render_to_response('tags.html', {
- "tags" : tags,
- "stag" : stag,
- "tab_id" : sortby,
- "keywords" : stag,
- "context" : {
- 'is_paginated' : is_paginated,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': tags.has_previous(),
- 'has_next': tags.has_next(),
- 'previous': tags.previous_page_number(),
- 'next': tags.next_page_number(),
- 'base_url' : '/tags/?sort=%s&' % sortby
- }
-
- }, context_instance=RequestContext(request))
-
-def tag(request, tag):
- return questions(request, tagname=tag)
-
-def vote(request, id):
- """
- vote_type:
- acceptAnswer : 0,
- questionUpVote : 1,
- questionDownVote : 2,
- favorite : 4,
- answerUpVote: 5,
- answerDownVote:6,
- offensiveQuestion : 7,
- offensiveAnswer:8,
- removeQuestion: 9,
- removeAnswer:10
-
- accept answer code:
- response_data['allowed'] = -1, Accept his own answer 0, no allowed - Anonymous 1, Allowed - by default
- response_data['success'] = 0, failed 1, Success - by default
- response_data['status'] = 0, By default 1, Answer has been accepted already(Cancel)
-
- vote code:
- allowed = -3, Don't have enough votes left
- -2, Don't have enough reputation score
- -1, Vote his own post
- 0, no allowed - Anonymous
- 1, Allowed - by default
- status = 0, By default
- 1, Cancel
- 2, Vote is too old to be canceled
-
- offensive code:
- allowed = -3, Don't have enough flags left
- -2, Don't have enough reputation score to do this
- 0, not allowed
- 1, allowed
- status = 0, by default
- 1, can't do it again
- """
- response_data = {
- "allowed": 1,
- "success": 1,
- "status" : 0,
- "count" : 0,
- "message" : ''
- }
-
- def can_vote(vote_score, user):
- if vote_score == 1:
- return can_vote_up(request.user)
- else:
- return can_vote_down(request.user)
-
- try:
- if not request.user.is_authenticated():
- response_data['allowed'] = 0
- response_data['success'] = 0
-
- elif request.is_ajax():
- question = get_object_or_404(Question, id=id)
- vote_type = request.POST.get('type')
-
- #accept answer
- if vote_type == '0':
- answer_id = request.POST.get('postId')
- answer = get_object_or_404(Answer, id=answer_id)
- # make sure question author is current user
- if question.author == request.user:
- # answer user who is also question author is not allow to accept answer
- if answer.author == question.author:
- response_data['success'] = 0
- response_data['allowed'] = -1
- # check if answer has been accepted already
- elif answer.accepted:
- onAnswerAcceptCanceled(answer, request.user)
- response_data['status'] = 1
- else:
- # set other answers in this question not accepted first
- for answer_of_question in Answer.objects.get_answers_from_question(question, request.user):
- if answer_of_question != answer and answer_of_question.accepted:
- onAnswerAcceptCanceled(answer_of_question, request.user)
-
- #make sure retrieve data again after above author changes, they may have related data
- answer = get_object_or_404(Answer, id=answer_id)
- onAnswerAccept(answer, request.user)
- else:
- response_data['allowed'] = 0
- response_data['success'] = 0
- # favorite
- elif vote_type == '4':
- has_favorited = False
- fav_questions = FavoriteQuestion.objects.filter(question=question)
- # if the same question has been favorited before, then delete it
- if fav_questions is not None:
- for item in fav_questions:
- if item.user == request.user:
- item.delete()
- response_data['status'] = 1
- response_data['count'] = len(fav_questions) - 1
- if response_data['count'] < 0:
- response_data['count'] = 0
- has_favorited = True
- # if above deletion has not been executed, just insert a new favorite question
- if not has_favorited:
- new_item = FavoriteQuestion(question=question, user=request.user)
- new_item.save()
- response_data['count'] = FavoriteQuestion.objects.filter(question=question).count()
- Question.objects.update_favorite_count(question)
-
- elif vote_type in ['1', '2', '5', '6']:
- post_id = id
- post = question
- vote_score = 1
- if vote_type in ['5', '6']:
- answer_id = request.POST.get('postId')
- answer = get_object_or_404(Answer, id=answer_id)
- post_id = answer_id
- post = answer
- if vote_type in ['2', '6']:
- vote_score = -1
-
- if post.author == request.user:
- response_data['allowed'] = -1
- elif not can_vote(vote_score, request.user):
- response_data['allowed'] = -2
- elif post.votes.filter(user=request.user).count() > 0:
- vote = post.votes.filter(user=request.user)[0]
- # unvote should be less than certain time
- if (datetime.datetime.now().day - vote.voted_at.day) >= VOTE_RULES['scope_deny_unvote_days']:
- response_data['status'] = 2
- else:
- voted = vote.vote
- if voted > 0:
- # cancel upvote
- onUpVotedCanceled(vote, post, request.user)
-
- else:
- # cancel downvote
- onDownVotedCanceled(vote, post, request.user)
-
- response_data['status'] = 1
- response_data['count'] = post.score
- elif Vote.objects.get_votes_count_today_from_user(request.user) >= VOTE_RULES['scope_votes_per_user_per_day']:
- response_data['allowed'] = -3
- else:
- vote = Vote(user=request.user, content_object=post, vote=vote_score, voted_at=datetime.datetime.now())
- if vote_score > 0:
- # upvote
- onUpVoted(vote, post, request.user)
- else:
- # downvote
- onDownVoted(vote, post, request.user)
-
- votes_left = VOTE_RULES['scope_votes_per_user_per_day'] - Vote.objects.get_votes_count_today_from_user(request.user)
- if votes_left <= VOTE_RULES['scope_warn_votes_left']:
- response_data['message'] = u'%s votes left' % votes_left
- response_data['count'] = post.score
- elif vote_type in ['7', '8']:
- post = question
- post_id = id
- if vote_type == '8':
- post_id = request.POST.get('postId')
- post = get_object_or_404(Answer, id=post_id)
-
- if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= VOTE_RULES['scope_flags_per_user_per_day']:
- response_data['allowed'] = -3
- elif not can_flag_offensive(request.user):
- response_data['allowed'] = -2
- elif post.flagged_items.filter(user=request.user).count() > 0:
- response_data['status'] = 1
- else:
- item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now())
- onFlaggedItem(item, post, request.user)
- response_data['count'] = post.offensive_flag_count
- # send signal when question or answer be marked offensive
- mark_offensive.send(sender=post.__class__, instance=post, mark_by=request.user)
- elif vote_type in ['9', '10']:
- post = question
- post_id = id
- if vote_type == '10':
- post_id = request.POST.get('postId')
- post = get_object_or_404(Answer, id=post_id)
-
- if not can_delete_post(request.user, post):
- response_data['allowed'] = -2
- elif post.deleted:
- onDeleteCanceled(post, request.user)
- response_data['status'] = 1
- else:
- onDeleted(post, request.user)
- delete_post_or_answer.send(sender=post.__class__, instance=post, delete_by=request.user)
- else:
- response_data['success'] = 0
- response_data['message'] = u'Request mode is not supported. Please try again.'
-
- data = simplejson.dumps(response_data)
-
- except Exception, e:
- response_data['message'] = str(e)
- data = simplejson.dumps(response_data)
- return HttpResponse(data, mimetype="application/json")
-
-def users(request):
- is_paginated = True
- sortby = request.GET.get('sort', 'reputation')
- suser = request.REQUEST.get('q', "")
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- if suser == "":
- if sortby == "newest":
- objects_list = Paginator(User.objects.all().order_by('-date_joined'), USERS_PAGE_SIZE)
- elif sortby == "last":
- objects_list = Paginator(User.objects.all().order_by('date_joined'), USERS_PAGE_SIZE)
- elif sortby == "user":
- objects_list = Paginator(User.objects.all().order_by('username'), USERS_PAGE_SIZE)
- # default
- else:
- objects_list = Paginator(User.objects.all().order_by('-reputation'), USERS_PAGE_SIZE)
- base_url = '/users/?sort=%s&' % sortby
- else:
- sortby = "reputation"
- objects_list = Paginator(User.objects.extra(where=['username like %s'], params=['%' + suser + '%']).order_by('-reputation'), USERS_PAGE_SIZE)
- base_url = '/users/?name=%s&sort=%s&' % (suser, sortby)
-
- try:
- users = objects_list.page(page)
- except (EmptyPage, InvalidPage):
- users = objects_list.page(objects_list.num_pages)
-
- return render_to_response('users.html', {
- "users" : users,
- "suser" : suser,
- "keywords" : suser,
- "tab_id" : sortby,
- "context" : {
- 'is_paginated' : is_paginated,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': users.has_previous(),
- 'has_next': users.has_next(),
- 'previous': users.previous_page_number(),
- 'next': users.next_page_number(),
- 'base_url' : base_url
- }
-
- }, context_instance=RequestContext(request))
-
-def user(request, id):
- sort = request.GET.get('sort', 'stats')
- user_view = dict((v.id, v) for v in USER_TEMPLATE_VIEWS).get(sort, USER_TEMPLATE_VIEWS[0])
- from forum import views
- func = getattr(views, user_view.view_name)
- return func(request, id, user_view)
-
-@login_required
-def edit_user(request, id):
- user = get_object_or_404(User, id=id)
- if request.user != user:
- raise Http404
- if request.method == "POST":
- form = EditUserForm(user, request.POST)
- if form.is_valid():
- user.email = sanitize_html(form.cleaned_data['email'])
- 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'])
- user.date_of_birth = sanitize_html(form.cleaned_data['birthday'])
- if len(user.date_of_birth) == 0:
- user.date_of_birth = '1900-01-01'
- user.about = sanitize_html(form.cleaned_data['about'])
-
- user.save()
- # send user updated singal if full fields have been updated
- if user.email and user.real_name and user.website and user.location and \
- user.date_of_birth and user.about:
- user_updated.send(sender=user.__class__, instance=user, updated_by=user)
- return HttpResponseRedirect(user.get_profile_url())
- else:
- form = EditUserForm(user)
- return render_to_response('user_edit.html', {
- 'form' : form,
- }, context_instance=RequestContext(request))
-
-def user_stats(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- questions = Question.objects.extra(
- select={
- 'vote_count' : 'question.score',
- 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s AND f.question_id = question.id',
- 'la_user_id' : 'auth_user.id',
- 'la_username' : 'auth_user.username',
- 'la_user_gold' : 'auth_user.gold',
- 'la_user_silver' : 'auth_user.silver',
- 'la_user_bronze' : 'auth_user.bronze',
- 'la_user_reputation' : 'auth_user.reputation'
- },
- select_params=[user_id],
- tables=['question', 'auth_user'],
- where=['question.deleted = 0 AND question.author_id=%s AND question.last_activity_by_id = auth_user.id'],
- params=[user_id],
- order_by=['-vote_count', '-last_activity_at']
- ).values('vote_count',
- 'favorited_myself',
- 'id',
- 'title',
- 'author_id',
- 'added_at',
- 'answer_accepted',
- 'answer_count',
- 'comment_count',
- 'view_count',
- 'favourite_count',
- 'summary',
- 'tagnames',
- 'vote_up_count',
- 'vote_down_count',
- 'last_activity_at',
- 'la_user_id',
- 'la_username',
- 'la_user_gold',
- 'la_user_silver',
- 'la_user_bronze',
- 'la_user_reputation')[:100]
-
- answered_questions = Question.objects.extra(
- select={
- 'vote_up_count' : 'answer.vote_up_count',
- 'vote_down_count' : 'answer.vote_down_count',
- 'answer_id' : 'answer.id',
- 'accepted' : 'answer.accepted',
- 'vote_count' : 'answer.score',
- 'comment_count' : 'answer.comment_count'
- },
- tables=['question', 'answer'],
- where=['answer.deleted=0 AND answer.author_id=%s AND answer.question_id=question.id'],
- params=[user_id],
- order_by=['-vote_count', '-answer_id'],
- select_params=[user_id]
- ).distinct().values('comment_count',
- 'id',
- 'answer_id',
- 'title',
- 'author_id',
- 'accepted',
- 'vote_count',
- 'answer_count',
- 'vote_up_count',
- 'vote_down_count')[:100]
- up_votes = Vote.objects.get_up_vote_count_from_user(user)
- down_votes = Vote.objects.get_down_vote_count_from_user(user)
- votes_today = Vote.objects.get_votes_count_today_from_user(user)
- votes_total = VOTE_RULES['scope_votes_per_user_per_day']
- tags = user.created_tags.all().order_by('-used_count')[:50]
- awards = Award.objects.extra(
- select={'id': 'badge.id', 'count': 'count(badge_id)', 'name':'badge.name', 'description': 'badge.description', 'type': 'badge.type'},
- tables=['award', 'badge'],
- order_by=['-awarded_at'],
- where=['user_id=%s AND badge_id=badge.id'],
- params=[user.id]
- ).values('id', 'count', 'name', 'description', 'type')
- total_awards = awards.count()
- awards.query.group_by = ['badge_id']
-
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "view_user" : user,
- "questions" : questions,
- "answered_questions" : answered_questions,
- "up_votes" : up_votes,
- "down_votes" : down_votes,
- "total_votes": up_votes + down_votes,
- "votes_today_left": votes_total-votes_today,
- "votes_total_per_day": votes_total,
- "tags" : tags,
- "awards": awards,
- "total_awards" : total_awards,
- }, context_instance=RequestContext(request))
-
-def user_recent(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- def get_type_name(type_id):
- for item in TYPE_ACTIVITY:
- if type_id in item:
- return item[1]
-
- class Event:
- def __init__(self, time, type, title, summary, answer_id, question_id):
- self.time = time
- self.type = get_type_name(type)
- self.type_id = type
- self.title = title
- self.summary = summary
- self.title_link = u'/questions/%s/%s#%s' %(question_id, title, answer_id)\
- if int(answer_id) > 0 else u'/questions/%s/%s' %(question_id, title)
- class AwardEvent:
- def __init__(self, time, type, id):
- self.time = time
- self.type = get_type_name(type)
- self.type_id = type
- self.badge = get_object_or_404(Badge, id=id)
-
- activities = []
- # ask questions
- questions = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'active_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'question'],
- where=['activity.content_type_id = %s AND activity.object_id = ' +
- 'question.id 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(
- 'title',
- 'question_id',
- 'active_at',
- 'activity_type'
- )
- if len(questions) > 0:
- questions = [(Event(q['active_at'], q['activity_type'], q['title'], '', '0', \
- q['question_id'])) for q in questions]
- activities.extend(questions)
-
- # answers
- answers = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'active_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'answer', 'question'],
- where=['activity.content_type_id = %s AND activity.object_id = answer.id AND ' +
- 'answer.question_id=question.id AND activity.user_id=%s AND activity.activity_type=%s'],
- params=[answer_type_id, user_id, TYPE_ACTIVITY_ANSWER],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'active_at',
- 'activity_type'
- )
- if len(answers) > 0:
- answers = [(Event(q['active_at'], q['activity_type'], q['title'], '', q['answer_id'], \
- q['question_id'])) for q in answers]
- activities.extend(answers)
-
- # question comments
- comments = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'comment.object_id',
- 'added_at' : 'comment.added_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'question', 'comment'],
-
- 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'],
- params=[comment_type_id, question_type_id, user_id, TYPE_ACTIVITY_COMMENT_QUESTION],
- order_by=['-comment.added_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'activity_type'
- )
-
- if len(comments) > 0:
- comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \
- q['question_id'])) for q in comments]
- activities.extend(comments)
-
- # answer comments
- comments = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'comment.added_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'question', 'answer', 'comment'],
-
- where=['activity.content_type_id = %s AND activity.object_id = comment.id AND '+
- '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'],
- params=[comment_type_id, answer_type_id, user_id, TYPE_ACTIVITY_COMMENT_ANSWER],
- order_by=['-comment.added_at']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'activity_type'
- )
-
- if len(comments) > 0:
- comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', q['answer_id'], \
- q['question_id'])) for q in comments]
- activities.extend(comments)
-
- # question revisions
- revisions = Activity.objects.extra(
- select={
- 'title' : 'question_revision.title',
- 'question_id' : 'question_revision.question_id',
- 'added_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type',
- 'summary' : 'question_revision.summary'
- },
- tables=['activity', 'question_revision'],
- where=['activity.content_type_id = %s AND activity.object_id = question_revision.id 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],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'activity_type',
- 'summary'
- )
-
- if len(revisions) > 0:
- revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], '0', \
- q['question_id'])) for q in revisions]
- activities.extend(revisions)
-
- # answer revisions
- revisions = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type',
- 'summary' : 'answer_revision.summary'
- },
- tables=['activity', 'answer_revision', 'question', 'answer'],
-
- 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 '+
- 'activity.activity_type=%s'],
- params=[answer_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_ANSWER],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'answer_id',
- 'activity_type',
- 'summary'
- )
-
- if len(revisions) > 0:
- revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], \
- q['answer_id'], q['question_id'])) for q in revisions]
- activities.extend(revisions)
-
- # accepted answers
- accept_answers = Activity.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'added_at' : 'activity.active_at',
- 'activity_type' : 'activity.activity_type',
- },
- 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.question_id=question.id AND activity.activity_type=%s'],
- params=[answer_type_id, user_id, TYPE_ACTIVITY_MARK_ANSWER],
- order_by=['-activity.active_at']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'activity_type',
- )
- if len(accept_answers) > 0:
- accept_answers = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \
- q['question_id'])) for q in accept_answers]
- activities.extend(accept_answers)
- #award history
- awards = Activity.objects.extra(
- select={
- 'badge_id' : 'badge.id',
- 'awarded_at': 'award.awarded_at',
- 'activity_type' : 'activity.activity_type'
- },
- tables=['activity', 'award', 'badge'],
- where=['activity.user_id = award.user_id AND activity.user_id = %s AND '+
- 'award.badge_id=badge.id AND activity.object_id=award.id AND activity.activity_type=%s'],
- params=[user_id, TYPE_ACTIVITY_PRIZE],
- order_by=['-activity.active_at']
- ).values(
- 'badge_id',
- 'awarded_at',
- 'activity_type'
- )
- if len(awards) > 0:
- awards = [(AwardEvent(q['awarded_at'], q['activity_type'], q['badge_id'])) for q in awards]
- activities.extend(awards)
-
- activities.sort(lambda x,y: cmp(y.time, x.time))
-
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "view_user" : user,
- "activities" : activities[:user_view.data_size]
- }, context_instance=RequestContext(request))
-
-def user_responses(request, user_id, user_view):
- """
- We list answers for question, comments, and answer accepted by others for this user.
- """
- class Response:
- def __init__(self, type, title, question_id, answer_id, time, username, user_id, content):
- self.type = type
- self.title = title
- self.titlelink = u'/questions/%s/%s#%s' % (question_id, title, answer_id)
- self.time = time
- self.userlink = u'/users/%s/%s/' % (user_id, username)
- self.username = username
- self.content = u'%s ...' % strip_tags(content)[:300]
-
- def __unicode__(self):
- return u'%s %s' % (self.type, self.titlelink)
-
- user = get_object_or_404(User, id=user_id)
- responses = []
- answers = Answer.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'answer.added_at',
- 'html' : 'answer.html',
- 'username' : 'auth_user.username',
- 'user_id' : 'auth_user.id'
- },
- select_params=[user_id],
- tables=['answer', 'question', 'auth_user'],
- where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+
- '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']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'html',
- 'username',
- 'user_id'
- )
- if len(answers) > 0:
- answers = [(Response(TYPE_RESPONSE['QUESTION_ANSWERED'], a['title'], a['question_id'],
- a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
- responses.extend(answers)
-
-
- # question comments
- comments = Comment.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'comment.object_id',
- 'added_at' : 'comment.added_at',
- 'comment' : 'comment.comment',
- 'username' : 'auth_user.username',
- '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 '+
- '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']
- ).values(
- 'title',
- 'question_id',
- 'added_at',
- 'comment',
- 'username',
- 'user_id'
- )
-
- if len(comments) > 0:
- comments = [(Response(TYPE_RESPONSE['QUESTION_COMMENTED'], c['title'], c['question_id'],
- '', c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments]
- responses.extend(comments)
-
- # answer comments
- comments = Comment.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'comment.added_at',
- 'comment' : 'comment.comment',
- 'username' : 'auth_user.username',
- 'user_id' : 'auth_user.id'
- },
- tables=['answer', 'auth_user', 'comment', 'question'],
- where=['answer.deleted = 0 AND answer.author_id = %s AND comment.object_id=answer.id AND '+
- 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id '+
- 'AND question.id = answer.question_id'],
- params=[user_id, answer_type_id, user_id],
- order_by=['-comment.added_at']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'comment',
- 'username',
- 'user_id'
- )
-
- if len(comments) > 0:
- comments = [(Response(TYPE_RESPONSE['ANSWER_COMMENTED'], c['title'], c['question_id'],
- c['answer_id'], c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments]
- responses.extend(comments)
-
- # answer has been accepted
- answers = Answer.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'added_at' : 'answer.accepted_at',
- 'html' : 'answer.html',
- 'username' : 'auth_user.username',
- 'user_id' : 'auth_user.id'
- },
- select_params=[user_id],
- tables=['answer', 'question', 'auth_user'],
- where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+
- 'answer.author_id = %s AND answer.accepted=1 AND question.author_id=auth_user.id'],
- params=[user_id],
- order_by=['-answer.id']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'added_at',
- 'html',
- 'username',
- 'user_id'
- )
- if len(answers) > 0:
- answers = [(Response(TYPE_RESPONSE['ANSWER_ACCEPTED'], a['title'], a['question_id'],
- a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
- responses.extend(answers)
-
- # sort posts by time
- responses.sort(lambda x,y: cmp(y.time, x.time))
-
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "view_user" : user,
- "responses" : responses[:user_view.data_size],
-
- }, context_instance=RequestContext(request))
-
-def user_votes(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- if not can_view_user_votes(request.user, user):
- raise Http404
- votes = []
- question_votes = Vote.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 0,
- 'voted_at' : 'vote.voted_at',
- 'vote' : 'vote',
- },
- select_params=[user_id],
- tables=['vote', 'question', 'auth_user'],
- where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = question.id '+
- 'AND vote.user_id=auth_user.id'],
- params=[question_type_id, user_id],
- order_by=['-vote.id']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'voted_at',
- 'vote',
- )
- if(len(question_votes) > 0):
- votes.extend(question_votes)
-
- answer_votes = Vote.objects.extra(
- select={
- 'title' : 'question.title',
- 'question_id' : 'question.id',
- 'answer_id' : 'answer.id',
- 'voted_at' : 'vote.voted_at',
- 'vote' : 'vote',
- },
- select_params=[user_id],
- tables=['vote', 'answer', 'question', 'auth_user'],
- where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = answer.id '+
- 'AND answer.question_id = question.id AND vote.user_id=auth_user.id'],
- params=[answer_type_id, user_id],
- order_by=['-vote.id']
- ).values(
- 'title',
- 'question_id',
- 'answer_id',
- 'voted_at',
- 'vote',
- )
- if(len(answer_votes) > 0):
- votes.extend(answer_votes)
- votes.sort(lambda x,y: cmp(y['voted_at'], x['voted_at']))
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "view_user" : user,
- "votes" : votes[:user_view.data_size]
-
- }, context_instance=RequestContext(request))
-
-def user_reputation(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- reputation = Repute.objects.extra(
- select={'positive': 'sum(positive)', 'negative': 'sum(negative)', 'question_id':'question_id',
- 'title': 'question.title'},
- tables=['repute', 'question'],
- order_by=['-reputed_at'],
- where=['user_id=%s AND question_id=question.id'],
- params=[user.id]
- ).values('positive', 'negative', 'question_id', 'title', 'reputed_at', 'reputation')
-
- reputation.query.group_by = ['question_id']
-
-
- rep_list = []
- for rep in Repute.objects.filter(user=user).order_by('reputed_at'):
- dic = '[%s,%s]' % (calendar.timegm(rep.reputed_at.timetuple()) * 1000, rep.reputation)
- rep_list.append(dic)
- reps = ','.join(rep_list)
- reps = '[%s]' % reps
-
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "view_user" : user,
- "reputation" : reputation,
- "reps" : reps
- }, context_instance=RequestContext(request))
-
-def user_favorites(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- questions = Question.objects.extra(
- select={
- 'vote_count' : 'question.vote_up_count + question.vote_down_count',
- 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s '+
- 'AND f.question_id = question.id',
- 'la_user_id' : 'auth_user.id',
- 'la_username' : 'auth_user.username',
- 'la_user_gold' : 'auth_user.gold',
- 'la_user_silver' : 'auth_user.silver',
- 'la_user_bronze' : 'auth_user.bronze',
- 'la_user_reputation' : 'auth_user.reputation'
- },
- select_params=[user_id],
- tables=['question', 'auth_user', 'favorite_question'],
- where=['question.deleted = 0 AND question.last_activity_by_id = auth_user.id '+
- 'AND favorite_question.question_id = question.id AND favorite_question.user_id = %s'],
- params=[user_id],
- order_by=['-vote_count', '-question.id']
- ).values('vote_count',
- 'favorited_myself',
- 'id',
- 'title',
- 'author_id',
- 'added_at',
- 'answer_accepted',
- 'answer_count',
- 'comment_count',
- 'view_count',
- 'favourite_count',
- 'summary',
- 'tagnames',
- 'vote_up_count',
- 'vote_down_count',
- 'last_activity_at',
- 'la_user_id',
- 'la_username',
- 'la_user_gold',
- 'la_user_silver',
- 'la_user_bronze',
- 'la_user_reputation')
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "questions" : questions[:user_view.data_size],
- "view_user" : user
- }, context_instance=RequestContext(request))
-
-
-def user_preferences(request, user_id, user_view):
- user = get_object_or_404(User, id=user_id)
- return render_to_response(user_view.template_file,{
- "tab_name" : user_view.id,
- "tab_description" : user_view.tab_description,
- "page_title" : user_view.page_title,
- "view_user" : user,
- }, context_instance=RequestContext(request))
-
-def question_comments(request, id):
- question = get_object_or_404(Question, id=id)
- user = request.user
- return __comments(request, question, 'question', user)
-
-def answer_comments(request, id):
- answer = get_object_or_404(Answer, id=id)
- user = request.user
- return __comments(request, answer, 'answer', user)
-
-def __comments(request, obj, type, user):
- # only support get comments by ajax now
- if request.is_ajax():
- if request.method == "GET":
- return __generate_comments_json(obj, type, user)
- elif request.method == "POST":
- comment_data = request.POST.get('comment')
- comment = Comment(content_object=obj, comment=comment_data, user=request.user)
- comment.save()
- obj.comment_count = obj.comment_count + 1
- obj.save()
- return __generate_comments_json(obj, type, user)
-
-def __generate_comments_json(obj, type, user):
- comments = obj.comments.all().order_by('-id')
- # {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
- json_comments = []
- for comment in comments:
- comment_user = comment.user
- delete_url = ""
- if user != None and auth.can_delete_comment(user, comment):
- #/posts/392845/comments/219852/delete
- delete_url = "/" + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id)
- json_comments.append({"id" : comment.id,
- "object_id" : obj.id,
- "add_date" : comment.added_at.strftime('%Y-%m-%d'),
- "text" : comment.comment,
- "user_display_name" : comment_user.username,
- "user_url" : "/users/%s/%s" % (comment_user.id, comment_user.username),
- "delete_url" : delete_url
- })
-
- data = simplejson.dumps(json_comments)
- return HttpResponse(data, mimetype="application/json")
-
-def delete_question_comment(request, question_id, comment_id):
- if request.is_ajax():
- question = get_object_or_404(Question, id=question_id)
- comment = get_object_or_404(Comment, id=comment_id)
-
- question.comments.remove(comment)
- question.comment_count = question.comment_count - 1
- question.save()
- user = request.user
- return __generate_comments_json(question, 'question', user)
-
-def delete_answer_comment(request, answer_id, comment_id):
- if request.is_ajax():
- answer = get_object_or_404(Answer, id=answer_id)
- comment = get_object_or_404(Comment, id=comment_id)
-
- answer.comments.remove(comment)
- answer.comment_count = answer.comment_count - 1
- answer.save()
- user = request.user
- return __generate_comments_json(answer, 'answer', user)
-
-def logout(request):
- url = request.GET.get('next')
- return render_to_response('logout.html', {
- 'next' : url,
- }, context_instance=RequestContext(request))
-
-def badges(request):
- badges = Badge.objects.all().order_by('type')
- my_badges = []
- if request.user.is_authenticated():
- my_badges = Award.objects.filter(user=request.user)
- my_badges.query.group_by = ['badge_id']
-
- return render_to_response('badges.html', {
- 'badges' : badges,
- 'mybadges' : my_badges,
- }, context_instance=RequestContext(request))
-
-def badge(request, id):
- badge = get_object_or_404(Badge, id=id)
- awards = Award.objects.extra(
- select={'id': 'auth_user.id',
- 'name': 'auth_user.username',
- 'rep':'auth_user.reputation',
- 'gold': 'auth_user.gold',
- 'silver': 'auth_user.silver',
- 'bronze': 'auth_user.bronze'},
- tables=['award', 'auth_user'],
- where=['badge_id=%s AND user_id=auth_user.id'],
- params=[id]
- ).values('id').distinct()
-
- return render_to_response('badge.html', {
- 'awards' : awards,
- 'badge' : badge,
- }, context_instance=RequestContext(request))
-
-def read_message(request):
- if request.method == "POST":
- if request.POST['formdata'] == 'required':
- request.session['message_silent'] = 1
-
- if request.user.is_authenticated():
- request.user.delete_messages()
- return HttpResponse('')
-
-def upload(request):
- class FileTypeNotAllow(Exception):
- pass
- class FileSizeNotAllow(Exception):
- pass
- class UploadPermissionNotAuthorized(Exception):
- pass
-
- #<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>
- xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>"
-
- try:
- f = request.FILES['file-upload']
- # check upload permission
- if not can_upload_files(request.user):
- raise UploadPermissionNotAuthorized
-
- # check file type
- file_name_suffix = os.path.splitext(f.name)[1].lower()
- if not file_name_suffix in settings.ALLOW_FILE_TYPES:
- raise FileTypeNotAllow
-
- # genetate new file name
- new_file_name = str(time.time()).replace('.', str(random.randint(0,100000))) + file_name_suffix
- # use default storage to store file
- default_storage.save(new_file_name, f)
- # check file size
- # byte
- size = default_storage.size(new_file_name)
- if size > settings.ALLOW_MAX_FILE_SIZE:
- default_storage.delete(new_file_name)
- raise FileSizeNotAllow
-
- result = xml_template % ('Good', '', default_storage.url(new_file_name))
- except UploadPermissionNotAuthorized:
- result = xml_template % ('', _('uploading images is limited to users with >60 reputation points'), '')
- except FileTypeNotAllow:
- result = xml_template % ('', _("allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"), '')
- except FileSizeNotAllow:
- result = xml_template % ('', _("maximum upload file size is %sK") % settings.ALLOW_MAX_FILE_SIZE / 1024, '')
- except Exception as e:
- result = xml_template % ('', _('Error uploading file. Please contact the site administrator. Thank you. %s' % e), '')
-
- return HttpResponse(result, mimetype="application/xml")
-
-def books(request):
- return HttpResponseRedirect("/books/mysql-zhaoyang")
-
-def book(request, short_name, unanswered=False):
- """
- 1. questions list
- 2. book info
- 3. author info and blog rss items
- """
- """
- List of Questions, Tagged questions, and Unanswered questions.
- """
- books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
- match_count = len(books)
- if match_count == 0 :
- raise Http404
- else:
- # the book info
- book = books[0]
- # get author info
- author_info = BookAuthorInfo.objects.get(book=book)
- # get author rss info
- author_rss = BookAuthorRss.objects.filter(book=book)
-
- # get pagesize from session, if failed then get default value
- user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
- # set pagesize equal to logon user specified value in database
- if request.user.is_authenticated() and request.user.questions_per_page > 0:
- user_page_size = request.user.questions_per_page
-
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- view_id = "latest"
- orderby = "-added_at"
-
- # check if request is from tagged questions
- if unanswered:
- # check if request is from unanswered questions
- # Article.objects.filter(publications__id__exact=1)
- objects = Question.objects.filter(book__id__exact=book.id, deleted=False, answer_count=0).order_by(orderby)
- else:
- objects = Question.objects.filter(book__id__exact=book.id, deleted=False).order_by(orderby)
-
- # RISK - inner join queries
- objects = objects.select_related();
- objects_list = Paginator(objects, user_page_size)
- questions = objects_list.page(page)
-
- return render_to_response('book.html', {
- "book" : book,
- "author_info" : author_info,
- "author_rss" : author_rss,
- "questions" : questions,
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url' : request.path + '?sort=%s&' % view_id,
- 'pagesize' : user_page_size
- }
- }, context_instance=RequestContext(request))
-
-@login_required
-def ask_book(request, short_name):
- if request.method == "POST":
- form = AskForm(request.POST)
- if form.is_valid():
- added_at = datetime.datetime.now()
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- question = Question(
- title = strip_tags(form.cleaned_data['title']),
- author = request.user,
- added_at = added_at,
- last_activity_at = added_at,
- last_activity_by = request.user,
- wiki = form.cleaned_data['wiki'],
- tagnames = form.cleaned_data['tags'].strip(),
- html = html,
- summary = strip_tags(html)[:120]
- )
- if question.wiki:
- question.last_edited_by = question.author
- question.last_edited_at = added_at
- question.wikified_at = added_at
-
- question.save()
-
- # create the first revision
- QuestionRevision.objects.create(
- question = question,
- revision = 1,
- title = question.title,
- author = request.user,
- revised_at = added_at,
- tagnames = question.tagnames,
- summary = CONST['default_version'],
- text = form.cleaned_data['text']
- )
-
- books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
- match_count = len(books)
- if match_count == 1:
- # the book info
- book = books[0]
- book.questions.add(question)
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- form = AskForm()
-
- tags = _get_tags_cache_json()
- return render_to_response('ask.html', {
- 'form' : form,
- 'tags' : tags,
- }, context_instance=RequestContext(request))
-
-def search(request):
- """
- Search by question, user and tag keywords.
- For questions now we only search keywords in question title.
- """
- if request.method == "GET":
- keywords = request.GET.get("q")
- search_type = request.GET.get("t")
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
- if keywords is None:
- return HttpResponseRedirect('/')
- if search_type == 'tag':
- return HttpResponseRedirect('/tags/?q=%s&page=%s' % (keywords.strip(), page))
- elif search_type == "user":
- return HttpResponseRedirect('/users/?q=%s&page=%s' % (keywords.strip(), page))
- elif search_type == "question":
-
- template_file = "questions.html"
- # Set flag to False by default. If it is equal to True, then need to be saved.
- pagesize_changed = False
- # get pagesize from session, if failed then get default value
- user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
- # set pagesize equal to logon user specified value in database
- if request.user.is_authenticated() and request.user.questions_per_page > 0:
- user_page_size = request.user.questions_per_page
-
- try:
- page = int(request.GET.get('page', '1'))
- # get new pagesize from UI selection
- pagesize = int(request.GET.get('pagesize', user_page_size))
- if pagesize <> user_page_size:
- pagesize_changed = True
-
- except ValueError:
- page = 1
- pagesize = user_page_size
-
- # save this pagesize to user database
- if pagesize_changed:
- request.session["pagesize"] = pagesize
- if request.user.is_authenticated():
- user = request.user
- user.questions_per_page = pagesize
- user.save()
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- view_id = "latest"
- orderby = "-added_at"
-
- objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby)
-
- # RISK - inner join queries
- objects = objects.select_related();
- objects_list = Paginator(objects, pagesize)
- questions = objects_list.page(page)
-
- # Get related tags from this page objects
- related_tags = []
- for question in questions.object_list:
- tags = list(question.tags.all())
- for tag in tags:
- if tag not in related_tags:
- related_tags.append(tag)
-
- return render_to_response(template_file, {
- "questions" : questions,
- "tab_id" : view_id,
- "questions_count" : objects_list.count,
- "tags" : related_tags,
- "searchtag" : None,
- "searchtitle" : keywords,
- "keywords" : keywords,
- "is_unanswered" : False,
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url' : request.path + '?t=question&q=%s&sort=%s&' % (keywords, view_id),
- 'pagesize' : pagesize
- }}, context_instance=RequestContext(request))
-
- else:
- raise Http404
-
+# encoding:utf-8
+import os.path
+import time, datetime, calendar, random
+import logging
+from urllib import quote, unquote
+from django.conf import settings
+from django.core.files.storage import default_storage
+from django.shortcuts import render_to_response, get_object_or_404
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponseRedirect, HttpResponse,Http404
+from django.core.paginator import Paginator, EmptyPage, InvalidPage
+from django.template import RequestContext
+from django.utils.html import *
+from django.utils import simplejson
+from django.core import serializers
+from django.db import transaction
+from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import ugettext as _
+
+from utils.html import sanitize_html
+from markdown2 import Markdown
+#from lxml.html.diff import htmldiff
+from forum.diff import textDiff as htmldiff
+from forum.forms import *
+from forum.models import *
+from forum.auth import *
+from forum.const import *
+from forum.user import *
+from forum import auth
+
+# used in index page
+INDEX_PAGE_SIZE = 20
+INDEX_AWARD_SIZE = 15
+INDEX_TAGS_SIZE = 100
+# used in tags list
+DEFAULT_PAGE_SIZE = 60
+# used in questions
+QUESTIONS_PAGE_SIZE = 10
+# used in users
+USERS_PAGE_SIZE = 35
+# used in answers
+ANSWERS_PAGE_SIZE = 10
+markdowner = Markdown(html4tags=True)
+question_type = ContentType.objects.get_for_model(Question)
+answer_type = ContentType.objects.get_for_model(Answer)
+comment_type = ContentType.objects.get_for_model(Comment)
+question_revision_type = ContentType.objects.get_for_model(QuestionRevision)
+answer_revision_type = ContentType.objects.get_for_model(AnswerRevision)
+repute_type =ContentType.objects.get_for_model(Repute)
+question_type_id = question_type.id
+answer_type_id = answer_type.id
+comment_type_id = comment_type.id
+question_revision_type_id = question_revision_type.id
+answer_revision_type_id = answer_revision_type.id
+repute_type_id = repute_type.id
+def _get_tags_cache_json():
+ tags = Tag.objects.filter(deleted=False).all()
+ tags_list = []
+ for tag in tags:
+ dic = {'n': tag.name, 'c': tag.used_count }
+ tags_list.append(dic)
+ tags = simplejson.dumps(tags_list)
+ return tags
+
+def index(request):
+ view_id = request.GET.get('sort', None)
+ view_dic = {
+ "latest":"-last_activity_at",
+ "hottest":"-answer_count",
+ "mostvoted":"-score",
+ "trans": "-last_activity_at"
+ }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "latest"
+ orderby = "-last_activity_at"
+ # group questions by author_id of 28,29
+ if view_id == 'trans':
+ questions = Question.objects.get_translation_questions(orderby, INDEX_PAGE_SIZE)
+ else:
+ questions = Question.objects.get_questions_by_pagesize(orderby, INDEX_PAGE_SIZE)
+ # RISK - inner join queries
+ questions = questions.select_related()
+ tags = Tag.objects.get_valid_tags(INDEX_TAGS_SIZE)
+
+ awards = Award.objects.get_recent_awards()
+
+ return render_to_response('index.html', {
+ "questions" : questions,
+ "tab_id" : view_id,
+ "tags" : tags,
+ "awards" : awards[:INDEX_AWARD_SIZE],
+ }, context_instance=RequestContext(request))
+
+def about(request):
+ return render_to_response('about.html', context_instance=RequestContext(request))
+
+def faq(request):
+ return render_to_response('faq.html', context_instance=RequestContext(request))
+
+def privacy(request):
+ return render_to_response('privacy.html', context_instance=RequestContext(request))
+
+def unanswered(request):
+ return questions(request, unanswered=True)
+
+def questions(request, tagname=None, unanswered=False):
+ """
+ List of Questions, Tagged questions, and Unanswered questions.
+ """
+ # template file
+ # "questions.html" or "unanswered.html"
+ template_file = "questions.html"
+ # Set flag to False by default. If it is equal to True, then need to be saved.
+ pagesize_changed = False
+ # get pagesize from session, if failed then get default value
+ pagesize = request.session.get("pagesize",10)
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ view_id = request.GET.get('sort', None)
+ view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "latest"
+ orderby = "-added_at"
+
+ # check if request is from tagged questions
+ if tagname is not None:
+ objects = Question.objects.get_questions_by_tag(tagname, orderby)
+ elif unanswered:
+ #check if request is from unanswered questions
+ template_file = "unanswered.html"
+ objects = Question.objects.get_unanswered_questions(orderby)
+ else:
+ objects = Question.objects.get_questions(orderby)
+
+ # RISK - inner join queries
+ objects = objects.select_related(depth=1);
+ objects_list = Paginator(objects, pagesize)
+ questions = objects_list.page(page)
+
+ # Get related tags from this page objects
+ if questions.object_list.count() > 0:
+ related_tags = Tag.objects.get_tags_by_questions(questions.object_list)
+ else:
+ related_tags = None
+ return render_to_response(template_file, {
+ "questions" : questions,
+ "tab_id" : view_id,
+ "questions_count" : objects_list.count,
+ "tags" : related_tags,
+ "searchtag" : tagname,
+ "is_unanswered" : unanswered,
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?sort=%s&' % view_id,
+ 'pagesize' : pagesize
+ }}, context_instance=RequestContext(request))
+
+def create_new_answer( question=None, author=None,\
+ added_at=None, wiki=False,\
+ text='', email_notify=False):
+
+ html = sanitize_html(markdowner.convert(text))
+
+ #create answer
+ answer = Answer(
+ question = question,
+ author = author,
+ added_at = added_at,
+ wiki = wiki,
+ html = html
+ )
+ if answer.wiki:
+ answer.last_edited_by = answer.author
+ answer.last_edited_at = added_at
+ answer.wikified_at = added_at
+
+ answer.save()
+
+ #update question data
+ question.last_activity_at = added_at
+ question.last_activity_by = author
+ question.save()
+ Question.objects.update_answer_count(question)
+
+ #update revision
+ AnswerRevision.objects.create(
+ answer = answer,
+ revision = 1,
+ author = author,
+ revised_at = added_at,
+ summary = CONST['default_version'],
+ text = text
+ )
+
+ #set notification/delete
+ if email_notify:
+ try:
+ EmailFeed.objects.get(feed_id = question.id, subscriber_id = author.id, feed_content_type=question_type)
+ except EmailFeed.DoesNotExist:
+ feed = EmailFeed(content = question, subscriber = author)
+ feed.save()
+ else:
+ #not sure if this is necessary. ajax should take care of this...
+ try:
+ feed = Email.objects.get(feed_id = question.id, subscriber_id = author.id, feed_content_type=question_type)
+ feed.delete()
+ except:
+ pass
+
+def create_new_question(title=None,author=None,added_at=None,
+ wiki=False,tagnames=None,summary=None,
+ text=None):
+ """this is not a view
+ and maybe should become one of the methods on Question object?
+ """
+ html = sanitize_html(markdowner.convert(text))
+ question = Question(
+ title = title,
+ author = author,
+ added_at = added_at,
+ last_activity_at = added_at,
+ last_activity_by = author,
+ wiki = wiki,
+ tagnames = tagnames,
+ html = html,
+ summary = summary
+ )
+ if question.wiki:
+ question.last_edited_by = question.author
+ question.last_edited_at = added_at
+ question.wikified_at = added_at
+
+ question.save()
+
+ # create the first revision
+ QuestionRevision.objects.create(
+ question = question,
+ revision = 1,
+ title = question.title,
+ author = author,
+ revised_at = added_at,
+ tagnames = question.tagnames,
+ summary = CONST['default_version'],
+ text = text
+ )
+ return question
+
+#TODO: allow anynomus user to ask question by providing email and username.
+#@login_required
+def ask(request):
+ if request.method == "POST":
+ form = AskForm(request.POST)
+ if form.is_valid():
+
+ added_at = datetime.datetime.now()
+ title = strip_tags(form.cleaned_data['title'])
+ wiki = form.cleaned_data['wiki']
+ tagnames = form.cleaned_data['tags'].strip()
+ text = form.cleaned_data['text']
+ html = sanitize_html(markdowner.convert(text))
+ summary = strip_tags(html)[:120]
+
+ if request.user.is_authenticated():
+ author = request.user
+
+ question = create_new_question(
+ title = title,
+ author = author,
+ added_at = added_at,
+ wiki = wiki,
+ tagnames = tagnames,
+ summary = summary,
+ text = text
+ )
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ request.session.flush()
+ session_key = request.session.session_key
+ question = AnonymousQuestion(
+ session_key = session_key,
+ title = title,
+ tagnames = tagnames,
+ wiki = wiki,
+ text = text,
+ summary = summary,
+ added_at = added_at,
+ ip_addr = request.META['REMOTE_ADDR'],
+ )
+ question.save()
+ return HttpResponseRedirect('%s%s%s' % ( _('/account/'),_('signin/'),('newquestion/')))
+ else:
+ form = AskForm()
+
+ tags = _get_tags_cache_json()
+ return render_to_response('ask.html', {
+ 'form' : form,
+ 'tags' : tags,
+ }, context_instance=RequestContext(request))
+
+def question(request, id):
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+ view_id = request.GET.get('sort', 'votes')
+ view_dic = {"latest":"-added_at", "oldest":"added_at", "votes":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "votes"
+ orderby = "-score"
+
+ question = get_object_or_404(Question, id=id)
+ if question.deleted and not can_view_deleted_post(request.user, question):
+ raise Http404
+ answer_form = AnswerForm(question,request.user)
+ answers = Answer.objects.get_answers_from_question(question, request.user)
+ answers = answers.select_related(depth=1)
+
+ favorited = question.has_favorite_by_user(request.user)
+ question_vote = question.votes.select_related().filter(user=request.user)
+ if question_vote is not None and question_vote.count() > 0:
+ question_vote = question_vote[0]
+
+ user_answer_votes = {}
+ for answer in answers:
+ vote = answer.get_user_vote(request.user)
+ if vote is not None and not user_answer_votes.has_key(answer.id):
+ vote_value = -1
+ if vote.is_upvote():
+ vote_value = 1
+ user_answer_votes[answer.id] = vote_value
+
+
+ if answers is not None:
+ answers = answers.order_by("-accepted", orderby)
+
+ filtered_answers = []
+ for answer in answers:
+ if answer.deleted == True:
+ if answer.author_id == request.user.id:
+ filtered_answers.append(answer)
+ else:
+ filtered_answers.append(answer)
+
+ objects_list = Paginator(filtered_answers, ANSWERS_PAGE_SIZE)
+ page_objects = objects_list.page(page)
+ # update view count
+ Question.objects.update_view_count(question)
+ return render_to_response('question.html', {
+ "question" : question,
+ "question_vote" : question_vote,
+ "question_comment_count":question.comments.count(),
+ "answer" : answer_form,
+ "answers" : page_objects.object_list,
+ "user_answer_votes": user_answer_votes,
+ "tags" : question.tags.all(),
+ "tab_id" : view_id,
+ "favorited" : favorited,
+ "similar_questions" : Question.objects.get_similar_questions(question),
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': page_objects.has_previous(),
+ 'has_next': page_objects.has_next(),
+ 'previous': page_objects.previous_page_number(),
+ 'next': page_objects.next_page_number(),
+ 'base_url' : request.path + '?sort=%s&' % view_id,
+ 'extend_url' : "#sort-top"
+ }
+ }, context_instance=RequestContext(request))
+
+@login_required
+def close(request, id):
+ question = get_object_or_404(Question, id=id)
+ if not can_close_question(request.user, question):
+ return HttpResponse('Permission denied.')
+ if request.method == 'POST':
+ form = CloseForm(request.POST)
+ if form.is_valid():
+ reason = form.cleaned_data['reason']
+ question.closed = True
+ question.closed_by = request.user
+ question.closed_at = datetime.datetime.now()
+ question.close_reason = reason
+ question.save()
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ form = CloseForm()
+ return render_to_response('close.html', {
+ 'form' : form,
+ 'question' : question,
+ }, context_instance=RequestContext(request))
+
+@login_required
+def reopen(request, id):
+ question = get_object_or_404(Question, id=id)
+ # open question
+ if not can_reopen_question(request.user, question):
+ return HttpResponse('Permission denied.')
+ if request.method == 'POST' :
+ Question.objects.filter(id=question.id).update(closed=False,
+ closed_by=None, closed_at=None, close_reason=None)
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ return render_to_response('reopen.html', {
+ 'question' : question,
+ }, context_instance=RequestContext(request))
+
+@login_required
+def edit_question(request, id):
+ question = get_object_or_404(Question, id=id)
+ if question.deleted and not can_view_deleted_post(request.user, question):
+ raise Http404
+ if can_edit_post(request.user, question):
+ return _edit_question(request, question)
+ elif can_retag_questions(request.user):
+ return _retag_question(request, question)
+ else:
+ raise Http404
+
+def _retag_question(request, question):
+ if request.method == 'POST':
+ form = RetagQuestionForm(question, request.POST)
+ if form.is_valid():
+ if form.has_changed():
+ latest_revision = question.get_latest_revision()
+ retagged_at = datetime.datetime.now()
+ # Update the Question itself
+ Question.objects.filter(id=question.id).update(
+ tagnames = form.cleaned_data['tags'],
+ last_edited_at = retagged_at,
+ last_edited_by = request.user,
+ last_activity_at = retagged_at,
+ last_activity_by = request.user
+ )
+ # Update the Question's tag associations
+ tags_updated = Question.objects.update_tags(question,
+ form.cleaned_data['tags'], request.user)
+ # Create a new revision
+ QuestionRevision.objects.create(
+ question = question,
+ title = latest_revision.title,
+ author = request.user,
+ revised_at = retagged_at,
+ tagnames = form.cleaned_data['tags'],
+ summary = CONST['retagged'],
+ text = latest_revision.text
+ )
+ # send tags updated singal
+ tags_updated.send(sender=question.__class__, question=question)
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ form = RetagQuestionForm(question)
+ return render_to_response('question_retag.html', {
+ 'question': question,
+ 'form' : form,
+ 'tags' : _get_tags_cache_json(),
+ }, context_instance=RequestContext(request))
+
+
+def _edit_question(request, question):
+ latest_revision = question.get_latest_revision()
+ revision_form = None
+ if request.method == 'POST':
+ if 'select_revision' in request.POST:
+ # user has changed revistion number
+ revision_form = RevisionForm(question, latest_revision, request.POST)
+ if revision_form.is_valid():
+ # Replace with those from the selected revision
+ form = EditQuestionForm(question,
+ QuestionRevision.objects.get(question=question,
+ revision=revision_form.cleaned_data['revision']))
+ else:
+ form = EditQuestionForm(question, latest_revision, request.POST)
+ else:
+ # Always check modifications against the latest revision
+ form = EditQuestionForm(question, latest_revision, request.POST)
+ if form.is_valid():
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
+ if form.has_changed():
+ edited_at = datetime.datetime.now()
+ tags_changed = (latest_revision.tagnames !=
+ form.cleaned_data['tags'])
+ tags_updated = False
+ # Update the Question itself
+ updated_fields = {
+ 'title': form.cleaned_data['title'],
+ 'last_edited_at': edited_at,
+ 'last_edited_by': request.user,
+ 'last_activity_at': edited_at,
+ 'last_activity_by': request.user,
+ 'tagnames': form.cleaned_data['tags'],
+ 'summary': strip_tags(html)[:120],
+ 'html': html,
+ }
+
+ # only save when it's checked
+ # because wiki doesn't allow to be edited if last version has been enabled already
+ # and we make sure this in forms.
+ if ('wiki' in form.cleaned_data and
+ form.cleaned_data['wiki']):
+ updated_fields['wiki'] = True
+ updated_fields['wikified_at'] = edited_at
+
+ Question.objects.filter(
+ id=question.id).update(**updated_fields)
+ # Update the Question's tag associations
+ if tags_changed:
+ tags_updated = Question.objects.update_tags(
+ question, form.cleaned_data['tags'], request.user)
+ # Create a new revision
+ revision = QuestionRevision(
+ question = question,
+ title = form.cleaned_data['title'],
+ author = request.user,
+ revised_at = edited_at,
+ tagnames = form.cleaned_data['tags'],
+ text = form.cleaned_data['text'],
+ )
+ if form.cleaned_data['summary']:
+ revision.summary = form.cleaned_data['summary']
+ else:
+ revision.summary = 'No.%s Revision' % latest_revision.revision
+ revision.save()
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+
+ revision_form = RevisionForm(question, latest_revision)
+ form = EditQuestionForm(question, latest_revision)
+ return render_to_response('question_edit.html', {
+ 'question': question,
+ 'revision_form': revision_form,
+ 'form' : form,
+ 'tags' : _get_tags_cache_json()
+ }, context_instance=RequestContext(request))
+
+
+@login_required
+def edit_answer(request, id):
+ answer = get_object_or_404(Answer, id=id)
+ if answer.deleted and not can_view_deleted_post(request.user, answer):
+ raise Http404
+ elif not can_edit_post(request.user, answer):
+ raise Http404
+ else:
+ latest_revision = answer.get_latest_revision()
+ if request.method == "POST":
+ if 'select_revision' in request.POST:
+ # user has changed revistion number
+ revision_form = RevisionForm(answer, latest_revision, request.POST)
+ if revision_form.is_valid():
+ # Replace with those from the selected revision
+ form = EditAnswerForm(answer,
+ AnswerRevision.objects.get(answer=answer,
+ revision=revision_form.cleaned_data['revision']))
+ else:
+ form = EditAnswerForm(answer, latest_revision, request.POST)
+ else:
+ form = EditAnswerForm(answer, latest_revision, request.POST)
+ if form.is_valid():
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
+ if form.has_changed():
+ edited_at = datetime.datetime.now()
+ updated_fields = {
+ 'last_edited_at': edited_at,
+ 'last_edited_by': request.user,
+ 'html': html,
+ }
+ Answer.objects.filter(id=answer.id).update(**updated_fields)
+
+ revision = AnswerRevision(
+ answer = answer,
+ author = request.user,
+ revised_at = edited_at,
+ text = form.cleaned_data['text']
+ )
+
+ if form.cleaned_data['summary']:
+ revision.summary = form.cleaned_data['summary']
+ else:
+ revision.summary = 'No.%s Revision' % latest_revision.revision
+ revision.save()
+
+ answer.question.last_activity_at = edited_at
+ answer.question.last_activity_by = request.user
+ answer.question.save()
+
+ return HttpResponseRedirect(answer.get_absolute_url())
+ else:
+ revision_form = RevisionForm(answer, latest_revision)
+ form = EditAnswerForm(answer, latest_revision)
+ return render_to_response('answer_edit.html', {
+ 'answer': answer,
+ 'revision_form': revision_form,
+ 'form' : form,
+ }, context_instance=RequestContext(request))
+
+QUESTION_REVISION_TEMPLATE = ('<h1>%(title)s</h1>\n'
+ '<div class="text">%(html)s</div>\n'
+ '<div class="tags">%(tags)s</div>')
+def question_revisions(request, id):
+ post = get_object_or_404(Question, id=id)
+ revisions = list(post.revisions.all())
+ for i, revision in enumerate(revisions):
+ revision.html = QUESTION_REVISION_TEMPLATE % {
+ 'title': revision.title,
+ 'html': sanitize_html(markdowner.convert(revision.text)),
+ 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
+ for tag in revision.tagnames.split(' ')]),
+ }
+ if i > 0:
+ revisions[i - 1].diff = htmldiff(revision.html,
+ revisions[i - 1].html)
+ else:
+ revisions[i - 1].diff = QUESTION_REVISION_TEMPLATE % {
+ 'title': revisions[0].title,
+ 'html': sanitize_html(markdowner.convert(revisions[0].text)),
+ 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
+ for tag in revisions[0].tagnames.split(' ')]),
+ }
+ revisions[i - 1].summary = None
+ return render_to_response('revisions_question.html', {
+ 'post': post,
+ 'revisions': revisions,
+ }, context_instance=RequestContext(request))
+
+ANSWER_REVISION_TEMPLATE = ('<div class="text">%(html)s</div>')
+def answer_revisions(request, id):
+ post = get_object_or_404(Answer, id=id)
+ revisions = list(post.revisions.all())
+ for i, revision in enumerate(revisions):
+ revision.html = ANSWER_REVISION_TEMPLATE % {
+ 'html': sanitize_html(markdowner.convert(revision.text))
+ }
+ if i > 0:
+ revisions[i - 1].diff = htmldiff(revision.html,
+ revisions[i - 1].html)
+ else:
+ revisions[i - 1].diff = revisions[i-1].text
+ revisions[i - 1].summary = None
+ return render_to_response('revisions_answer.html', {
+ 'post': post,
+ 'revisions': revisions,
+ }, context_instance=RequestContext(request))
+
+def answer(request, id):
+ question = get_object_or_404(Question, id=id)
+ if request.method == "POST":
+ form = AnswerForm(question, request.user, request.POST)
+ if form.is_valid():
+ wiki = form.cleaned_data['wiki']
+ text = form.cleaned_data['text']
+ update_time = datetime.datetime.now()
+
+ if request.user.is_authenticated():
+ create_new_answer(
+ question=question,
+ author=request.user,
+ added_at=update_time,
+ wiki=wiki,
+ text=text,
+ email_notify=form.cleaned_data['email_notify']
+ )
+ else:
+ request.session.flush()
+ html = sanitize_html(markdowner.convert(text))
+ summary = strip_tags(html)[:120]
+ anon = AnonymousAnswer(
+ question = question,
+ wiki = wiki,
+ text = text,
+ summary = summary,
+ session_key = request.session.session_key,
+ ip_addr = request.META['REMOTE_ADDR'],
+ )
+ anon.save()
+ return HttpResponseRedirect('/account/signin/newanswer')
+
+ return HttpResponseRedirect(question.get_absolute_url())
+
+def tags(request):
+ stag = ""
+ is_paginated = True
+ sortby = request.GET.get('sort', 'used')
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ if request.method == "GET":
+ stag = request.GET.get("q", "").strip()
+ if stag is not None:
+ objects_list = Paginator(Tag.objects.filter(deleted=False).exclude(used_count=0).extra(where=['name like %s'], params=['%' + stag + '%']), DEFAULT_PAGE_SIZE)
+ else:
+ if sortby == "name":
+ objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("name"), DEFAULT_PAGE_SIZE)
+ else:
+ objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-used_count"), DEFAULT_PAGE_SIZE)
+
+ try:
+ tags = objects_list.page(page)
+ except (EmptyPage, InvalidPage):
+ tags = objects_list.page(objects_list.num_pages)
+
+ return render_to_response('tags.html', {
+ "tags" : tags,
+ "stag" : stag,
+ "tab_id" : sortby,
+ "keywords" : stag,
+ "context" : {
+ 'is_paginated' : is_paginated,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': tags.has_previous(),
+ 'has_next': tags.has_next(),
+ 'previous': tags.previous_page_number(),
+ 'next': tags.next_page_number(),
+ 'base_url' : '/tags/?sort=%s&' % sortby
+ }
+
+ }, context_instance=RequestContext(request))
+
+def tag(request, tag):
+ return questions(request, tagname=tag)
+
+def vote(request, id):
+ """
+ vote_type:
+ acceptAnswer : 0,
+ questionUpVote : 1,
+ questionDownVote : 2,
+ favorite : 4,
+ answerUpVote: 5,
+ answerDownVote:6,
+ offensiveQuestion : 7,
+ offensiveAnswer:8,
+ removeQuestion: 9,
+ removeAnswer:10
+ questionSubscribeUpdates:11
+
+ accept answer code:
+ response_data['allowed'] = -1, Accept his own answer 0, no allowed - Anonymous 1, Allowed - by default
+ response_data['success'] = 0, failed 1, Success - by default
+ response_data['status'] = 0, By default 1, Answer has been accepted already(Cancel)
+
+ vote code:
+ allowed = -3, Don't have enough votes left
+ -2, Don't have enough reputation score
+ -1, Vote his own post
+ 0, no allowed - Anonymous
+ 1, Allowed - by default
+ status = 0, By default
+ 1, Cancel
+ 2, Vote is too old to be canceled
+
+ offensive code:
+ allowed = -3, Don't have enough flags left
+ -2, Don't have enough reputation score to do this
+ 0, not allowed
+ 1, allowed
+ status = 0, by default
+ 1, can't do it again
+ """
+ response_data = {
+ "allowed": 1,
+ "success": 1,
+ "status" : 0,
+ "count" : 0,
+ "message" : ''
+ }
+
+ def can_vote(vote_score, user):
+ if vote_score == 1:
+ return can_vote_up(request.user)
+ else:
+ return can_vote_down(request.user)
+
+ try:
+ if not request.user.is_authenticated():
+ response_data['allowed'] = 0
+ response_data['success'] = 0
+
+ elif request.is_ajax():
+ question = get_object_or_404(Question, id=id)
+ vote_type = request.POST.get('type')
+
+ #accept answer
+ if vote_type == '0':
+ answer_id = request.POST.get('postId')
+ answer = get_object_or_404(Answer, id=answer_id)
+ # make sure question author is current user
+ if question.author == request.user:
+ # answer user who is also question author is not allow to accept answer
+ if answer.author == question.author:
+ response_data['success'] = 0
+ response_data['allowed'] = -1
+ # check if answer has been accepted already
+ elif answer.accepted:
+ onAnswerAcceptCanceled(answer, request.user)
+ response_data['status'] = 1
+ else:
+ # set other answers in this question not accepted first
+ for answer_of_question in Answer.objects.get_answers_from_question(question, request.user):
+ if answer_of_question != answer and answer_of_question.accepted:
+ onAnswerAcceptCanceled(answer_of_question, request.user)
+
+ #make sure retrieve data again after above author changes, they may have related data
+ answer = get_object_or_404(Answer, id=answer_id)
+ onAnswerAccept(answer, request.user)
+ else:
+ response_data['allowed'] = 0
+ response_data['success'] = 0
+ # favorite
+ elif vote_type == '4':
+ has_favorited = False
+ fav_questions = FavoriteQuestion.objects.filter(question=question)
+ # if the same question has been favorited before, then delete it
+ if fav_questions is not None:
+ for item in fav_questions:
+ if item.user == request.user:
+ item.delete()
+ response_data['status'] = 1
+ response_data['count'] = len(fav_questions) - 1
+ if response_data['count'] < 0:
+ response_data['count'] = 0
+ has_favorited = True
+ # if above deletion has not been executed, just insert a new favorite question
+ if not has_favorited:
+ new_item = FavoriteQuestion(question=question, user=request.user)
+ new_item.save()
+ response_data['count'] = FavoriteQuestion.objects.filter(question=question).count()
+ Question.objects.update_favorite_count(question)
+
+ elif vote_type in ['1', '2', '5', '6']:
+ post_id = id
+ post = question
+ vote_score = 1
+ if vote_type in ['5', '6']:
+ answer_id = request.POST.get('postId')
+ answer = get_object_or_404(Answer, id=answer_id)
+ post_id = answer_id
+ post = answer
+ if vote_type in ['2', '6']:
+ vote_score = -1
+
+ if post.author == request.user:
+ response_data['allowed'] = -1
+ elif not can_vote(vote_score, request.user):
+ response_data['allowed'] = -2
+ elif post.votes.filter(user=request.user).count() > 0:
+ vote = post.votes.filter(user=request.user)[0]
+ # unvote should be less than certain time
+ if (datetime.datetime.now().day - vote.voted_at.day) >= VOTE_RULES['scope_deny_unvote_days']:
+ response_data['status'] = 2
+ else:
+ voted = vote.vote
+ if voted > 0:
+ # cancel upvote
+ onUpVotedCanceled(vote, post, request.user)
+
+ else:
+ # cancel downvote
+ onDownVotedCanceled(vote, post, request.user)
+
+ response_data['status'] = 1
+ response_data['count'] = post.score
+ elif Vote.objects.get_votes_count_today_from_user(request.user) >= VOTE_RULES['scope_votes_per_user_per_day']:
+ response_data['allowed'] = -3
+ else:
+ vote = Vote(user=request.user, content_object=post, vote=vote_score, voted_at=datetime.datetime.now())
+ if vote_score > 0:
+ # upvote
+ onUpVoted(vote, post, request.user)
+ else:
+ # downvote
+ onDownVoted(vote, post, request.user)
+
+ votes_left = VOTE_RULES['scope_votes_per_user_per_day'] - Vote.objects.get_votes_count_today_from_user(request.user)
+ if votes_left <= VOTE_RULES['scope_warn_votes_left']:
+ response_data['message'] = u'%s votes left' % votes_left
+ response_data['count'] = post.score
+ elif vote_type in ['7', '8']:
+ post = question
+ post_id = id
+ if vote_type == '8':
+ post_id = request.POST.get('postId')
+ post = get_object_or_404(Answer, id=post_id)
+
+ if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= VOTE_RULES['scope_flags_per_user_per_day']:
+ response_data['allowed'] = -3
+ elif not can_flag_offensive(request.user):
+ response_data['allowed'] = -2
+ elif post.flagged_items.filter(user=request.user).count() > 0:
+ response_data['status'] = 1
+ else:
+ item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now())
+ onFlaggedItem(item, post, request.user)
+ response_data['count'] = post.offensive_flag_count
+ # send signal when question or answer be marked offensive
+ mark_offensive.send(sender=post.__class__, instance=post, mark_by=request.user)
+ elif vote_type in ['9', '10']:
+ post = question
+ post_id = id
+ if vote_type == '10':
+ post_id = request.POST.get('postId')
+ post = get_object_or_404(Answer, id=post_id)
+
+ if not can_delete_post(request.user, post):
+ response_data['allowed'] = -2
+ elif post.deleted:
+ onDeleteCanceled(post, request.user)
+ response_data['status'] = 1
+ else:
+ onDeleted(post, request.user)
+ delete_post_or_answer.send(sender=post.__class__, instance=post, delete_by=request.user)
+ elif vote_type == '11':#subscribe q updates
+ user = request.user
+ if user.is_authenticated():
+ try:
+ EmailFeed.objects.get(feed_id=question.id,subscriber_id=user.id,feed_content_type=question_type)
+ except EmailFeed.DoesNotExist:
+ feed = EmailFeed(subscriber=user,content=question)
+ feed.save()
+ if settings.EMAIL_VALIDATION == 'on' and user.email_isvalid == False:
+ response_data['message'] = _('subscription saved, %(email)s needs validation') % {'email':user.email}
+ #response_data['status'] = 1
+ #responst_data['allowed'] = 1
+ else:
+ pass
+ #response_data['status'] = 0
+ #response_data['allowed'] = 0
+ elif vote_type == '12':#unsubscribe q updates
+ user = request.user
+ if user.is_authenticated():
+ try:
+ feed = EmailFeed.objects.get(feed_id=question.id,subscriber_id=user.id)
+ feed.delete()
+ except EmailFeed.DoesNotExist:
+ pass
+
+ else:
+ response_data['success'] = 0
+ response_data['message'] = u'Request mode is not supported. Please try again.'
+
+ data = simplejson.dumps(response_data)
+
+ except Exception, e:
+ response_data['message'] = str(e)
+ data = simplejson.dumps(response_data)
+ return HttpResponse(data, mimetype="application/json")
+
+def users(request):
+ is_paginated = True
+ sortby = request.GET.get('sort', 'reputation')
+ suser = request.REQUEST.get('q', "")
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ if suser == "":
+ if sortby == "newest":
+ objects_list = Paginator(User.objects.all().order_by('-date_joined'), USERS_PAGE_SIZE)
+ elif sortby == "last":
+ objects_list = Paginator(User.objects.all().order_by('date_joined'), USERS_PAGE_SIZE)
+ elif sortby == "user":
+ objects_list = Paginator(User.objects.all().order_by('username'), USERS_PAGE_SIZE)
+ # default
+ else:
+ objects_list = Paginator(User.objects.all().order_by('-reputation'), USERS_PAGE_SIZE)
+ base_url = '/users/?sort=%s&' % sortby
+ else:
+ sortby = "reputation"
+ objects_list = Paginator(User.objects.extra(where=['username like %s'], params=['%' + suser + '%']).order_by('-reputation'), USERS_PAGE_SIZE)
+ base_url = '/users/?name=%s&sort=%s&' % (suser, sortby)
+
+ try:
+ users = objects_list.page(page)
+ except (EmptyPage, InvalidPage):
+ users = objects_list.page(objects_list.num_pages)
+
+ return render_to_response('users.html', {
+ "users" : users,
+ "suser" : suser,
+ "keywords" : suser,
+ "tab_id" : sortby,
+ "context" : {
+ 'is_paginated' : is_paginated,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': users.has_previous(),
+ 'has_next': users.has_next(),
+ 'previous': users.previous_page_number(),
+ 'next': users.next_page_number(),
+ 'base_url' : base_url
+ }
+
+ }, context_instance=RequestContext(request))
+
+def user(request, id):
+ sort = request.GET.get('sort', 'stats')
+ user_view = dict((v.id, v) for v in USER_TEMPLATE_VIEWS).get(sort, USER_TEMPLATE_VIEWS[0])
+ from forum import views
+ func = getattr(views, user_view.view_name)
+ return func(request, id, user_view)
+
+@login_required
+def edit_user(request, id):
+ user = get_object_or_404(User, id=id)
+ if request.user != user:
+ raise Http404
+ if request.method == "POST":
+ form = EditUserForm(user, request.POST)
+ if form.is_valid():
+ new_email = sanitize_html(form.cleaned_data['email'])
+
+ from django_authopenid.views import set_new_email
+ set_new_email(user, new_email)
+
+ 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'])
+ user.date_of_birth = sanitize_html(form.cleaned_data['birthday'])
+ if len(user.date_of_birth) == 0:
+ user.date_of_birth = '1900-01-01'
+ user.about = sanitize_html(form.cleaned_data['about'])
+
+ user.save()
+ # send user updated singal if full fields have been updated
+ if user.email and user.real_name and user.website and user.location and \
+ user.date_of_birth and user.about:
+ user_updated.send(sender=user.__class__, instance=user, updated_by=user)
+ return HttpResponseRedirect(user.get_profile_url())
+ else:
+ form = EditUserForm(user)
+ return render_to_response('user_edit.html', {
+ 'form' : form,
+ }, context_instance=RequestContext(request))
+
+def user_stats(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ questions = Question.objects.extra(
+ select={
+ 'vote_count' : 'question.score',
+ 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s AND f.question_id = question.id',
+ 'la_user_id' : 'auth_user.id',
+ 'la_username' : 'auth_user.username',
+ 'la_user_gold' : 'auth_user.gold',
+ 'la_user_silver' : 'auth_user.silver',
+ 'la_user_bronze' : 'auth_user.bronze',
+ 'la_user_reputation' : 'auth_user.reputation'
+ },
+ select_params=[user_id],
+ tables=['question', 'auth_user'],
+ where=['question.deleted = 0 AND question.author_id=%s AND question.last_activity_by_id = auth_user.id'],
+ params=[user_id],
+ order_by=['-vote_count', '-last_activity_at']
+ ).values('vote_count',
+ 'favorited_myself',
+ 'id',
+ 'title',
+ 'author_id',
+ 'added_at',
+ 'answer_accepted',
+ 'answer_count',
+ 'comment_count',
+ 'view_count',
+ 'favourite_count',
+ 'summary',
+ 'tagnames',
+ 'vote_up_count',
+ 'vote_down_count',
+ 'last_activity_at',
+ 'la_user_id',
+ 'la_username',
+ 'la_user_gold',
+ 'la_user_silver',
+ 'la_user_bronze',
+ 'la_user_reputation')[:100]
+
+ answered_questions = Question.objects.extra(
+ select={
+ 'vote_up_count' : 'answer.vote_up_count',
+ 'vote_down_count' : 'answer.vote_down_count',
+ 'answer_id' : 'answer.id',
+ 'accepted' : 'answer.accepted',
+ 'vote_count' : 'answer.score',
+ 'comment_count' : 'answer.comment_count'
+ },
+ tables=['question', 'answer'],
+ where=['answer.deleted=0 AND answer.author_id=%s AND answer.question_id=question.id'],
+ params=[user_id],
+ order_by=['-vote_count', '-answer_id'],
+ select_params=[user_id]
+ ).distinct().values('comment_count',
+ 'id',
+ 'answer_id',
+ 'title',
+ 'author_id',
+ 'accepted',
+ 'vote_count',
+ 'answer_count',
+ 'vote_up_count',
+ 'vote_down_count')[:100]
+ up_votes = Vote.objects.get_up_vote_count_from_user(user)
+ down_votes = Vote.objects.get_down_vote_count_from_user(user)
+ votes_today = Vote.objects.get_votes_count_today_from_user(user)
+ votes_total = VOTE_RULES['scope_votes_per_user_per_day']
+ tags = user.created_tags.all().order_by('-used_count')[:50]
+ awards = Award.objects.extra(
+ select={'id': 'badge.id', 'count': 'count(badge_id)', 'name':'badge.name', 'description': 'badge.description', 'type': 'badge.type'},
+ tables=['award', 'badge'],
+ order_by=['-awarded_at'],
+ where=['user_id=%s AND badge_id=badge.id'],
+ params=[user.id]
+ ).values('id', 'count', 'name', 'description', 'type')
+ total_awards = awards.count()
+ awards.query.group_by = ['badge_id']
+
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "questions" : questions,
+ "answered_questions" : answered_questions,
+ "up_votes" : up_votes,
+ "down_votes" : down_votes,
+ "total_votes": up_votes + down_votes,
+ "votes_today_left": votes_total-votes_today,
+ "votes_total_per_day": votes_total,
+ "tags" : tags,
+ "awards": awards,
+ "total_awards" : total_awards,
+ }, context_instance=RequestContext(request))
+
+def user_recent(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ def get_type_name(type_id):
+ for item in TYPE_ACTIVITY:
+ if type_id in item:
+ return item[1]
+
+ class Event:
+ def __init__(self, time, type, title, summary, answer_id, question_id):
+ self.time = time
+ self.type = get_type_name(type)
+ self.type_id = type
+ self.title = title
+ self.summary = summary
+ self.title_link = u'/questions/%s/%s#%s' %(question_id, title, answer_id)\
+ if int(answer_id) > 0 else u'/questions/%s/%s' %(question_id, title)
+ class AwardEvent:
+ def __init__(self, time, type, id):
+ self.time = time
+ self.type = get_type_name(type)
+ self.type_id = type
+ self.badge = get_object_or_404(Badge, id=id)
+
+ activities = []
+ # ask questions
+ questions = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'active_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'question'],
+ where=['activity.content_type_id = %s AND activity.object_id = ' +
+ 'question.id 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(
+ 'title',
+ 'question_id',
+ 'active_at',
+ 'activity_type'
+ )
+ if len(questions) > 0:
+ questions = [(Event(q['active_at'], q['activity_type'], q['title'], '', '0', \
+ q['question_id'])) for q in questions]
+ activities.extend(questions)
+
+ # answers
+ answers = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'active_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'answer', 'question'],
+ where=['activity.content_type_id = %s AND activity.object_id = answer.id AND ' +
+ 'answer.question_id=question.id AND activity.user_id=%s AND activity.activity_type=%s'],
+ params=[answer_type_id, user_id, TYPE_ACTIVITY_ANSWER],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'active_at',
+ 'activity_type'
+ )
+ if len(answers) > 0:
+ answers = [(Event(q['active_at'], q['activity_type'], q['title'], '', q['answer_id'], \
+ q['question_id'])) for q in answers]
+ activities.extend(answers)
+
+ # question comments
+ comments = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'comment.object_id',
+ 'added_at' : 'comment.added_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'question', 'comment'],
+
+ 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'],
+ params=[comment_type_id, question_type_id, user_id, TYPE_ACTIVITY_COMMENT_QUESTION],
+ order_by=['-comment.added_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'activity_type'
+ )
+
+ if len(comments) > 0:
+ comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \
+ q['question_id'])) for q in comments]
+ activities.extend(comments)
+
+ # answer comments
+ comments = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'comment.added_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'question', 'answer', 'comment'],
+
+ where=['activity.content_type_id = %s AND activity.object_id = comment.id AND '+
+ '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'],
+ params=[comment_type_id, answer_type_id, user_id, TYPE_ACTIVITY_COMMENT_ANSWER],
+ order_by=['-comment.added_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'activity_type'
+ )
+
+ if len(comments) > 0:
+ comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', q['answer_id'], \
+ q['question_id'])) for q in comments]
+ activities.extend(comments)
+
+ # question revisions
+ revisions = Activity.objects.extra(
+ select={
+ 'title' : 'question_revision.title',
+ 'question_id' : 'question_revision.question_id',
+ 'added_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type',
+ 'summary' : 'question_revision.summary'
+ },
+ tables=['activity', 'question_revision'],
+ where=['activity.content_type_id = %s AND activity.object_id = question_revision.id 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],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'activity_type',
+ 'summary'
+ )
+
+ if len(revisions) > 0:
+ revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], '0', \
+ q['question_id'])) for q in revisions]
+ activities.extend(revisions)
+
+ # answer revisions
+ revisions = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type',
+ 'summary' : 'answer_revision.summary'
+ },
+ tables=['activity', 'answer_revision', 'question', 'answer'],
+
+ 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 '+
+ 'activity.activity_type=%s'],
+ params=[answer_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_ANSWER],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'answer_id',
+ 'activity_type',
+ 'summary'
+ )
+
+ if len(revisions) > 0:
+ revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], \
+ q['answer_id'], q['question_id'])) for q in revisions]
+ activities.extend(revisions)
+
+ # accepted answers
+ accept_answers = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'added_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type',
+ },
+ 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.question_id=question.id AND activity.activity_type=%s'],
+ params=[answer_type_id, user_id, TYPE_ACTIVITY_MARK_ANSWER],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'activity_type',
+ )
+ if len(accept_answers) > 0:
+ accept_answers = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \
+ q['question_id'])) for q in accept_answers]
+ activities.extend(accept_answers)
+ #award history
+ awards = Activity.objects.extra(
+ select={
+ 'badge_id' : 'badge.id',
+ 'awarded_at': 'award.awarded_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'award', 'badge'],
+ where=['activity.user_id = award.user_id AND activity.user_id = %s AND '+
+ 'award.badge_id=badge.id AND activity.object_id=award.id AND activity.activity_type=%s'],
+ params=[user_id, TYPE_ACTIVITY_PRIZE],
+ order_by=['-activity.active_at']
+ ).values(
+ 'badge_id',
+ 'awarded_at',
+ 'activity_type'
+ )
+ if len(awards) > 0:
+ awards = [(AwardEvent(q['awarded_at'], q['activity_type'], q['badge_id'])) for q in awards]
+ activities.extend(awards)
+
+ activities.sort(lambda x,y: cmp(y.time, x.time))
+
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "activities" : activities[:user_view.data_size]
+ }, context_instance=RequestContext(request))
+
+def user_responses(request, user_id, user_view):
+ """
+ We list answers for question, comments, and answer accepted by others for this user.
+ """
+ class Response:
+ def __init__(self, type, title, question_id, answer_id, time, username, user_id, content):
+ self.type = type
+ self.title = title
+ self.titlelink = u'/questions/%s/%s#%s' % (question_id, title, answer_id)
+ self.time = time
+ self.userlink = u'/users/%s/%s/' % (user_id, username)
+ self.username = username
+ self.content = u'%s ...' % strip_tags(content)[:300]
+
+ def __unicode__(self):
+ return u'%s %s' % (self.type, self.titlelink)
+
+ user = get_object_or_404(User, id=user_id)
+ responses = []
+ answers = Answer.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'answer.added_at',
+ 'html' : 'answer.html',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ select_params=[user_id],
+ tables=['answer', 'question', 'auth_user'],
+ where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+
+ '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']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'html',
+ 'username',
+ 'user_id'
+ )
+ if len(answers) > 0:
+ answers = [(Response(TYPE_RESPONSE['QUESTION_ANSWERED'], a['title'], a['question_id'],
+ a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
+ responses.extend(answers)
+
+
+ # question comments
+ comments = Comment.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'comment.object_id',
+ 'added_at' : 'comment.added_at',
+ 'comment' : 'comment.comment',
+ 'username' : 'auth_user.username',
+ '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 '+
+ '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']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'comment',
+ 'username',
+ 'user_id'
+ )
+
+ if len(comments) > 0:
+ comments = [(Response(TYPE_RESPONSE['QUESTION_COMMENTED'], c['title'], c['question_id'],
+ '', c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments]
+ responses.extend(comments)
+
+ # answer comments
+ comments = Comment.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'comment.added_at',
+ 'comment' : 'comment.comment',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ tables=['answer', 'auth_user', 'comment', 'question'],
+ where=['answer.deleted = 0 AND answer.author_id = %s AND comment.object_id=answer.id AND '+
+ 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id '+
+ 'AND question.id = answer.question_id'],
+ params=[user_id, answer_type_id, user_id],
+ order_by=['-comment.added_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'comment',
+ 'username',
+ 'user_id'
+ )
+
+ if len(comments) > 0:
+ comments = [(Response(TYPE_RESPONSE['ANSWER_COMMENTED'], c['title'], c['question_id'],
+ c['answer_id'], c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments]
+ responses.extend(comments)
+
+ # answer has been accepted
+ answers = Answer.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'answer.accepted_at',
+ 'html' : 'answer.html',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ select_params=[user_id],
+ tables=['answer', 'question', 'auth_user'],
+ where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+
+ 'answer.author_id = %s AND answer.accepted=1 AND question.author_id=auth_user.id'],
+ params=[user_id],
+ order_by=['-answer.id']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'html',
+ 'username',
+ 'user_id'
+ )
+ if len(answers) > 0:
+ answers = [(Response(TYPE_RESPONSE['ANSWER_ACCEPTED'], a['title'], a['question_id'],
+ a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
+ responses.extend(answers)
+
+ # sort posts by time
+ responses.sort(lambda x,y: cmp(y.time, x.time))
+
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "responses" : responses[:user_view.data_size],
+
+ }, context_instance=RequestContext(request))
+
+def user_votes(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ if not can_view_user_votes(request.user, user):
+ raise Http404
+ votes = []
+ question_votes = Vote.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 0,
+ 'voted_at' : 'vote.voted_at',
+ 'vote' : 'vote',
+ },
+ select_params=[user_id],
+ tables=['vote', 'question', 'auth_user'],
+ where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = question.id '+
+ 'AND vote.user_id=auth_user.id'],
+ params=[question_type_id, user_id],
+ order_by=['-vote.id']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'voted_at',
+ 'vote',
+ )
+ if(len(question_votes) > 0):
+ votes.extend(question_votes)
+
+ answer_votes = Vote.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'voted_at' : 'vote.voted_at',
+ 'vote' : 'vote',
+ },
+ select_params=[user_id],
+ tables=['vote', 'answer', 'question', 'auth_user'],
+ where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = answer.id '+
+ 'AND answer.question_id = question.id AND vote.user_id=auth_user.id'],
+ params=[answer_type_id, user_id],
+ order_by=['-vote.id']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'voted_at',
+ 'vote',
+ )
+ if(len(answer_votes) > 0):
+ votes.extend(answer_votes)
+ votes.sort(lambda x,y: cmp(y['voted_at'], x['voted_at']))
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "votes" : votes[:user_view.data_size]
+
+ }, context_instance=RequestContext(request))
+
+def user_reputation(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ reputation = Repute.objects.extra(
+ select={'positive': 'sum(positive)', 'negative': 'sum(negative)', 'question_id':'question_id',
+ 'title': 'question.title'},
+ tables=['repute', 'question'],
+ order_by=['-reputed_at'],
+ where=['user_id=%s AND question_id=question.id'],
+ params=[user.id]
+ ).values('positive', 'negative', 'question_id', 'title', 'reputed_at', 'reputation')
+
+ reputation.query.group_by = ['question_id']
+
+ rep_list = []
+ for rep in Repute.objects.filter(user=user).order_by('reputed_at'):
+ dic = '[%s,%s]' % (calendar.timegm(rep.reputed_at.timetuple()) * 1000, rep.reputation)
+ rep_list.append(dic)
+ reps = ','.join(rep_list)
+ reps = '[%s]' % reps
+
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "reputation" : reputation,
+ "reps" : reps
+ }, context_instance=RequestContext(request))
+
+def user_favorites(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ questions = Question.objects.extra(
+ select={
+ 'vote_count' : 'question.vote_up_count + question.vote_down_count',
+ 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s '+
+ 'AND f.question_id = question.id',
+ 'la_user_id' : 'auth_user.id',
+ 'la_username' : 'auth_user.username',
+ 'la_user_gold' : 'auth_user.gold',
+ 'la_user_silver' : 'auth_user.silver',
+ 'la_user_bronze' : 'auth_user.bronze',
+ 'la_user_reputation' : 'auth_user.reputation'
+ },
+ select_params=[user_id],
+ tables=['question', 'auth_user', 'favorite_question'],
+ where=['question.deleted = 0 AND question.last_activity_by_id = auth_user.id '+
+ 'AND favorite_question.question_id = question.id AND favorite_question.user_id = %s'],
+ params=[user_id],
+ order_by=['-vote_count', '-question.id']
+ ).values('vote_count',
+ 'favorited_myself',
+ 'id',
+ 'title',
+ 'author_id',
+ 'added_at',
+ 'answer_accepted',
+ 'answer_count',
+ 'comment_count',
+ 'view_count',
+ 'favourite_count',
+ 'summary',
+ 'tagnames',
+ 'vote_up_count',
+ 'vote_down_count',
+ 'last_activity_at',
+ 'la_user_id',
+ 'la_username',
+ 'la_user_gold',
+ 'la_user_silver',
+ 'la_user_bronze',
+ 'la_user_reputation')
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "questions" : questions[:user_view.data_size],
+ "view_user" : user
+ }, context_instance=RequestContext(request))
+
+
+def user_preferences(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ }, context_instance=RequestContext(request))
+
+def question_comments(request, id):
+ question = get_object_or_404(Question, id=id)
+ user = request.user
+ return __comments(request, question, 'question', user)
+
+def answer_comments(request, id):
+ answer = get_object_or_404(Answer, id=id)
+ user = request.user
+ return __comments(request, answer, 'answer', user)
+
+def __comments(request, obj, type, user):
+ # only support get comments by ajax now
+ if request.is_ajax():
+ if request.method == "GET":
+ return __generate_comments_json(obj, type, user)
+ elif request.method == "POST":
+ comment_data = request.POST.get('comment')
+ comment = Comment(content_object=obj, comment=comment_data, user=request.user)
+ comment.save()
+ obj.comment_count = obj.comment_count + 1
+ obj.save()
+ return __generate_comments_json(obj, type, user)
+
+def __generate_comments_json(obj, type, user):
+ comments = obj.comments.all().order_by('-id')
+ # {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
+ json_comments = []
+ for comment in comments:
+ comment_user = comment.user
+ delete_url = ""
+ if user != None and auth.can_delete_comment(user, comment):
+ #/posts/392845/comments/219852/delete
+ delete_url = "/" + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id)
+ json_comments.append({"id" : comment.id,
+ "object_id" : obj.id,
+ "add_date" : comment.added_at.strftime('%Y-%m-%d'),
+ "text" : comment.comment,
+ "user_display_name" : comment_user.username,
+ "user_url" : "/users/%s/%s" % (comment_user.id, comment_user.username),
+ "delete_url" : delete_url
+ })
+
+ data = simplejson.dumps(json_comments)
+ return HttpResponse(data, mimetype="application/json")
+
+def delete_question_comment(request, question_id, comment_id):
+ if request.is_ajax():
+ question = get_object_or_404(Question, id=question_id)
+ comment = get_object_or_404(Comment, id=comment_id)
+
+ question.comments.remove(comment)
+ question.comment_count = question.comment_count - 1
+ question.save()
+ user = request.user
+ return __generate_comments_json(question, 'question', user)
+
+def delete_answer_comment(request, answer_id, comment_id):
+ if request.is_ajax():
+ answer = get_object_or_404(Answer, id=answer_id)
+ comment = get_object_or_404(Comment, id=comment_id)
+
+ answer.comments.remove(comment)
+ answer.comment_count = answer.comment_count - 1
+ answer.save()
+ user = request.user
+ return __generate_comments_json(answer, 'answer', user)
+
+def logout(request):
+ url = request.GET.get('next')
+ return render_to_response('logout.html', {
+ 'next' : url,
+ }, context_instance=RequestContext(request))
+
+def badges(request):
+ badges = Badge.objects.all().order_by('type')
+ my_badges = []
+ if request.user.is_authenticated():
+ my_badges = Award.objects.filter(user=request.user)
+ my_badges.query.group_by = ['badge_id']
+
+ return render_to_response('badges.html', {
+ 'badges' : badges,
+ 'mybadges' : my_badges,
+ }, context_instance=RequestContext(request))
+
+def badge(request, id):
+ badge = get_object_or_404(Badge, id=id)
+ awards = Award.objects.extra(
+ select={'id': 'auth_user.id',
+ 'name': 'auth_user.username',
+ 'rep':'auth_user.reputation',
+ 'gold': 'auth_user.gold',
+ 'silver': 'auth_user.silver',
+ 'bronze': 'auth_user.bronze'},
+ tables=['award', 'auth_user'],
+ where=['badge_id=%s AND user_id=auth_user.id'],
+ params=[id]
+ ).values('id').distinct()
+
+ return render_to_response('badge.html', {
+ 'awards' : awards,
+ 'badge' : badge,
+ }, context_instance=RequestContext(request))
+
+def read_message(request):
+ if request.method == "POST":
+ if request.POST['formdata'] == 'required':
+ request.session['message_silent'] = 1
+ if request.user.is_authenticated():
+ request.user.delete_messages()
+ return HttpResponse('')
+
+def upload(request):
+ class FileTypeNotAllow(Exception):
+ pass
+ class FileSizeNotAllow(Exception):
+ pass
+ class UploadPermissionNotAuthorized(Exception):
+ pass
+
+ #<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>
+ xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>"
+
+ try:
+ f = request.FILES['file-upload']
+ # check upload permission
+ if not can_upload_files(request.user):
+ raise UploadPermissionNotAuthorized
+
+ # check file type
+ file_name_suffix = os.path.splitext(f.name)[1].lower()
+ if not file_name_suffix in settings.ALLOW_FILE_TYPES:
+ raise FileTypeNotAllow
+
+ # genetate new file name
+ new_file_name = str(time.time()).replace('.', str(random.randint(0,100000))) + file_name_suffix
+ # use default storage to store file
+ default_storage.save(new_file_name, f)
+ # check file size
+ # byte
+ size = default_storage.size(new_file_name)
+ if size > settings.ALLOW_MAX_FILE_SIZE:
+ default_storage.delete(new_file_name)
+ raise FileSizeNotAllow
+
+ result = xml_template % ('Good', '', default_storage.url(new_file_name))
+ except UploadPermissionNotAuthorized:
+ result = xml_template % ('', _('uploading images is limited to users with >60 reputation points'), '')
+ except FileTypeNotAllow:
+ result = xml_template % ('', _("allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"), '')
+ except FileSizeNotAllow:
+ result = xml_template % ('', _("maximum upload file size is %sK") % settings.ALLOW_MAX_FILE_SIZE / 1024, '')
+ except Exception:
+ result = xml_template % ('', _('Error uploading file. Please contact the site administrator. Thank you. %s' % Exception), '')
+
+ return HttpResponse(result, mimetype="application/xml")
+
+def books(request):
+ return HttpResponseRedirect("/books/mysql-zhaoyang")
+
+def book(request, short_name, unanswered=False):
+ """
+ 1. questions list
+ 2. book info
+ 3. author info and blog rss items
+ """
+ """
+ List of Questions, Tagged questions, and Unanswered questions.
+ """
+ books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
+ match_count = len(books)
+ if match_count == 0 :
+ raise Http404
+ else:
+ # the book info
+ book = books[0]
+ # get author info
+ author_info = BookAuthorInfo.objects.get(book=book)
+ # get author rss info
+ author_rss = BookAuthorRss.objects.filter(book=book)
+
+ # get pagesize from session, if failed then get default value
+ user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
+ # set pagesize equal to logon user specified value in database
+ if request.user.is_authenticated() and request.user.questions_per_page > 0:
+ user_page_size = request.user.questions_per_page
+
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ view_id = request.GET.get('sort', None)
+ view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "latest"
+ orderby = "-added_at"
+
+ # check if request is from tagged questions
+ if unanswered:
+ # check if request is from unanswered questions
+ # Article.objects.filter(publications__id__exact=1)
+ objects = Question.objects.filter(book__id__exact=book.id, deleted=False, answer_count=0).order_by(orderby)
+ else:
+ objects = Question.objects.filter(book__id__exact=book.id, deleted=False).order_by(orderby)
+
+ # RISK - inner join queries
+ objects = objects.select_related();
+ objects_list = Paginator(objects, user_page_size)
+ questions = objects_list.page(page)
+
+ return render_to_response('book.html', {
+ "book" : book,
+ "author_info" : author_info,
+ "author_rss" : author_rss,
+ "questions" : questions,
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?sort=%s&' % view_id,
+ 'pagesize' : user_page_size
+ }
+ }, context_instance=RequestContext(request))
+
+@login_required
+def ask_book(request, short_name):
+ if request.method == "POST":
+ form = AskForm(request.POST)
+ if form.is_valid():
+ added_at = datetime.datetime.now()
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
+ question = Question(
+ title = strip_tags(form.cleaned_data['title']),
+ author = request.user,
+ added_at = added_at,
+ last_activity_at = added_at,
+ last_activity_by = request.user,
+ wiki = form.cleaned_data['wiki'],
+ tagnames = form.cleaned_data['tags'].strip(),
+ html = html,
+ summary = strip_tags(html)[:120]
+ )
+ if question.wiki:
+ question.last_edited_by = question.author
+ question.last_edited_at = added_at
+ question.wikified_at = added_at
+
+ question.save()
+
+ # create the first revision
+ QuestionRevision.objects.create(
+ question = question,
+ revision = 1,
+ title = question.title,
+ author = request.user,
+ revised_at = added_at,
+ tagnames = question.tagnames,
+ summary = CONST['default_version'],
+ text = form.cleaned_data['text']
+ )
+
+ books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
+ match_count = len(books)
+ if match_count == 1:
+ # the book info
+ book = books[0]
+ book.questions.add(question)
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ form = AskForm()
+
+ tags = _get_tags_cache_json()
+ return render_to_response('ask.html', {
+ 'form' : form,
+ 'tags' : tags,
+ }, context_instance=RequestContext(request))
+
+def search(request):
+ """
+ Search by question, user and tag keywords.
+ For questions now we only search keywords in question title.
+ """
+ if request.method == "GET":
+ keywords = request.GET.get("q")
+ search_type = request.GET.get("t")
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+ if keywords is None:
+ return HttpResponseRedirect('/')
+ if search_type == 'tag':
+ return HttpResponseRedirect('/tags/?q=%s&page=%s' % (keywords.strip(), page))
+ elif search_type == "user":
+ return HttpResponseRedirect('/users/?q=%s&page=%s' % (keywords.strip(), page))
+ elif search_type == "question":
+
+ template_file = "questions.html"
+ # Set flag to False by default. If it is equal to True, then need to be saved.
+ pagesize_changed = False
+ # get pagesize from session, if failed then get default value
+ user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
+ # set pagesize equal to logon user specified value in database
+ if request.user.is_authenticated() and request.user.questions_per_page > 0:
+ user_page_size = request.user.questions_per_page
+
+ try:
+ page = int(request.GET.get('page', '1'))
+ # get new pagesize from UI selection
+ pagesize = int(request.GET.get('pagesize', user_page_size))
+ if pagesize <> user_page_size:
+ pagesize_changed = True
+
+ except ValueError:
+ page = 1
+ pagesize = user_page_size
+
+ # save this pagesize to user database
+ if pagesize_changed:
+ request.session["pagesize"] = pagesize
+ if request.user.is_authenticated():
+ user = request.user
+ user.questions_per_page = pagesize
+ user.save()
+
+ view_id = request.GET.get('sort', None)
+ view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "latest"
+ orderby = "-added_at"
+
+ objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby)
+
+ # RISK - inner join queries
+ objects = objects.select_related();
+ objects_list = Paginator(objects, pagesize)
+ questions = objects_list.page(page)
+
+ # Get related tags from this page objects
+ related_tags = []
+ for question in questions.object_list:
+ tags = list(question.tags.all())
+ for tag in tags:
+ if tag not in related_tags:
+ related_tags.append(tag)
+
+ return render_to_response(template_file, {
+ "questions" : questions,
+ "tab_id" : view_id,
+ "questions_count" : objects_list.count,
+ "tags" : related_tags,
+ "searchtag" : None,
+ "searchtitle" : keywords,
+ "keywords" : keywords,
+ "is_unanswered" : False,
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?t=question&q=%s&sort=%s&' % (keywords, view_id),
+ 'pagesize' : pagesize
+ }}, context_instance=RequestContext(request))
+
+ else:
+ raise Http404
diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo
index 8c359972..11d15deb 100644
--- a/locale/en/LC_MESSAGES/django.mo
+++ b/locale/en/LC_MESSAGES/django.mo
Binary files differ
diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po
index 93cfdfe4..3dda9ce9 100644
--- a/locale/en/LC_MESSAGES/django.po
+++ b/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-07-28 15:57+0000\n"
+"POT-Creation-Date: 2009-08-05 22:26-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -20,15 +20,17 @@ msgstr ""
msgid "account/"
msgstr ""
-#: settings.py:12 django_authopenid/urls.py:9 django_authopenid/urls.py:11
+#: settings.py:12 django_authopenid/urls.py:9 django_authopenid/urls.py:10
+#: django_authopenid/urls.py:11 django_authopenid/urls.py:13
+#: forum/views.py:304 templates/authopenid/confirm_email.txt:10
msgid "signin/"
msgstr ""
-#: django_authopenid/forms.py:67 django_authopenid/views.py:93
+#: django_authopenid/forms.py:67 django_authopenid/views.py:102
msgid "i-names are not supported"
msgstr ""
-#: django_authopenid/forms.py:102 django_authopenid/forms.py:207
+#: django_authopenid/forms.py:102
msgid ""
"Usernames can only contain letters, numbers and "
"underscores"
@@ -40,19 +42,19 @@ msgid ""
"choose another."
msgstr ""
-#: django_authopenid/forms.py:126 django_authopenid/forms.py:231
+#: django_authopenid/forms.py:126 django_authopenid/forms.py:233
msgid ""
"Please enter a valid username and password. Note that "
"both fields are case-sensitive."
msgstr ""
-#: django_authopenid/forms.py:130 django_authopenid/forms.py:235
+#: django_authopenid/forms.py:130 django_authopenid/forms.py:237
msgid "This account is inactive."
msgstr ""
-#: django_authopenid/forms.py:158
+#: django_authopenid/forms.py:158 django_authopenid/forms.py:210
msgid "invalid user name"
-msgstr ""
+msgstr "User names can contain letters, underscore and empty space."
#: django_authopenid/forms.py:160
msgid "sorry, this name can not be used, please try another"
@@ -66,137 +68,147 @@ msgstr ""
msgid "this name is already in use - please try anoter"
msgstr ""
-#: django_authopenid/forms.py:184
+#: django_authopenid/forms.py:185
msgid ""
-"This email is already registered in our database. Please "
-"choose another."
+"This email is already registered in our database. "
+"Please choose another."
msgstr ""
-#: django_authopenid/forms.py:214
+#: django_authopenid/forms.py:216
msgid ""
"This username don't exist. Please choose another."
msgstr ""
-#: django_authopenid/forms.py:253
+#: django_authopenid/forms.py:255
msgid "choose a username"
msgstr ""
-#: django_authopenid/forms.py:255 templates/authopenid/signup.html:36
+#: django_authopenid/forms.py:257 templates/authopenid/signup.html:38
msgid "your email address"
msgstr ""
-#: django_authopenid/forms.py:257 templates/authopenid/signup.html:37
+#: django_authopenid/forms.py:259 templates/authopenid/signup.html:39
msgid "choose password"
msgstr ""
-#: django_authopenid/forms.py:259 templates/authopenid/signup.html:38
+#: django_authopenid/forms.py:261 templates/authopenid/signup.html:40
msgid "retype password"
msgstr ""
-#: django_authopenid/forms.py:330
+#: django_authopenid/forms.py:335
msgid ""
"Old password is incorrect. Please enter the correct "
"password."
msgstr ""
-#: django_authopenid/forms.py:342
+#: django_authopenid/forms.py:347
msgid "new passwords do not match"
msgstr ""
-#: django_authopenid/forms.py:434
+#: django_authopenid/forms.py:442
msgid "Incorrect username."
msgstr ""
#: django_authopenid/urls.py:10
-msgid "signout/"
+msgid "newquestion/"
msgstr ""
#: django_authopenid/urls.py:11
-msgid "complete/"
+msgid "newanswer/"
+msgstr ""
+
+#: django_authopenid/urls.py:12
+msgid "signout/"
msgstr ""
#: django_authopenid/urls.py:13
+msgid "complete/"
+msgstr ""
+
+#: django_authopenid/urls.py:15
msgid "register/"
msgstr ""
-#: django_authopenid/urls.py:14
+#: django_authopenid/urls.py:16
msgid "signup/"
msgstr ""
-#: django_authopenid/urls.py:16
+#: django_authopenid/urls.py:18
msgid "sendpw/"
msgstr ""
-#: django_authopenid/urls.py:26
+#: django_authopenid/urls.py:29
msgid "delete/"
msgstr ""
-#: django_authopenid/views.py:99
+#: django_authopenid/views.py:108
#, python-format
-msgid "非法OpenID地址: %s"
+msgid "OpenID %(openid_url)s is invalid"
msgstr ""
-#: django_authopenid/views.py:366
+#: django_authopenid/views.py:417 django_authopenid/views.py:544
msgid "Welcome"
-msgstr ""
+msgstr "Verification Email from Q&A forum"
-#: django_authopenid/views.py:456
+#: django_authopenid/views.py:507
msgid "Password changed."
msgstr ""
-#: django_authopenid/views.py:488
-msgid "Email changed."
+#: django_authopenid/views.py:519 django_authopenid/views.py:524
+msgid "your email needs to be validated"
msgstr ""
+"Your email needs to be validated. Please see details <a "
+"id='validate_email_alert' href=\"/faq#validate\">here</a>."
-#: django_authopenid/views.py:519 django_authopenid/views.py:671
+#: django_authopenid/views.py:681 django_authopenid/views.py:833
#, python-format
msgid "No OpenID %s found associated in our database"
msgstr ""
-#: django_authopenid/views.py:523 django_authopenid/views.py:678
+#: django_authopenid/views.py:685 django_authopenid/views.py:840
#, python-format
msgid "The OpenID %s isn't associated to current user logged in"
msgstr ""
-#: django_authopenid/views.py:531
+#: django_authopenid/views.py:693
msgid "Email Changed."
msgstr ""
-#: django_authopenid/views.py:606
+#: django_authopenid/views.py:768
msgid "This OpenID is already associated with another account."
msgstr ""
-#: django_authopenid/views.py:611
+#: django_authopenid/views.py:773
#, python-format
msgid "OpenID %s is now associated with your account."
msgstr ""
-#: django_authopenid/views.py:681
+#: django_authopenid/views.py:843
msgid "Account deleted."
msgstr ""
-#: django_authopenid/views.py:721
+#: django_authopenid/views.py:883
msgid "Request for new password"
msgstr ""
-#: django_authopenid/views.py:734
+#: django_authopenid/views.py:896
msgid "A new password has been sent to your email address."
msgstr ""
-#: django_authopenid/views.py:764
+#: django_authopenid/views.py:926
#, python-format
msgid ""
"Could not change password. Confirmation key '%s' is not "
"registered."
msgstr ""
-#: django_authopenid/views.py:773
+#: django_authopenid/views.py:935
msgid ""
"Can not change password. User don't exist anymore in our "
"database."
msgstr ""
-#: django_authopenid/views.py:782
+#: django_authopenid/views.py:944
#, python-format
msgid "Password changed for %s. You may now sign in."
msgstr ""
@@ -259,7 +271,7 @@ msgstr ""
#: forum/const.py:62
msgid "received award"
-msgstr ""
+msgstr "received badge"
#: forum/const.py:63
msgid "marked best answer"
@@ -317,31 +329,20 @@ msgstr ""
msgid "retagged"
msgstr ""
-#: forum/feed.py:17
-msgid "site title"
-msgstr ""
-
-#: forum/feed.py:17
+#: forum/feed.py:18
msgid " - "
msgstr ""
-#: forum/feed.py:17
-msgid "site slogan"
-msgstr ""
-
-#: forum/feed.py:17
+#: forum/feed.py:18
msgid "latest questions"
msgstr ""
-#: forum/feed.py:20
-msgid "meta site content"
-msgstr ""
-
-#: forum/feed.py:22
-msgid "copyright message"
+#: forum/feed.py:19
+msgid "questions/"
msgstr ""
-#: forum/forms.py:14 templates/question_edit_tips.html:31
+#: forum/forms.py:14 templates/answer_edit_tips.html:34
+#: templates/answer_edit_tips.html.py:38 templates/question_edit_tips.html:31
#: templates/question_edit_tips.html:36
msgid "title"
msgstr ""
@@ -366,197 +367,239 @@ msgstr ""
msgid "tags"
msgstr ""
-#: forum/forms.py:46
-msgid "please use space to separate tags (this enables autocomplete feature)"
+#: forum/forms.py:47
+msgid ""
+"Tags are short keywords, with no spaces within. Up to five tags can be used."
msgstr ""
-#: forum/forms.py:53
+#: forum/forms.py:54 templates/question_retag.html:38
msgid "tags are required"
msgstr ""
-#: forum/forms.py:57
+#: forum/forms.py:58
msgid "please use 5 tags or less"
msgstr ""
-#: forum/forms.py:60
+#: forum/forms.py:61
msgid "tags must be shorter than 20 characters"
msgstr ""
-#: forum/forms.py:64
+#: forum/forms.py:65
msgid ""
"please use following characters in tags: letters 'a-z', numbers, and "
"characters '.-_#'"
msgstr ""
-#: forum/forms.py:74 templates/index.html:56 templates/question.html:196
-#: templates/question.html.py:377 templates/unanswered.html:48
+#: forum/forms.py:75 templates/index.html:57 templates/question.html:199
+#: templates/question.html.py:380 templates/questions.html:58
+#: templates/questions.html.py:70 templates/unanswered.html:48
#: templates/unanswered.html.py:60
msgid "community wiki"
msgstr ""
-#: forum/forms.py:75
+#: forum/forms.py:76
msgid ""
"if you choose community wiki option, the question and answer do not generate "
"points and name of author will not be shown"
msgstr ""
-#: forum/forms.py:84
+#: forum/forms.py:89
msgid "update summary:"
msgstr ""
-#: forum/forms.py:85
+#: forum/forms.py:90
msgid ""
"enter a brief summary of your revision (e.g. fixed spelling, grammar, "
"improved style, this field is optional)"
msgstr ""
-#: forum/forms.py:160
+#: forum/forms.py:175
msgid "this email does not have to be linked to gravatar"
msgstr ""
-#: forum/forms.py:161
+#: forum/forms.py:176
msgid "Real name"
msgstr ""
-#: forum/forms.py:162
+#: forum/forms.py:177
msgid "Website"
msgstr ""
-#: forum/forms.py:163
+#: forum/forms.py:178
msgid "Location"
msgstr ""
-#: forum/forms.py:164
+#: forum/forms.py:179
msgid "Date of birth"
msgstr ""
-#: forum/forms.py:164
+#: forum/forms.py:179
msgid "will not be shown, used to calculate age, format: YYYY-MM-DD"
msgstr ""
-#: forum/forms.py:165 templates/authopenid/settings.html:20
+#: forum/forms.py:180 templates/authopenid/settings.html:21
msgid "Profile"
msgstr ""
-#: forum/forms.py:190 forum/forms.py:191
+#: forum/forms.py:207 forum/forms.py:208
msgid "this email has already been registered, please use another one"
msgstr ""
-#: forum/models.py:316 templates/badges.html:50
+#: forum/models.py:238
+#, python-format
+msgid "%(author)s modified the question"
+msgstr ""
+
+#: forum/models.py:242
+#, python-format
+msgid "%(people)s posted %(new_answer_count)s new answers"
+msgstr ""
+
+#: forum/models.py:247
+#, python-format
+msgid "%(people)s commented the question"
+msgstr ""
+
+#: forum/models.py:252
+#, python-format
+msgid "%(people)s commented answers"
+msgstr ""
+
+#: forum/models.py:254
+#, python-format
+msgid "%(people)s commented the answer"
+msgstr ""
+
+#: forum/models.py:433 templates/badges.html:52
msgid "gold"
msgstr ""
-#: forum/models.py:317 templates/badges.html:58
+#: forum/models.py:434 templates/badges.html:60
msgid "silver"
msgstr ""
-#: forum/models.py:318 templates/badges.html:65
+#: forum/models.py:435 templates/badges.html:67
msgid "bronze"
msgstr ""
-#: forum/user.py:17 templates/user_tabs.html:7
+#: forum/user.py:16 templates/user_tabs.html:7
msgid "overview"
msgstr ""
-#: forum/user.py:18
+#: forum/user.py:17
msgid "user profile"
msgstr ""
-#: forum/user.py:19
+#: forum/user.py:18
msgid "user profile overview"
msgstr ""
-#: forum/user.py:25 templates/user_tabs.html:9
+#: forum/user.py:24 templates/user_tabs.html:9
msgid "recent activity"
msgstr ""
-#: forum/user.py:26
+#: forum/user.py:25
msgid "recent user activity"
msgstr ""
-#: forum/user.py:27
+#: forum/user.py:26
msgid "profile - recent activity"
msgstr ""
-#: forum/user.py:34 templates/user_tabs.html:13
+#: forum/user.py:33 templates/user_tabs.html:13
msgid "responses"
msgstr ""
-#: forum/user.py:35 templates/user_tabs.html:12
+#: forum/user.py:34 templates/user_tabs.html:12
msgid "comments and answers to others questions"
msgstr ""
-#: forum/user.py:36
+#: forum/user.py:35
msgid "profile - responses"
msgstr ""
-#: forum/user.py:43 templates/user_info.html:23 templates/users.html:25
+#: forum/user.py:42 templates/user_info.html:23 templates/users.html:26
msgid "reputation"
msgstr ""
-#: forum/user.py:44
+#: forum/user.py:43
msgid "user reputation in the community"
msgstr ""
-#: forum/user.py:45
+#: forum/user.py:44
msgid "profile - user reputation"
msgstr ""
-#: forum/user.py:51
+#: forum/user.py:50
msgid "favorite questions"
msgstr ""
-#: forum/user.py:52
+#: forum/user.py:51
msgid "users favorite questions"
msgstr ""
-#: forum/user.py:53
+#: forum/user.py:52
msgid "profile - favorite questions"
msgstr ""
-#: forum/user.py:60 templates/user_tabs.html:20
+#: forum/user.py:59 templates/user_tabs.html:20
msgid "casted votes"
-msgstr ""
+msgstr "votes"
-#: forum/user.py:61 templates/user_tabs.html:20
+#: forum/user.py:60 templates/user_tabs.html:20
msgid "user vote record"
msgstr ""
-#: forum/user.py:62
+#: forum/user.py:61
msgid "profile - votes"
msgstr ""
-#: forum/user.py:69
+#: forum/user.py:68
msgid "preferences"
msgstr ""
-#: forum/user.py:70 templates/user_tabs.html:28
+#: forum/user.py:69 templates/user_tabs.html:27
msgid "user preference settings"
msgstr ""
-#: forum/user.py:71
+#: forum/user.py:70
msgid "profile - user preferences"
msgstr ""
-#: forum/views.py:1726
+#: forum/views.py:304
+msgid "/account/"
+msgstr ""
+
+#: forum/views.py:943
+#, python-format
+msgid "subscription saved, %(email)s needs validation"
+msgstr ""
+"Your subscription is saved, but email address %(email)s needs to be "
+"validated, please see <a href='/faq#validate'>more details here</a>"
+
+#: forum/views.py:1853
msgid "uploading images is limited to users with >60 reputation points"
msgstr ""
-#: forum/views.py:1728
+#: forum/views.py:1855
msgid "allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"
msgstr ""
-#: forum/views.py:1730
+#: forum/views.py:1857
#, python-format
msgid "maximum upload file size is %sK"
msgstr ""
-#: forum/views.py:1732
+#: forum/views.py:1859
#, python-format
msgid ""
"Error uploading file. Please contact the site administrator. Thank you. %s"
msgstr ""
+#: forum/management/commands/send_email_alerts.py:35
+msgid "updates from website"
+msgstr ""
+
#: forum/templatetags/extra_tags.py:139 forum/templatetags/extra_tags.py:168
#: templates/header.html:33
msgid "badges"
@@ -612,6 +655,10 @@ msgstr ""
msgid "see all tags"
msgstr ""
+#: templates/500.html:22
+msgid "sorry, system error"
+msgstr ""
+
#: templates/500.html:24
msgid "system error log is recorded, error will be fixed as soon as possible"
msgstr ""
@@ -628,23 +675,28 @@ msgstr ""
msgid "see tags"
msgstr ""
+#: templates/about.html:6 templates/about.html.py:11
+msgid "About"
+msgstr ""
+
#: templates/answer_edit.html:4 templates/answer_edit.html.py:47
msgid "Edit answer"
msgstr ""
#: templates/answer_edit.html:24 templates/answer_edit.html.py:27
-#: templates/ask.html:25 templates/ask.html.py:28 templates/question.html:37
-#: templates/question.html.py:40 templates/question_edit.html:27
+#: templates/ask.html:25 templates/ask.html.py:28 templates/question.html:40
+#: templates/question.html.py:43 templates/question_edit.html:27
msgid "hide preview"
msgstr ""
#: templates/answer_edit.html:27 templates/ask.html:28
-#: templates/question.html:40 templates/question_edit.html:27
+#: templates/question.html:43 templates/question_edit.html:27
msgid "show preview"
msgstr ""
#: templates/answer_edit.html:47 templates/question_edit.html:65
-#: templates/revisions_answer.html:36 templates/revisions_question.html:36
+#: templates/question_retag.html:52 templates/revisions_answer.html:36
+#: templates/revisions_question.html:36
msgid "back"
msgstr ""
@@ -657,59 +709,125 @@ msgstr ""
msgid "select revision"
msgstr ""
-#: templates/answer_edit.html:62 templates/ask.html:81
-#: templates/question.html:447 templates/question_edit.html:91
+#: templates/answer_edit.html:62 templates/ask.html:94
+#: templates/question.html:452 templates/question_edit.html:91
msgid "Toggle the real time Markdown editor preview"
msgstr ""
-#: templates/answer_edit.html:62 templates/ask.html:81
-#: templates/question.html:447 templates/question_edit.html:91
+#: templates/answer_edit.html:62 templates/ask.html:94
+#: templates/question.html:452 templates/question_edit.html:91
msgid "toggle preview"
msgstr ""
#: templates/answer_edit.html:73 templates/question_edit.html:119
+#: templates/question_retag.html:75
msgid "Save edit"
msgstr ""
#: templates/answer_edit.html:74 templates/close.html:29
-#: templates/question_edit.html:120 templates/reopen.html:30
-#: templates/user_edit.html:83
+#: templates/question_edit.html:120 templates/question_retag.html:76
+#: templates/reopen.html:30 templates/user_edit.html:83
+#: templates/authopenid/changeemail.html:34
msgid "Cancel"
msgstr ""
-#: templates/ask.html:4 templates/ask.html.py:60
-msgid "Ask a question"
+#: templates/answer_edit_tips.html:4
+msgid "answer tips"
+msgstr "Tips"
+
+#: templates/answer_edit_tips.html:7
+msgid "please make your answer relevant to this community"
msgstr ""
-#: templates/ask.html:106
-msgid "Use"
+#: templates/answer_edit_tips.html:10
+msgid "try to give an answer, rather than engage into a discussion"
msgstr ""
-#: templates/ask.html:106
-msgid "learn more about OpenID"
+#: templates/answer_edit_tips.html:13
+msgid "please try to provide details"
msgstr ""
-#: templates/ask.html:106 templates/authopenid/signin.html:35
-#: templates/authopenid/signin.html:61
-msgid "Login"
+#: templates/answer_edit_tips.html:16 templates/question_edit_tips.html:13
+msgid "be clear and concise"
msgstr ""
-#: templates/ask.html:109
-msgid "Get your own "
+#: templates/answer_edit_tips.html:19 templates/question_edit_tips.html:16
+msgid "see frequently asked questions"
msgstr ""
-#: templates/ask.html:117 templates/authopenid/sendpw.html:27
-msgid "User name"
+#: templates/answer_edit_tips.html:25 templates/question_edit_tips.html:22
+msgid "Markdown tips"
+msgstr "Markdown basics"
+
+#: templates/answer_edit_tips.html:28 templates/question_edit_tips.html:25
+msgid "*italic* or __italic__"
msgstr ""
-#: templates/ask.html:120
-msgid "Email: (won't be shown to anyone)"
+#: templates/answer_edit_tips.html:31 templates/question_edit_tips.html:28
+msgid "**bold** or __bold__"
msgstr ""
-#: templates/ask.html:127
-msgid "Ask your question"
+#: templates/answer_edit_tips.html:34 templates/question_edit_tips.html:31
+msgid "link"
+msgstr ""
+
+#: templates/answer_edit_tips.html:34 templates/answer_edit_tips.html.py:38
+#: templates/question_edit_tips.html:31 templates/question_edit_tips.html:36
+msgid "text"
+msgstr ""
+
+#: templates/answer_edit_tips.html:38 templates/question_edit_tips.html:36
+msgid "image"
+msgstr ""
+
+#: templates/answer_edit_tips.html:42 templates/question_edit_tips.html:40
+msgid "numbered list:"
+msgstr ""
+
+#: templates/answer_edit_tips.html:47 templates/question_edit_tips.html:45
+msgid "basic HTML tags are also supported"
+msgstr ""
+
+#: templates/answer_edit_tips.html:50 templates/question_edit_tips.html:48
+msgid "learn more about Markdown"
msgstr ""
+#: templates/ask.html:4 templates/ask.html.py:60
+msgid "Ask a question"
+msgstr ""
+
+#: templates/ask.html:67
+msgid "login to post question info"
+msgstr ""
+"<span class=\"strong big\">You are welcome to start submitting your question "
+"anonymously</span> - you are currently not logged in. When you post your "
+"question, you will be redirected to the login/signup page. Your question "
+"will be saved meanwhile and will be posted when you log in. Login/signup "
+"process is very simple. Login takes about 30 seconds, initial signup takes a "
+"minute or less."
+
+#: templates/ask.html:73
+#, python-format
+msgid "must have valid %(email)s to post"
+msgstr ""
+"<span class='strong big'>Looks like your email address, %(email)s has not "
+"yet been validated.</span> To post messages you must verify your email, "
+"please see <a href='/faq#validate'>more details here</a>.<br>You can submit "
+"your question now and validate email after that. Your question will saved as "
+"pending meanwhile. "
+
+#: templates/ask.html:108
+msgid "(required)"
+msgstr ""
+
+#: templates/ask.html:115
+msgid "Login/signup to post your question"
+msgstr "Login/Signup to Post"
+
+#: templates/ask.html:117
+msgid "Ask your question"
+msgstr "Ask Your Question"
+
#: templates/badge.html:6 templates/badge.html.py:17
msgid "Badge"
msgstr ""
@@ -718,59 +836,57 @@ msgstr ""
msgid "The users have been awarded with badges:"
msgstr ""
-#: templates/badges.html:5
+#: templates/badges.html:6
msgid "Badges summary"
msgstr ""
-#: templates/badges.html:16 templates/user_stats.html:113
+#: templates/badges.html:17 templates/user_stats.html:113
msgid "Badges"
msgstr ""
-#: templates/badges.html:20
-msgid ""
-"Community gives you awards for your questions, answers and votes. Below is "
-"the list of available badges and number of times each type of badge has been "
-"awarded."
-msgstr ""
-
-#: templates/badges.html:47
-msgid "Community badges"
+#: templates/badges.html:21
+msgid "Community gives you awards for your questions, answers and votes."
msgstr ""
+"If your questions and answers are highly voted, your contribution to this "
+"Q&amp;A community will be recognized with the variety of badges."
-#: templates/badges.html:53
+#: templates/badges.html:22
msgid ""
-"Gold badge is very rare. To obtain it you have to show profound knowledge "
-"and ability in addition to actively participating in the community. Gold "
-"badge is the highest award in this community."
+"Below is the list of available badges and number of times each type of badge "
+"has been awarded."
msgstr ""
+"Currently badges differ only by their level: <strong>gold</strong>, "
+"<strong>silver</strong> and <strong>bronze</strong> (their meanings are "
+"described on the right). In the future there will be many types of badges at "
+"each level. <strong>Please give us your <a href=\"/faq#feedback\">feedback</"
+"a></strong> - what kinds of badges would you like to see and suggest the "
+"activity for which those badges might be awarded."
-#: templates/badges.html:61
-msgid ""
-"Obtaining silver badge requires significant patience. If you got one, you've "
-"very significantly contributed to this community"
-msgstr ""
+#: templates/badges.html:49
+msgid "Community badges"
+msgstr "Badge levels"
-#: templates/badges.html:64
-msgid "bronze badge: often given as a special honor"
+#: templates/badges.html:55
+msgid "gold badge description"
msgstr ""
+"Gold badge is the highest award in this community. To obtain it have to show "
+"profound knowledge and ability in addition to your active participation."
-#: templates/badges.html:68
-msgid ""
-"If you are active in this community, you will get this medal - still it is a "
-"special honor."
+#: templates/badges.html:63
+msgid "silver badge description"
msgstr ""
+"Obtaining silver badge requires significant patience. If you have received "
+"one, that means you have greatly contributed to this community."
-#: templates/base.html:61 templates/base_content.html:60
-msgid "congratulations, community gave you a badge"
-msgstr ""
-
-#: templates/base.html:63 templates/base_content.html:62
-msgid "profile"
+#: templates/badges.html:66
+msgid "bronze badge: often given as a special honor"
msgstr ""
-#: templates/base_content.html:61
-msgid "see"
+#: templates/badges.html:70
+msgid "bronze badge description"
msgstr ""
+"If you are an active participant in this community, you will be recognized "
+"with this badge."
#: templates/book.html:7
msgid "reading channel"
@@ -838,7 +954,7 @@ msgstr ""
msgid "number of times"
msgstr ""
-#: templates/book.html:105 templates/index.html:47
+#: templates/book.html:105 templates/index.html:48 templates/questions.html:46
#: templates/unanswered.html:37 templates/users_questions.html:30
msgid "votes"
msgstr ""
@@ -847,13 +963,14 @@ msgstr ""
msgid "the answer has been accepted to be correct"
msgstr ""
-#: templates/book.html:115 templates/index.html:48
+#: templates/book.html:115 templates/index.html:49 templates/questions.html:47
#: templates/unanswered.html:38 templates/users_questions.html:40
msgid "views"
msgstr ""
-#: templates/book.html:125 templates/index.html:68 templates/question.html:112
-#: templates/question.html.py:479 templates/tags.html:46
+#: templates/book.html:125 templates/index.html:69 templates/question.html:115
+#: templates/question.html.py:486 templates/questions.html:84
+#: templates/questions.html.py:156 templates/tags.html:47
#: templates/unanswered.html:75 templates/unanswered.html.py:109
#: templates/users_questions.html:52
msgid "using tags"
@@ -863,7 +980,7 @@ msgstr ""
msgid "subscribe to book RSS feed"
msgstr ""
-#: templates/book.html:147 templates/index.html:115
+#: templates/book.html:147 templates/index.html:116
msgid "subscribe to the questions feed"
msgstr ""
@@ -883,44 +1000,255 @@ msgstr ""
msgid "OK to close"
msgstr ""
-#: templates/footer.html:5
-msgid "About us"
+#: templates/faq.html:11
+msgid "Frequently Asked Questions "
msgstr ""
-#: templates/footer.html:6 templates/header.html:13 templates/index.html:83
-msgid "faq"
+#: templates/faq.html:16
+msgid "What kinds of questions can I ask here?"
+msgstr ""
+
+#: templates/faq.html:17
+msgid ""
+"Most importanly - questions should be <strong>relevant</strong> to this "
+"community."
+msgstr ""
+
+#: templates/faq.html:18
+msgid ""
+"Before asking the question - please make sure to use search to see whether "
+"your question has alredy been answered."
+msgstr ""
+"Before you ask - please make sure to search for a similar question. You can "
+"search questions by their title or tags."
+
+#: templates/faq.html:21
+msgid "What questions should I avoid asking?"
+msgstr "What kinds of questions should be avoided?"
+
+#: templates/faq.html:22
+msgid ""
+"Please avoid asking questions that are not relevant to this community, too "
+"subjective and argumentative."
+msgstr ""
+
+#: templates/faq.html:27
+msgid "What should I avoid in my answers?"
+msgstr ""
+
+#: templates/faq.html:28 templates/faq.html.py:132
+msgid "site title"
+msgstr ""
+
+#: templates/faq.html:28
+msgid ""
+"is a Q&A site, not a discussion group. Therefore - please avoid having "
+"discussions in your answers, comment facility allows some space for brief "
+"discussions."
+msgstr ""
+"is a <strong>question and answer</strong> site - <strong>it is not a "
+"discussion group</strong>. Please avoid holding debates in your answers as "
+"they tend to dilute the essense of questions and answers. For the brief "
+"discussions please use commenting facility."
+
+#: templates/faq.html:32
+msgid "Who moderates this community?"
+msgstr ""
+
+#: templates/faq.html:33
+msgid "The short answer is: <strong>you</strong>."
+msgstr ""
+
+#: templates/faq.html:34
+msgid "This website is moderated by the users."
+msgstr ""
+
+#: templates/faq.html:35
+msgid ""
+"The reputation system allows users earn the authorization to perform a "
+"variety of moderation tasks."
+msgstr ""
+
+#: templates/faq.html:40
+msgid "How does reputation system work?"
+msgstr ""
+
+#: templates/faq.html:41
+msgid "Rep system summary"
+msgstr ""
+"When a question or answer is upvoted, the user who posted them will gain "
+"some points, which are called \"reputation points\". These points serve as a "
+"rough measure of the community trust to him/her. Various moderation tasks "
+"are gradually assigned to the users based on those points."
+
+#: templates/faq.html:59 templates/user_votes.html:14
+msgid "upvote"
+msgstr ""
+
+#: templates/faq.html:63
+msgid "use tags"
+msgstr ""
+
+#: templates/faq.html:68
+msgid "add comments"
+msgstr ""
+
+#: templates/faq.html:72 templates/user_votes.html:16
+msgid "downvote"
msgstr ""
-#: templates/footer.html:8
-msgid "Contact"
+#: templates/faq.html:75
+msgid "open and close own questions"
+msgstr ""
+
+#: templates/faq.html:79
+msgid "retag questions"
+msgstr ""
+
+#: templates/faq.html:83
+msgid "edit community wiki questions"
+msgstr ""
+
+#: templates/faq.html:87
+msgid "edit any answer"
+msgstr ""
+
+#: templates/faq.html:91
+msgid "open any closed question"
+msgstr ""
+
+#: templates/faq.html:95
+msgid "delete any comment"
+msgstr ""
+
+#: templates/faq.html:99
+msgid "delete any questions and answers and perform other moderation tasks"
+msgstr ""
+
+#: templates/faq.html:106
+msgid "how to validate email title"
+msgstr "How to validate email and why?"
+
+#: templates/faq.html:108
+msgid "how to validate email info"
+msgstr ""
+"<form style='margin:0;padding:0;' action='/email/sendkey/'><p><span class="
+"\"bigger strong\">How?</span> If you have just set or changed your email "
+"address - <strong>check your email and click the included link</strong>."
+"<br>The link contains a key generated specifically for you. You can also "
+"<button style='display:inline' type='submit'><strong>get a new key</strong></"
+"button> and check your email again.</p></form><span class=\"bigger strong"
+"\">Why?</span> Email validation is required to make sure that <strong>only "
+"you can post messages</strong> on your behalf and to <strong>minimize spam</"
+"strong> posts.<br>With email you can <strong>subscribe for updates</strong> "
+"on the most interesting questions. Also, when you sign up for the first time "
+"- create a unique <a href='/faq#gravatar'><strong>gravatar</strong></a> "
+"personal image.</p>"
+
+#: templates/faq.html:112
+msgid "what is gravatar"
+msgstr "What is gravatar?"
+
+#: templates/faq.html:113
+msgid "gravatar faq info"
+msgstr ""
+"<strong>Gravatar</strong> means <strong>g</strong>lobally <strong>r</"
+"strong>ecognized <strong>avatar</strong> - your unique avatar image "
+"associated with your email address. It's simply a picture that shows next to "
+"your posts on the websites that support gravatar protocol. By default gravar "
+"appears as a square filled with a snowflake-like figure. You can <strong>set "
+"your image</strong> at <a href='http://gravatar.com'><strong>gravatar.com</"
+"strong></a>"
+
+#: templates/faq.html:116
+msgid "To register, do I need to create new password?"
+msgstr ""
+
+#: templates/faq.html:117
+msgid ""
+"No, you don't have to. You can login through any service that supports "
+"OpenID, e.g. Google, Yahoo, AOL, etc."
+msgstr ""
+
+#: templates/faq.html:118
+msgid "Login now!"
+msgstr ""
+
+#: templates/faq.html:123
+msgid "Why other people can edit my questions/answers?"
+msgstr ""
+
+#: templates/faq.html:124
+msgid "Goal of this site is..."
+msgstr ""
+
+#: templates/faq.html:124
+msgid ""
+"So questions and answers can be edited like wiki pages by experienced users "
+"of this site and this improves the overall quality of the knowledge base "
+"content."
+msgstr ""
+
+#: templates/faq.html:125
+msgid "If this approach is not for you, we respect your choice."
+msgstr ""
+
+#: templates/faq.html:129
+msgid "Still have questions?"
+msgstr ""
+
+#: templates/faq.html:130
+msgid "Please ask your question, help make our community better!"
+msgstr ""
+"Please <a href=\"/questions/ask\">ask</a> your question, help make our "
+"community better!"
+
+#: templates/faq.html:132 templates/header.html:29 templates/header.html.py:60
+msgid "questions"
+msgstr ""
+
+#: templates/faq.html:132 templates/index.html:121
+msgid "."
+msgstr ""
+
+#: templates/footer.html:7 templates/header.html:14 templates/index.html:83
+msgid "about"
+msgstr ""
+
+#: templates/footer.html:8 templates/header.html:15 templates/index.html:84
+msgid "faq"
msgstr ""
#: templates/footer.html:9
-msgid "Privacy"
+msgid "blog"
msgstr ""
#: templates/footer.html:10
-msgid "Feedback"
+msgid "contact us"
msgstr ""
-#: templates/header.html:8
-msgid "logout"
+#: templates/footer.html:11
+msgid "privacy policy"
msgstr ""
-#: templates/header.html:10 templates/authopenid/signup.html:39
-msgid "login"
+#: templates/footer.html:12
+msgid "give feedback"
msgstr ""
-#: templates/header.html:12 templates/index.html:82
-msgid "about"
+#: templates/footer.html:18
+msgid "current revision"
msgstr ""
-#: templates/header.html:23
-msgid "back to home page"
+#: templates/header.html:10
+msgid "logout"
msgstr ""
-#: templates/header.html:29 templates/header.html.py:60
-msgid "questions"
+#: templates/header.html:12 templates/authopenid/signup.html:41
+msgid "login"
+msgstr ""
+
+#: templates/header.html:23
+msgid "back to home page"
msgstr ""
#: templates/header.html:31 templates/header.html.py:62
@@ -931,9 +1259,9 @@ msgstr ""
msgid "books"
msgstr ""
-#: templates/header.html:34 templates/index.html:120
+#: templates/header.html:34
msgid "unanswered questions"
-msgstr ""
+msgstr "unanswered"
#: templates/header.html:38
msgid "my profile"
@@ -947,107 +1275,109 @@ msgstr ""
msgid "search"
msgstr ""
-#: templates/index.html:6
+#: templates/index.html:7
msgid "Home"
msgstr ""
-#: templates/index.html:21
+#: templates/index.html:22 templates/questions.html:7
msgid "Questions"
msgstr ""
-#: templates/index.html:23
+#: templates/index.html:24
msgid "last updated questions"
msgstr ""
-#: templates/index.html:23 templates/unanswered.html:20
+#: templates/index.html:24 templates/questions.html:25
+#: templates/unanswered.html:20
msgid "newest"
msgstr ""
-#: templates/index.html:24
+#: templates/index.html:25 templates/questions.html:27
msgid "hottest questions"
msgstr ""
-#: templates/index.html:24
+#: templates/index.html:25 templates/questions.html:27
msgid "hottest"
msgstr ""
-#: templates/index.html:25
+#: templates/index.html:26 templates/questions.html:28
msgid "most voted questions"
msgstr ""
-#: templates/index.html:25
+#: templates/index.html:26 templates/questions.html:28
msgid "most voted"
msgstr ""
-#: templates/index.html:26
+#: templates/index.html:27
msgid "all questions"
msgstr ""
-#: templates/index.html:46 templates/unanswered.html:36
-#: templates/users_questions.html:35
+#: templates/index.html:47 templates/questions.html:45
+#: templates/unanswered.html:36 templates/users_questions.html:35
msgid "answers"
msgstr ""
-#: templates/index.html:68 templates/question.html:112
-#: templates/question.html.py:479 templates/tags.html:46
+#: templates/index.html:69 templates/question.html:115
+#: templates/question.html.py:486 templates/questions.html:84
+#: templates/questions.html.py:156 templates/tags.html:47
#: templates/unanswered.html:75 templates/unanswered.html.py:109
#: templates/users_questions.html:52
msgid "see questions tagged"
msgstr ""
-#: templates/index.html:79
+#: templates/index.html:80
msgid "welcome to website"
msgstr ""
-#: templates/index.html:88
+#: templates/index.html:89
msgid "Recent tags"
msgstr ""
-#: templates/index.html:93
+#: templates/index.html:94
#, python-format
msgid "see questions tagged '%(tagname)s'"
msgstr ""
-#: templates/index.html:96 templates/index.html.py:120
+#: templates/index.html:97 templates/index.html.py:121
msgid "popular tags"
-msgstr ""
+msgstr "tags"
-#: templates/index.html:100
+#: templates/index.html:101
msgid "Recent awards"
-msgstr ""
+msgstr "Recent badges"
-#: templates/index.html:106
+#: templates/index.html:107
msgid "given to"
msgstr ""
-#: templates/index.html:111
+#: templates/index.html:112
msgid "all awards"
-msgstr ""
+msgstr "all badges"
-#: templates/index.html:115
+#: templates/index.html:116
msgid "subscribe to last 30 questions by RSS"
msgstr ""
-#: templates/index.html:120
+#: templates/index.html:121
msgid "Still looking for more? See"
msgstr ""
-#: templates/index.html:120
-msgid "complete list of quesionts"
-msgstr ""
+#: templates/index.html:121
+msgid "complete list of questions"
+msgstr "list of all questions"
-#: templates/index.html:120
+#: templates/index.html:121
msgid "or"
msgstr ""
-#: templates/index.html:120
-msgid "."
-msgstr ""
-
-#: templates/index.html:120
+#: templates/index.html:121
msgid "Please help us answer"
msgstr ""
+#: templates/index.html:121
+msgid "list of unanswered questions"
+msgstr "unanswered questions"
+
#: templates/logout.html:6 templates/logout.html.py:17
msgid "Logout"
msgstr ""
@@ -1060,14 +1390,30 @@ msgstr ""
#: templates/logout.html:21
msgid "Logout now"
+msgstr "Logout Now"
+
+#: templates/pagesize.html:6
+msgid "posts per page"
+msgstr ""
+
+#: templates/paginator.html:6 templates/paginator.html.py:7
+msgid "previous"
msgstr ""
-#: templates/pagesize.html:5
-msgid "Size per page:"
+#: templates/paginator.html:19
+msgid "current page"
msgstr ""
-#: templates/paginator.html:5
-msgid "Previous"
+#: templates/paginator.html:22 templates/paginator.html.py:29
+msgid "page number "
+msgstr ""
+
+#: templates/paginator.html:22 templates/paginator.html.py:29
+msgid "number - make blank in english"
+msgstr ""
+
+#: templates/paginator.html:33
+msgid "next page"
msgstr ""
#: templates/privacy.html:6 templates/privacy.html.py:11
@@ -1114,178 +1460,187 @@ msgstr ""
msgid "how privacy policies can be changed"
msgstr ""
-#: templates/question.html:66 templates/question.html.py:78
+#: templates/question.html:69 templates/question.html.py:81
msgid "i like this post (click again to cancel)"
msgstr ""
-#: templates/question.html:68 templates/question.html.py:80
-#: templates/question.html:273
+#: templates/question.html:71 templates/question.html.py:83
+#: templates/question.html:276
msgid "current number of votes"
msgstr ""
-#: templates/question.html:73 templates/question.html.py:84
+#: templates/question.html:76 templates/question.html.py:87
msgid "i dont like this post (click again to cancel)"
msgstr ""
-#: templates/question.html:90
+#: templates/question.html:93
msgid "mark this question as favorite (click again to cancel)"
msgstr ""
-#: templates/question.html:96
+#: templates/question.html:99
msgid "remove favorite mark from this question (click again to restore mark)"
msgstr ""
-#: templates/question.html:121 templates/question.html.py:304
+#: templates/question.html:124 templates/question.html.py:307
#: templates/revisions_answer.html:53 templates/revisions_question.html:53
msgid "edit"
msgstr ""
-#: templates/question.html:125 templates/question.html.py:314
+#: templates/question.html:128 templates/question.html.py:317
msgid "delete"
msgstr ""
-#: templates/question.html:130
+#: templates/question.html:133
msgid "reopen"
msgstr ""
-#: templates/question.html:135
+#: templates/question.html:138
msgid "close"
msgstr ""
-#: templates/question.html:141 templates/question.html.py:327
+#: templates/question.html:144 templates/question.html.py:330
msgid ""
"report as offensive (i.e containing spam, advertising, malicious text, etc.)"
msgstr ""
-#: templates/question.html:142 templates/question.html.py:328
+#: templates/question.html:145 templates/question.html.py:331
msgid "flag offensive"
msgstr ""
-#: templates/question.html:154 templates/question.html.py:337
+#: templates/question.html:157 templates/question.html.py:340
#: templates/revisions_answer.html:65 templates/revisions_question.html:65
msgid "updated"
msgstr ""
-#: templates/question.html:203 templates/question.html.py:384
+#: templates/question.html:206 templates/question.html.py:387
#: templates/revisions_answer.html:63 templates/revisions_question.html:63
msgid "asked"
msgstr ""
-#: templates/question.html:233 templates/question.html.py:411
+#: templates/question.html:236 templates/question.html.py:414
msgid "comments"
msgstr ""
-#: templates/question.html:234 templates/question.html.py:412
+#: templates/question.html:237 templates/question.html.py:415
msgid "add comment"
msgstr ""
-#: templates/question.html:247
+#: templates/question.html:250
#, python-format
msgid ""
"The question has been closed for the following reason \"%(question."
"get_close_reason_display)s\" by"
msgstr ""
-#: templates/question.html:249
+#: templates/question.html:252
#, python-format
msgid "close date %(question.closed_at)s"
msgstr ""
-#: templates/question.html:256 templates/questions.html:44
-#: templates/user_stats.html:28
+#: templates/question.html:259 templates/user_stats.html:28
msgid "Answers"
-msgstr ""
+msgstr " Answers"
-#: templates/question.html:258
+#: templates/question.html:261
msgid "oldest answers will be shown first"
msgstr ""
-#: templates/question.html:258
+#: templates/question.html:261
msgid "oldest answers"
-msgstr ""
+msgstr "oldest"
-#: templates/question.html:259
+#: templates/question.html:262
msgid "newest answers will be shown first"
msgstr ""
-#: templates/question.html:259
+#: templates/question.html:262
msgid "newest answers"
-msgstr ""
+msgstr "newest"
-#: templates/question.html:260
+#: templates/question.html:263
msgid "most voted answers will be shown first"
msgstr ""
-#: templates/question.html:260
+#: templates/question.html:263
msgid "popular answers"
-msgstr ""
+msgstr "most voted"
-#: templates/question.html:272
+#: templates/question.html:275
msgid "i like this answer (click again to cancel)"
msgstr ""
-#: templates/question.html:278
+#: templates/question.html:281
msgid "i dont like this answer (click again to cancel)"
msgstr ""
-#: templates/question.html:284
+#: templates/question.html:287
msgid "mark this answer as favorite (click again to undo)"
msgstr ""
-#: templates/question.html:289
+#: templates/question.html:292
msgid "the author of the question has selected this answer as correct"
msgstr ""
-#: templates/question.html:311
+#: templates/question.html:314
msgid "undelete"
msgstr ""
-#: templates/question.html:321
+#: templates/question.html:324
msgid "answer permanent link"
msgstr ""
-#: templates/question.html:322
+#: templates/question.html:325
msgid "permanent link"
msgstr ""
-#: templates/question.html:436
+#: templates/question.html:438
msgid "Your answer"
msgstr ""
-#: templates/question.html:460
+#: templates/question.html:441
+msgid "you can answer anonymously and then login"
+msgstr ""
+"<span class='strong big'>You are now not logged in</span> but you can answer "
+"first and then login"
+
+#: templates/question.html:465
msgid "Answer the question"
msgstr ""
-#: templates/question.html:462
-msgid "Login to answer"
+#: templates/question.html:467
+msgid "Notify me daily if there are any new answers."
msgstr ""
-#: templates/question.html:474
+#: templates/question.html:469
+msgid "once you sign in you will be able to subscribe for any updates here"
+msgstr "Here logged in users can sign up for the question updates."
+
+#: templates/question.html:481
msgid "Question tags"
-msgstr ""
+msgstr "Tags"
-#: templates/question.html:484
+#: templates/question.html:491
msgid "question asked"
-msgstr ""
+msgstr "Asked"
-#: templates/question.html:484 templates/question.html.py:490
+#: templates/question.html:491 templates/question.html.py:497
#: templates/user_info.html:51
msgid "ago"
msgstr ""
-#: templates/question.html:487
+#: templates/question.html:494
msgid "question was seen"
-msgstr ""
+msgstr "Seen"
-#: templates/question.html:487
+#: templates/question.html:494
msgid "times"
msgstr ""
-#: templates/question.html:490
+#: templates/question.html:497
msgid "last updated"
-msgstr ""
+msgstr "Last updated"
-#: templates/question.html:495
+#: templates/question.html:502
msgid "Related questions"
msgstr ""
@@ -1295,7 +1650,7 @@ msgstr ""
#: templates/question_edit_tips.html:4
msgid "question tips"
-msgstr ""
+msgstr "Tips"
#: templates/question_edit_tips.html:7
msgid "please ask a relevant question"
@@ -1303,119 +1658,140 @@ msgstr ""
#: templates/question_edit_tips.html:10
msgid "please try provide enough details"
-msgstr ""
-
-#: templates/question_edit_tips.html:13
-msgid "be clear and concise"
-msgstr ""
-
-#: templates/question_edit_tips.html:16
-msgid "see frequently asked questions"
-msgstr ""
+msgstr "provide enough details"
-#: templates/question_edit_tips.html:22
-msgid "Markdown tips"
+#: templates/question_retag.html:3 templates/question_retag.html.py:52
+msgid "Change tags"
msgstr ""
-#: templates/question_edit_tips.html:25
-msgid "*italic* or __italic__"
+#: templates/question_retag.html:39
+msgid "up to 5 tags, less than 20 characters each"
msgstr ""
-#: templates/question_edit_tips.html:28
-msgid "**bold** or __bold__"
+#: templates/question_retag.html:86
+msgid "Why use and modify tags?"
msgstr ""
-#: templates/question_edit_tips.html:31
-msgid "link"
+#: templates/question_retag.html:89
+msgid "tags help us keep Questions organized"
msgstr ""
-#: templates/question_edit_tips.html:31 templates/question_edit_tips.html:36
-msgid "text"
+#: templates/question_retag.html:95
+msgid "tag editors receive special awards from the community"
msgstr ""
-#: templates/question_edit_tips.html:36
-msgid "image"
-msgstr ""
+#: templates/questions.html:23
+msgid "Found by tags"
+msgstr "Tagged questions"
-#: templates/question_edit_tips.html:40
-msgid "numbered list:"
+#: templates/questions.html:23
+msgid "Found by title"
msgstr ""
-#: templates/question_edit_tips.html:45
-msgid "basic HTML tags are also supported"
-msgstr ""
-
-#: templates/question_edit_tips.html:48
-msgid "learn more about Markdown"
-msgstr ""
-
-#: templates/questions.html:6
-msgid "Question list"
-msgstr ""
-
-#: templates/questions.html:22
-msgid "Tagged questions"
-msgstr ""
-
-#: templates/questions.html:22
-msgid "Query result"
-msgstr ""
-
-#: templates/questions.html:22
+#: templates/questions.html:23
msgid "All questions"
msgstr ""
-#: templates/questions.html:24
-msgid "New questions"
-msgstr ""
-
-#: templates/questions.html:24
-msgid "Newest"
-msgstr ""
-
-#: templates/questions.html:25
-msgid "Newest updated questions"
-msgstr ""
-
-#: templates/questions.html:25
-msgid "Active"
+#: templates/questions.html:25 templates/unanswered.html:20
+msgid "most recently asked questions"
msgstr ""
#: templates/questions.html:26
-msgid "Questions with most answers"
+msgid "most recently updated questions"
msgstr ""
#: templates/questions.html:26
-msgid "Hottest"
+msgid "active"
msgstr ""
-#: templates/questions.html:27
-msgid "Questions with most votes"
-msgstr ""
+#: templates/questions.html:109
+#, python-format
+msgid ""
+"\n"
+"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
+"\t\t\t"
+msgid_plural ""
+"\n"
+"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
+"\t\t\t"
+msgstr[0] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>question tagged</p><p><span "
+"class=\"tag\">%(tagname)s</span></p>"
+msgstr[1] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>questions tagged</p><div "
+"class=\"tags\"><span class=\"tag\">%(tagname)s</span></div>"
+
+#: templates/questions.html:116
+#, python-format
+msgid ""
+"\n"
+"\t\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
+"\t\t\t\t"
+msgid_plural ""
+"\n"
+"\t\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
+"\t\t\t\t"
+msgstr[0] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>question with title "
+"containing <strong><span class=\"darkred\">%(searchtitle)s</span></strong></"
+"p>"
+msgstr[1] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>questions with title "
+"containing <strong><span class=\"darkred\">%(searchtitle)s</span></strong></"
+"p>"
+
+#: templates/questions.html:122
+#, python-format
+msgid ""
+"\n"
+"\t\t\t\thave total %(q_num)s questions\n"
+"\t\t\t\t"
+msgid_plural ""
+"\n"
+"\t\t\t\thave total %(q_num)s questions\n"
+"\t\t\t\t"
+msgstr[0] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>question</p>"
+msgstr[1] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>questions</p>"
-#: templates/questions.html:27
-msgid "Best"
-msgstr ""
+#: templates/questions.html:131
+msgid "latest questions info"
+msgstr "<strong>Newest</strong> questions are shown first."
-#: templates/questions.html:45
-msgid "Votes"
+#: templates/questions.html:135
+msgid "Questions are sorted by the <strong>time of last update</strong>."
msgstr ""
-#: templates/questions.html:46
-msgid "Visits"
-msgstr ""
+#: templates/questions.html:136
+msgid "Most recently answered ones are shown first."
+msgstr "<strong>Most recently answered</strong> questions are shown first."
+
+#: templates/questions.html:140
+msgid "Questions sorted by <strong>number of responses</strong>."
+msgstr "Questions sorted by the <strong>number of answers</strong>."
+
+#: templates/questions.html:141
+msgid "Most answered questions are shown first."
+msgstr " "
-#: templates/questions.html:57 templates/questions.html.py:69
-msgid "Community wiki"
+#: templates/questions.html:145
+msgid "Questions are sorted by the <strong>number of votes</strong>."
msgstr ""
-#: templates/questions.html:83
-msgid "Browse questions with tag of "
+#: templates/questions.html:146
+msgid "Most voted questions are shown first."
msgstr ""
-#: templates/questions.html:125 templates/unanswered.html:105
+#: templates/questions.html:153 templates/unanswered.html:105
msgid "Related tags"
-msgstr ""
+msgstr "Tags"
#: templates/reopen.html:6 templates/reopen.html.py:16
msgid "Reopen question"
@@ -1450,35 +1826,35 @@ msgstr ""
msgid "Revision history"
msgstr ""
-#: templates/tags.html:5 templates/tags.html.py:28
+#: templates/tags.html:6 templates/tags.html.py:29
msgid "Tag list"
msgstr ""
-#: templates/tags.html:30
+#: templates/tags.html:31
msgid "sorted alphabetically"
msgstr ""
-#: templates/tags.html:30
+#: templates/tags.html:31
msgid "by name"
msgstr ""
-#: templates/tags.html:31
+#: templates/tags.html:32
msgid "sorted by frequency of tag use"
msgstr ""
-#: templates/tags.html:31
+#: templates/tags.html:32
msgid "by popularity"
msgstr ""
-#: templates/tags.html:37
+#: templates/tags.html:38
msgid "All tags matching query"
msgstr ""
-#: templates/tags.html:37
+#: templates/tags.html:38
msgid "all tags - make this empty in english"
msgstr ""
-#: templates/tags.html:40
+#: templates/tags.html:41
msgid "Nothing found"
msgstr ""
@@ -1486,14 +1862,12 @@ msgstr ""
msgid "Unanswered questions"
msgstr ""
-#: templates/unanswered.html:20
-msgid "most recently asked questions"
-msgstr ""
-
#: templates/unanswered.html:97
#, python-format
msgid "have %(num_q)s unanswered questions"
msgstr ""
+"<div class=\"questions-count\">%(num_q)s</div><strong>unanswered</strong> "
+"questions"
#: templates/unanswered.html:99
msgid "Have a total of"
@@ -1513,7 +1887,7 @@ msgstr ""
#: templates/user_edit.html:31
msgid "avatar"
-msgstr ""
+msgstr "<a href='/faq#gravatar'>gravatar</a>"
#: templates/user_edit.html:36 templates/user_info.html:31
msgid "Registered user"
@@ -1553,16 +1927,40 @@ msgstr ""
#: templates/user_info.html:70
msgid "age unit"
-msgstr ""
+msgstr "years old"
-#: templates/user_info.html:75
+#: templates/user_info.html:76
msgid "todays unused votes"
msgstr ""
-#: templates/user_info.html:76
+#: templates/user_info.html:77
msgid "votes left"
msgstr ""
+#: templates/user_preferences.html:10
+msgid "Connect with Twitter"
+msgstr ""
+
+#: templates/user_preferences.html:12
+msgid "Twitter account name:"
+msgstr ""
+
+#: templates/user_preferences.html:14
+msgid "Twitter password:"
+msgstr ""
+
+#: templates/user_preferences.html:16
+msgid "Send my Questions to Twitter"
+msgstr ""
+
+#: templates/user_preferences.html:17
+msgid "Send my Answers to Twitter"
+msgstr ""
+
+#: templates/user_preferences.html:18
+msgid "Save"
+msgstr ""
+
#: templates/user_stats.html:15
msgid "User questions"
msgstr ""
@@ -1583,7 +1981,7 @@ msgstr ""
#: templates/user_stats.html:56
msgid "votes total"
-msgstr ""
+msgstr "Votes"
#: templates/user_stats.html:65
msgid "user has voted up this many times"
@@ -1618,40 +2016,32 @@ msgstr ""
msgid "favorites"
msgstr ""
-#: templates/user_tabs.html:29
+#: templates/user_tabs.html:28
msgid "settings"
msgstr ""
-#: templates/user_votes.html:14
-msgid "upvote"
-msgstr ""
-
-#: templates/user_votes.html:16
-msgid "downvote"
-msgstr ""
-
-#: templates/users.html:5 templates/users.html.py:23
+#: templates/users.html:6 templates/users.html.py:24
msgid "Users"
msgstr ""
-#: templates/users.html:26
+#: templates/users.html:27
msgid "recent"
msgstr ""
-#: templates/users.html:27
+#: templates/users.html:28
msgid "oldest"
msgstr ""
-#: templates/users.html:28
+#: templates/users.html:29
msgid "by username"
msgstr ""
-#: templates/users.html:34
+#: templates/users.html:35
#, python-format
msgid "users matching query %(suser)s:"
msgstr ""
-#: templates/users.html:38
+#: templates/users.html:39
msgid "Nothing found."
msgstr ""
@@ -1663,114 +2053,179 @@ msgstr ""
msgid "this answer has been accepted to be correct"
msgstr ""
-#: templates/authopenid/changeemail.html:6
-msgid "Account: change email"
-msgstr ""
+#: templates/authopenid/changeemail.html:7
+#: templates/authopenid/changeemail.html:33
+msgid "Change email"
+msgstr "Change Email"
-#: templates/authopenid/changeemail.html:9
-msgid ""
-"This is where you can change the email address associated with your account. "
-"Please keep this email address up to date so we can send you a password-"
-"reset email if you request one."
+#: templates/authopenid/changeemail.html:10
+#, python-format
+msgid "change %(email)s info"
msgstr ""
+"<span class=\"strong big\">Enter your new email into the box below</span> if "
+"you'd like to use another email for <strong>update subscriptions</strong>."
+"<br>Currently you are using <strong>%(email)s</strong>"
-#: templates/authopenid/changeemail.html:11
-#: templates/authopenid/changeopenid.html:13
-#: templates/authopenid/changepw.html:18 templates/authopenid/delete.html:14
-#: templates/authopenid/delete.html:24
+#: templates/authopenid/changeemail.html:13
+#: templates/authopenid/changeopenid.html:14
+#: templates/authopenid/changepw.html:19 templates/authopenid/delete.html:15
+#: templates/authopenid/delete.html:25
msgid "Please correct errors below:"
msgstr ""
-#: templates/authopenid/changeemail.html:28
-msgid "Email"
+#: templates/authopenid/changeemail.html:30
+msgid "Your new Email"
msgstr ""
+"<strong>Your new Email:</strong> (will <strong>not</strong> be shown to "
+"anyone, must be valid)"
-#: templates/authopenid/changeemail.html:29
-#: templates/authopenid/signin.html:60
+#: templates/authopenid/changeemail.html:31
+#: templates/authopenid/signin.html:138
msgid "Password"
msgstr ""
-#: templates/authopenid/changeemail.html:31
-msgid "Change email"
+#: templates/authopenid/changeemail.html:42
+msgid "Validate email"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:45
+#, python-format
+msgid "validate %(email)s info"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:50
+msgid "Email not changed"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:53
+#, python-format
+msgid "old %(email)s kept"
+msgstr ""
+"<span class=\"strong big\">Your email address %(email)s has not been changed."
+"</span> If you decide to change it later - you can always do it by editing "
+"it in your user profile or by using the <a href=\"/email/change/"
+"\"><strong>previous form</strong></a> again."
+
+#: templates/authopenid/changeemail.html:58
+msgid "Email changed"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:61
+#, python-format
+msgid "your current %(email)s can be used for this"
+msgstr ""
+"<span class='big strong'>Your email address is now set to %(email)s.</span> "
+"Updates on the questions that you like most will be sent to this address. "
+"Email notifications are sent once a day or less frequently - only when there "
+"are any news."
+
+#: templates/authopenid/changeemail.html:66
+msgid "Email verified"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:69
+msgid "thanks for verifying email"
msgstr ""
+"<span class=\"big strong\">Thank you for verifying your email!</span> Now "
+"you can <strong>ask</strong> and <strong>answer</strong> questions. Also if "
+"you find a very interesting question you can <strong>subscribe for the "
+"updates</strong> - then will be notified about changes <strong>once a day</"
+"strong> or less frequently."
-#: templates/authopenid/changeopenid.html:7
+#: templates/authopenid/changeemail.html:74
+msgid "email key not sent"
+msgstr "Validation email not sent"
+
+#: templates/authopenid/changeemail.html:77
+#, python-format
+msgid "email key not sent %(email)s change email here %(change_link)s"
+msgstr ""
+"<span class='big strong'>Your current email address %(email)s has been "
+"validated before</span> so the new key was not sent. You can <a href='%"
+"(change_link)s'>change</a> email used for update subscriptions if necessary."
+
+#: templates/authopenid/changeopenid.html:8
msgid "Account: change OpenID URL"
msgstr ""
-#: templates/authopenid/changeopenid.html:11
+#: templates/authopenid/changeopenid.html:12
msgid ""
"This is where you can change your OpenID URL. Make sure you remember it!"
msgstr ""
-#: templates/authopenid/changeopenid.html:28
+#: templates/authopenid/changeopenid.html:29
msgid "OpenID URL:"
msgstr ""
-#: templates/authopenid/changeopenid.html:29
+#: templates/authopenid/changeopenid.html:30
msgid "Change OpenID"
msgstr ""
-#: templates/authopenid/changepw.html:13
+#: templates/authopenid/changepw.html:14
msgid "Account: change password"
msgstr ""
-#: templates/authopenid/changepw.html:16
+#: templates/authopenid/changepw.html:17
msgid "This is where you can change your password. Make sure you remember it!"
msgstr ""
-#: templates/authopenid/changepw.html:26
+#: templates/authopenid/changepw.html:27
msgid "Current password"
msgstr ""
-#: templates/authopenid/changepw.html:27
+#: templates/authopenid/changepw.html:28
msgid "New password"
msgstr ""
-#: templates/authopenid/changepw.html:28
+#: templates/authopenid/changepw.html:29
msgid "New password again"
msgstr ""
-#: templates/authopenid/changepw.html:29 templates/authopenid/settings.html:28
+#: templates/authopenid/changepw.html:30 templates/authopenid/settings.html:29
msgid "Change password"
msgstr ""
-#: templates/authopenid/complete.html:4
+#: templates/authopenid/complete.html:5
msgid "Connect your OpenID with this site"
-msgstr ""
+msgstr "New user signup"
-#: templates/authopenid/complete.html:7
+#: templates/authopenid/complete.html:8
msgid "Connect your OpenID with your account on this site"
-msgstr ""
+msgstr "New user signup"
-#: templates/authopenid/complete.html:10
-msgid "Your OpenID is accepted. Please complete this to finish registration."
+#: templates/authopenid/complete.html:12
+#, python-format
+msgid "register new %(provider)s account info"
msgstr ""
+"<p><span class=\"big strong\">You are here for the first time with your %"
+"(provider)s login.</span> Please create your <strong>screen name</strong> "
+"and save your <strong>email</strong> address. Saved email address will let "
+"you <strong>subscribe for the updates</strong> on the most interesting "
+"questions and will be used to create and retrieve your unique avatar image - "
+"<a href='/faq#gravatar'><strong>gravatar</strong></a>."
-#: templates/authopenid/complete.html:11
+#: templates/authopenid/complete.html:14
msgid "This account already exists, please use another."
msgstr ""
-#: templates/authopenid/complete.html:16 templates/authopenid/complete.html:29
-#: templates/authopenid/signin.html:43
+#: templates/authopenid/complete.html:19 templates/authopenid/complete.html:32
+#: templates/authopenid/signin.html:121
msgid "Sorry, looks like we have some errors:"
msgstr ""
-#: templates/authopenid/complete.html:45
-msgid "New account"
-msgstr ""
-
-#: templates/authopenid/complete.html:46
-msgid "User name (<i>will be shown to others, cannot be modified</i>)"
-msgstr ""
-
#: templates/authopenid/complete.html:47
-msgid "Email (<i>not shared with anyone</i>)"
-msgstr ""
+msgid "Screen name label"
+msgstr "<strong>Screen Name</strong> (<i>will be shown to others</i>)"
#: templates/authopenid/complete.html:48
-msgid "create account"
+msgid "Email address label"
msgstr ""
+"<strong>Email Address</strong> (<i>will <strong>not</strong> be shared with "
+"anyone, must be valid</i>)"
+
+#: templates/authopenid/complete.html:49
+msgid "create account"
+msgstr "Signup"
#: templates/authopenid/complete.html:56
msgid "Existing account"
@@ -1788,186 +2243,328 @@ msgstr ""
msgid "Register"
msgstr ""
-#: templates/authopenid/complete.html:62 templates/authopenid/signin.html:62
+#: templates/authopenid/complete.html:62 templates/authopenid/signin.html:140
msgid "Forgot your password?"
msgstr ""
-#: templates/authopenid/delete.html:8
+#: templates/authopenid/confirm_email.txt:2
+msgid "Thank you for registering at our Q&A forum!"
+msgstr ""
+
+#: templates/authopenid/confirm_email.txt:4
+msgid "Your account details are:"
+msgstr ""
+
+#: templates/authopenid/confirm_email.txt:6
+#: templates/authopenid/sendpw_email.txt:7
+msgid "Username:"
+msgstr ""
+
+#: templates/authopenid/confirm_email.txt:7
+#: templates/authopenid/delete.html:20
+msgid "Password:"
+msgstr ""
+
+#: templates/authopenid/confirm_email.txt:9
+msgid "Please sign in here:"
+msgstr ""
+
+#: templates/authopenid/confirm_email.txt:12
+#: templates/authopenid/email_validation.txt:14
+#: templates/authopenid/sendpw_email.txt:13
+msgid ""
+"Sincerely,\n"
+"Forum Administrator"
+msgstr ""
+
+#: templates/authopenid/delete.html:9
msgid "Account: delete account"
msgstr ""
-#: templates/authopenid/delete.html:12
+#: templates/authopenid/delete.html:13
msgid ""
"Note: After deleting your account, anyone will be able to register this "
"username."
msgstr ""
-#: templates/authopenid/delete.html:16
+#: templates/authopenid/delete.html:17
msgid "Check confirm box, if you want delete your account."
msgstr ""
-#: templates/authopenid/delete.html:19
-msgid "Password:"
-msgstr ""
-
-#: templates/authopenid/delete.html:31
+#: templates/authopenid/delete.html:32
msgid "I am sure I want to delete my account."
msgstr ""
-#: templates/authopenid/delete.html:32
+#: templates/authopenid/delete.html:33
msgid "Password/OpenID URL"
msgstr ""
-#: templates/authopenid/delete.html:32
+#: templates/authopenid/delete.html:33
msgid "(required for your security)"
msgstr ""
-#: templates/authopenid/delete.html:34
+#: templates/authopenid/delete.html:35
msgid "Delete account permanently"
msgstr ""
-#: templates/authopenid/sendpw.html:3 templates/authopenid/sendpw.html.py:7
+#: templates/authopenid/email_validation.txt:2
+msgid "Greetings from the Q&A forum"
+msgstr ""
+
+#: templates/authopenid/email_validation.txt:4
+msgid "To make use of the Forum, please follow the link below:"
+msgstr ""
+
+#: templates/authopenid/email_validation.txt:8
+msgid "Following the link above will help us verify your email address."
+msgstr ""
+
+#: templates/authopenid/email_validation.txt:10
+msgid ""
+"If you beleive that this message was sent in mistake - \n"
+"no further action is needed. Just ingore this email, we apologize\n"
+"for any inconvenience"
+msgstr ""
+
+#: templates/authopenid/sendpw.html:4 templates/authopenid/sendpw.html.py:8
msgid "Send new password"
msgstr ""
-#: templates/authopenid/sendpw.html:11
+#: templates/authopenid/sendpw.html:12
msgid "Lost your password? No problem - here you can reset it."
msgstr ""
-#: templates/authopenid/sendpw.html:12
+#: templates/authopenid/sendpw.html:13
msgid ""
"Please enter your username below and new password will be sent to your "
"registered e-mail"
msgstr ""
-#: templates/authopenid/sendpw.html:29
+#: templates/authopenid/sendpw.html:28
+msgid "User name"
+msgstr ""
+
+#: templates/authopenid/sendpw.html:30
msgid "Reset password"
msgstr ""
-#: templates/authopenid/sendpw.html:29
+#: templates/authopenid/sendpw.html:30
msgid "return to login"
msgstr ""
-#: templates/authopenid/sendpw.html:32
+#: templates/authopenid/sendpw.html:33
msgid ""
"Note: your new password will be activated only after you click the "
"activation link in the email message"
msgstr ""
-#: templates/authopenid/settings.html:29
-msgid "Give your account a new password."
+#: templates/authopenid/sendpw_email.txt:2
+#, python-format
+msgid ""
+"Someone has requested to reset your password on %(site_url)s.\n"
+"If it were not you, it is safe to ignore this email."
+msgstr ""
+
+#: templates/authopenid/sendpw_email.txt:5
+msgid "Your new account details are:"
+msgstr ""
+
+#: templates/authopenid/sendpw_email.txt:8
+msgid "New password:"
+msgstr ""
+
+#: templates/authopenid/sendpw_email.txt:10
+msgid "To confirm that you wanted to reset your password please visit:"
msgstr ""
#: templates/authopenid/settings.html:30
-msgid "Change email "
+msgid "Give your account a new password."
msgstr ""
#: templates/authopenid/settings.html:31
+msgid "Change email "
+msgstr ""
+
+#: templates/authopenid/settings.html:32
msgid "Add or update the email address associated with your account."
msgstr ""
-#: templates/authopenid/settings.html:34
+#: templates/authopenid/settings.html:35
msgid "Change openid associated to your account"
msgstr ""
-#: templates/authopenid/settings.html:37
+#: templates/authopenid/settings.html:38
msgid "Delete account"
msgstr ""
-#: templates/authopenid/settings.html:38
+#: templates/authopenid/settings.html:39
msgid "Erase your username and all your data from website"
msgstr ""
-#: templates/authopenid/signin.html:3 templates/authopenid/signin.html:16
+#: templates/authopenid/signin.html:4 templates/authopenid/signin.html:21
msgid "User login"
+msgstr "User login"
+
+#: templates/authopenid/signin.html:28
+#, python-format
+msgid ""
+"\n"
+" Your answer to %(title)s %(summary)s will be posted once you "
+"log in\n"
+" "
msgstr ""
+"\n"
+"<span class=\"strong big\">Your answer to </span> <i>\"<strong>%(title)s</"
+"strong> %(summary)s...\"</i> <span class=\"strong big\">is saved and will be "
+"posted once you log in.</span>"
-#: templates/authopenid/signin.html:21
-msgid "we support two login modes"
+#: templates/authopenid/signin.html:35
+#, python-format
+msgid ""
+"Your question \n"
+" %(title)s %(summary)s will be posted once you log in\n"
+" "
msgstr ""
+"<span class=\"strong big\">Your question</span> <i>\"<strong>%(title)s</"
+"strong> %(summary)s...\"</i> <span class=\"strong big\">is saved and will be "
+"posted once you log in.</span>"
-#: templates/authopenid/signin.html:26 templates/authopenid/signup.html:49
-msgid "Login with your OpenID"
+#: templates/authopenid/signin.html:40
+msgid "Click to sign in through any of these services."
msgstr ""
+"<p><span class=\"big strong\">We accept <a href=\"http://openid.net"
+"\">OpenID</a> login.</span> <font color=\"gray\">Instead of creating a new "
+"password - log in through any of the services below.<p><span class=\"big "
+"strong\">1. Select the login service by clicking one of the icons</span></p>"
-#: templates/authopenid/signin.html:28
-msgid "select openid provider"
+#: templates/authopenid/signin.html:103
+msgid "Enter your <span id=\"enter_your_what\">Provider user name</span>"
+msgstr ""
+"<span class=\"big strong\">2. Enter your <span id=\"enter_your_what"
+"\">Provider user name</span></span><br>(or select another OpenID provider "
+"above)"
+
+#: templates/authopenid/signin.html:110
+msgid ""
+"Enter your <a class=\"openid_logo\" href=\"http://openid.net\">OpenID</a> "
+"web address"
+msgstr ""
+"<span class=\"big strong\">2. Enter your <a class=\"openid_logo\" href="
+"\"http://openid.net\">OpenID</a> web address</span><br>(or choose specific "
+"provider by clicking on one of the icons above)"
+
+#: templates/authopenid/signin.html:112 templates/authopenid/signin.html:139
+msgid "Login"
msgstr ""
-#: templates/authopenid/signin.html:32
-msgid "verify openid link and login"
+#: templates/authopenid/signin.html:116
+msgid "we support two login modes"
msgstr ""
+"You can log in either through one of these services or traditionally - using "
+"local username/password."
-#: templates/authopenid/signin.html:58
+#: templates/authopenid/signin.html:136
msgid "Use login name and password"
msgstr ""
-#: templates/authopenid/signin.html:59
+#: templates/authopenid/signin.html:137
msgid "Login name"
msgstr ""
-#: templates/authopenid/signin.html:63
-msgid "Create new acccount"
+#: templates/authopenid/signin.html:141
+msgid "Create new account"
msgstr ""
-#: templates/authopenid/signin.html:72
+#: templates/authopenid/signin.html:150
msgid "Why use OpenID?"
msgstr ""
-#: templates/authopenid/signin.html:76
+#: templates/authopenid/signin.html:154
msgid "with openid it is easier"
-msgstr ""
+msgstr "With the OpenID you don't need to create new username and password."
-#: templates/authopenid/signin.html:79
+#: templates/authopenid/signin.html:157
msgid "reuse openid"
-msgstr ""
+msgstr "You can safely re-use the same login for all OpenID-enabled websites."
-#: templates/authopenid/signin.html:82
+#: templates/authopenid/signin.html:160
msgid "openid is widely adopted"
msgstr ""
+"There are > 160,000,000 OpenID account in use. Over 10,000 sites are OpenID-"
+"enabled."
-#: templates/authopenid/signin.html:85
+#: templates/authopenid/signin.html:163
msgid "openid is supported open standard"
-msgstr ""
+msgstr "OpenID is based on an open standard, supported by many organizations."
-#: templates/authopenid/signin.html:89
+#: templates/authopenid/signin.html:167
msgid "Find out more"
msgstr ""
-#: templates/authopenid/signin.html:90
+#: templates/authopenid/signin.html:168
msgid "Get OpenID"
msgstr ""
-#: templates/authopenid/signup.html:2 templates/authopenid/signup.html.py:6
+#: templates/authopenid/signup.html:4 templates/authopenid/signup.html.py:8
msgid "Signup"
msgstr ""
-#: templates/authopenid/signup.html:10
+#: templates/authopenid/signup.html:12
msgid ""
"We support two types of user registration: conventional username/password, "
"and"
msgstr ""
-#: templates/authopenid/signup.html:10
+#: templates/authopenid/signup.html:12
msgid "the OpenID method"
msgstr ""
-#: templates/authopenid/signup.html:15
+#: templates/authopenid/signup.html:17
msgid "Sorry, looks like we have some errors"
msgstr ""
-#: templates/authopenid/signup.html:33
+#: templates/authopenid/signup.html:35
msgid "Conventional registration"
msgstr ""
-#: templates/authopenid/signup.html:34
+#: templates/authopenid/signup.html:36
msgid "choose a user name"
msgstr ""
-#: templates/authopenid/signup.html:40
+#: templates/authopenid/signup.html:42
msgid "back to login"
msgstr ""
-#: templates/authopenid/signup.html:46
+#: templates/authopenid/signup.html:48
msgid "Register with your OpenID"
msgstr ""
+
+#: templates/authopenid/signup.html:51
+msgid "Login with your OpenID"
+msgstr ""
+
+#~ msgid "what is this website"
+#~ msgstr ""
+#~ "This is a collaboratively edited question and answer site for the "
+#~ "Magnetic Resonance specialists."
+
+#
+#~ msgid "editing tips"
+#~ msgstr "Tips"
+
+#~ msgid "Newest questions shown first."
+#~ msgstr ""
+#~ "Questions are sorted by <strong>entry date</strong>.Newest questions "
+#~ "shown first."
+
+#~ msgid ""
+#~ "please use space to separate tags (this enables autocomplete feature)"
+#~ msgstr "please use space to separate tags (uses autocomplete utility)"
+
+#~ msgid "select openid provider"
+#~ msgstr "1) Please select your id service provider."
+
+#~ msgid "verify openid link and login"
+#~ msgstr ""
+#~ "2) Please verify the OpenID URL (type your login name over {username}, if "
+#~ "present) and then log in."
diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po
index e9058ad2..8dfa7959 100644
--- a/locale/es/LC_MESSAGES/django.po
+++ b/locale/es/LC_MESSAGES/django.po
@@ -1,16 +1,10 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
+"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-08-07 14:09-0300\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"POT-Creation-Date: 2009-08-08 20:34-0300\n"
+"PO-Revision-Date: \n"
+"Last-Translator: Bruno Sarlo <bsarlo@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -20,15 +14,17 @@ msgstr ""
msgid "account/"
msgstr "cuenta/"
-#: settings.py:12 django_authopenid/urls.py:9 django_authopenid/urls.py:11
+#: settings.py:12 django_authopenid/urls.py:9 django_authopenid/urls.py:10
+#: django_authopenid/urls.py:11 django_authopenid/urls.py:13
+#: forum/views.py:304
msgid "signin/"
msgstr "ingresar/"
-#: django_authopenid/forms.py:67 django_authopenid/views.py:93
+#: django_authopenid/forms.py:67 django_authopenid/views.py:102
msgid "i-names are not supported"
msgstr "i-names no son soportados"
-#: django_authopenid/forms.py:102 django_authopenid/forms.py:207
+#: django_authopenid/forms.py:102
msgid ""
"Usernames can only contain letters, numbers and "
"underscores"
@@ -43,7 +39,7 @@ msgstr ""
"Este nombre de usuario no existe en nuestra base de datos. Por favor elija "
"otro."
-#: django_authopenid/forms.py:126 django_authopenid/forms.py:231
+#: django_authopenid/forms.py:126 django_authopenid/forms.py:233
msgid ""
"Please enter a valid username and password. Note that "
"both fields are case-sensitive."
@@ -51,11 +47,11 @@ msgstr ""
"Por favor ingrese un usuario y contraseña validos. Ambos campos son "
"sensibles a mayúsculas y minúsculas."
-#: django_authopenid/forms.py:130 django_authopenid/forms.py:235
+#: django_authopenid/forms.py:130 django_authopenid/forms.py:237
msgid "This account is inactive."
msgstr "Esta cuenta esta inactiva."
-#: django_authopenid/forms.py:158
+#: django_authopenid/forms.py:158 django_authopenid/forms.py:210
msgid "invalid user name"
msgstr "nombre de usuario no valido"
@@ -72,126 +68,135 @@ msgstr "nombre de usuario muy corto"
msgid "this name is already in use - please try anoter"
msgstr "este nombre ya está tomado - por favor intente con otro"
-#: django_authopenid/forms.py:184
+#: django_authopenid/forms.py:185
msgid ""
-"This email is already registered in our database. Please "
-"choose another."
+"This email is already registered in our database. "
+"Please choose another."
msgstr ""
"Este email ya está registrado en nuestra base de datos. Por favor, intente "
"con otro."
-#: django_authopenid/forms.py:214
+#: django_authopenid/forms.py:216
msgid ""
"This username don't exist. Please choose another."
msgstr "Este nombre de usuario no existe, por favor ingrese otro."
-#: django_authopenid/forms.py:253
+#: django_authopenid/forms.py:255
msgid "choose a username"
msgstr "elija un nombre de usuario"
-#: django_authopenid/forms.py:255 templates/authopenid/signup.html:36
+#: django_authopenid/forms.py:257 templates/authopenid/signup.html:38
msgid "your email address"
msgstr "su email (correo electrónico)"
-#: django_authopenid/forms.py:257 templates/authopenid/signup.html:37
+#: django_authopenid/forms.py:259 templates/authopenid/signup.html:39
msgid "choose password"
msgstr "elija una contraseña"
-#: django_authopenid/forms.py:259 templates/authopenid/signup.html:38
+#: django_authopenid/forms.py:261 templates/authopenid/signup.html:40
msgid "retype password"
msgstr "re-ingrese la contraseña"
-#: django_authopenid/forms.py:330
+#: django_authopenid/forms.py:335
msgid ""
"Old password is incorrect. Please enter the correct "
"password."
msgstr "La antigua contraseña es incorrecta. Por favor ingrese la correcta"
-#: django_authopenid/forms.py:342
+#: django_authopenid/forms.py:347
msgid "new passwords do not match"
msgstr "la nueva contraseña no coincide"
-#: django_authopenid/forms.py:434
+#: django_authopenid/forms.py:442
msgid "Incorrect username."
msgstr "Nombre de usuario incorrecto"
#: django_authopenid/urls.py:10
+#, fuzzy
+msgid "newquestion/"
+msgstr "preguntas/"
+
+#: django_authopenid/urls.py:11
+msgid "newanswer/"
+msgstr "respuesta-nueva/"
+
+#: django_authopenid/urls.py:12
msgid "signout/"
msgstr "salir/"
-#: django_authopenid/urls.py:11
+#: django_authopenid/urls.py:13
msgid "complete/"
msgstr "completado/"
-#: django_authopenid/urls.py:13
+#: django_authopenid/urls.py:15
msgid "register/"
msgstr "registrarse/"
-#: django_authopenid/urls.py:14
+#: django_authopenid/urls.py:16
msgid "signup/"
msgstr "registrarse/"
-#: django_authopenid/urls.py:16
+#: django_authopenid/urls.py:18
msgid "sendpw/"
msgstr "enviarcontrasena/"
-#: django_authopenid/urls.py:26
+#: django_authopenid/urls.py:29
msgid "delete/"
msgstr "borrar/"
-#: django_authopenid/views.py:99
+#: django_authopenid/views.py:108
#, python-format
-msgid "非法OpenID地址: %s"
+msgid "OpenID %(openid_url)s is invalid"
msgstr ""
-#: django_authopenid/views.py:366
+#: django_authopenid/views.py:417 django_authopenid/views.py:544
msgid "Welcome"
msgstr "Bienvenido"
-#: django_authopenid/views.py:456
+#: django_authopenid/views.py:507
msgid "Password changed."
msgstr "Contraseña modificada"
-#: django_authopenid/views.py:488
-msgid "Email changed."
-msgstr "Email modificado."
+#: django_authopenid/views.py:519 django_authopenid/views.py:524
+msgid "your email needs to be validated"
+msgstr "su correo electrónico necesita ser validado"
-#: django_authopenid/views.py:519 django_authopenid/views.py:671
+#: django_authopenid/views.py:681 django_authopenid/views.py:833
#, python-format
msgid "No OpenID %s found associated in our database"
-msgstr "La OpenID %s no esta asociada en nuestra base de datos"
+msgstr "El OpenID %s no esta asociada en nuestra base de datos"
-#: django_authopenid/views.py:523 django_authopenid/views.py:678
+#: django_authopenid/views.py:685 django_authopenid/views.py:840
#, python-format
msgid "The OpenID %s isn't associated to current user logged in"
-msgstr "La OpenID %s no esta asociada al usuario actualmente autenticado"
+msgstr "El OpenID %s no esta asociada al usuario actualmente autenticado"
-#: django_authopenid/views.py:531
+#: django_authopenid/views.py:693
msgid "Email Changed."
msgstr "Email modificado"
-#: django_authopenid/views.py:606
+#: django_authopenid/views.py:768
msgid "This OpenID is already associated with another account."
-msgstr "Esta OpenID ya está asociada a otra cuenta."
+msgstr "Este OpenID ya está asociada a otra cuenta."
-#: django_authopenid/views.py:611
+#: django_authopenid/views.py:773
#, python-format
msgid "OpenID %s is now associated with your account."
-msgstr "La OpenID %s está ahora asociada con tu cuenta."
+msgstr "El OpenID %s está ahora asociada con tu cuenta."
-#: django_authopenid/views.py:681
+#: django_authopenid/views.py:843
msgid "Account deleted."
msgstr "Cuenta borrada."
-#: django_authopenid/views.py:721
+#: django_authopenid/views.py:883
msgid "Request for new password"
msgstr "Pedir nueva contraseña"
-#: django_authopenid/views.py:734
+#: django_authopenid/views.py:896
msgid "A new password has been sent to your email address."
msgstr "Una nueva contraseña ha sido enviada a tu cuenta de Email."
-#: django_authopenid/views.py:764
+#: django_authopenid/views.py:926
#, python-format
msgid ""
"Could not change password. Confirmation key '%s' is not "
@@ -200,7 +205,7 @@ msgstr ""
"No se ha podido modificar la contraseña. La clave de confirmación '%s' no "
"está registrada"
-#: django_authopenid/views.py:773
+#: django_authopenid/views.py:935
msgid ""
"Can not change password. User don't exist anymore in our "
"database."
@@ -208,7 +213,7 @@ msgstr ""
"No se puede cambiar la contraseña. El usuario no existe más en nuestra base "
"de datos."
-#: django_authopenid/views.py:782
+#: django_authopenid/views.py:944
#, python-format
msgid "Password changed for %s. You may now sign in."
msgstr "Contraseña cambiada por %s. Ahora puedes ingresar."
@@ -329,43 +334,31 @@ msgstr "versión inicial"
msgid "retagged"
msgstr "re-etiquetada"
-#: forum/feed.py:17
-msgid "site title"
-msgstr "titulo del sitio"
-
-#: forum/feed.py:17
+#: forum/feed.py:18
msgid " - "
msgstr " - "
-#: forum/feed.py:17
-msgid "site slogan"
-msgstr " slogan del sitio"
-
-#: forum/feed.py:17
+#: forum/feed.py:18
msgid "latest questions"
msgstr "últimas preguntas"
-#: forum/feed.py:20
-msgid "meta site content"
-msgstr "meta descripción"
-
-#: forum/feed.py:22
-msgid "copyright message"
-msgstr "mensaje de copyright"
+#: forum/feed.py:19
+msgid "questions/"
+msgstr "preguntas/"
#: forum/forms.py:14 templates/answer_edit_tips.html:34
-#: templates/answer_edit_tips.html.py:39 templates/question_edit_tips.html:31
+#: templates/answer_edit_tips.html.py:38 templates/question_edit_tips.html:31
#: templates/question_edit_tips.html:36
msgid "title"
-msgstr "titulo"
+msgstr "título"
#: forum/forms.py:15
msgid "please enter a descriptive title for your question"
-msgstr "ingrese un titulo descriptivo para su pregunta"
+msgstr "ingrese un título descriptivo para su pregunta"
#: forum/forms.py:20
msgid "title must be > 10 characters"
-msgstr "el titulo debe tener al menos 10 caracteres"
+msgstr "el título debe tener al menos 10 caracteres"
#: forum/forms.py:29
msgid "content"
@@ -375,29 +368,30 @@ msgstr "contenido"
msgid "question content must be > 10 characters"
msgstr "el contenido de la pregunta debe ser al menos de 10 caracteres"
-#: forum/forms.py:45 templates/header.html:30 templates/header.html.py:61
+#: forum/forms.py:45 templates/header.html:34 templates/header.html.py:62
msgid "tags"
msgstr "etiquetas"
-#: forum/forms.py:46
-msgid "please use space to separate tags (this enables autocomplete feature)"
+#: forum/forms.py:47
+msgid ""
+"Tags are short keywords, with no spaces within. Up to five tags can be used."
msgstr ""
"por favor utilice espacio para separar las etiquetas (esto habilitael auto-"
"completado)"
-#: forum/forms.py:53
+#: forum/forms.py:54 templates/question_retag.html:38
msgid "tags are required"
msgstr "las etiquetas son requeridas"
-#: forum/forms.py:57
+#: forum/forms.py:58
msgid "please use 5 tags or less"
msgstr "por favor use 5 o menos etiquetas"
-#: forum/forms.py:60
+#: forum/forms.py:61
msgid "tags must be shorter than 20 characters"
msgstr "las etiquetas deben ser menores a 20 caracteres"
-#: forum/forms.py:64
+#: forum/forms.py:65
msgid ""
"please use following characters in tags: letters 'a-z', numbers, and "
"characters '.-_#'"
@@ -405,14 +399,15 @@ msgstr ""
"por favor use solo los siguientes caracteres en los nombres de etiquetas: "
"letras 'a-z', números y caracteres '.-_#'"
-#: forum/forms.py:74 templates/index.html:56 templates/question.html:196
-#: templates/question.html.py:377 templates/unanswered.html:48
+#: forum/forms.py:75 templates/index.html:57 templates/question.html:199
+#: templates/question.html.py:380 templates/questions.html:58
+#: templates/questions.html.py:70 templates/unanswered.html:48
#: templates/unanswered.html.py:60
#, fuzzy
msgid "community wiki"
msgstr "wiki de comunidad"
-#: forum/forms.py:75
+#: forum/forms.py:76
msgid ""
"if you choose community wiki option, the question and answer do not generate "
"points and name of author will not be shown"
@@ -420,11 +415,11 @@ msgstr ""
"si marca la opción 'wiki de comunidad', la pregunta y respuestas no generan "
"puntos y el nombre del autor no será mostrado"
-#: forum/forms.py:84
+#: forum/forms.py:89
msgid "update summary:"
msgstr "resumen de modificación"
-#: forum/forms.py:85
+#: forum/forms.py:90
msgid ""
"enter a brief summary of your revision (e.g. fixed spelling, grammar, "
"improved style, this field is optional)"
@@ -432,164 +427,196 @@ msgstr ""
"ingresa un breve resumen de tu revisión (ej. error ortográfico, gramática, "
"mejoras de estilo. Este campo es opcional."
-#: forum/forms.py:160
+#: forum/forms.py:175
msgid "this email does not have to be linked to gravatar"
msgstr "este email no tiene porque estar asociado a un Gravatar"
-#: forum/forms.py:161
+#: forum/forms.py:176
msgid "Real name"
msgstr "Nombre real"
-#: forum/forms.py:162
+#: forum/forms.py:177
msgid "Website"
msgstr "Sitio Web"
-#: forum/forms.py:163
+#: forum/forms.py:178
msgid "Location"
msgstr "Ubicación"
-#: forum/forms.py:164
+#: forum/forms.py:179
msgid "Date of birth"
msgstr "Fecha de nacimiento"
-#: forum/forms.py:164
+#: forum/forms.py:179
msgid "will not be shown, used to calculate age, format: YYYY-MM-DD"
msgstr "no será mostrado, usado para calcular la edad. Formato: YYY-MM-DD"
-#: forum/forms.py:165 templates/authopenid/settings.html:20
+#: forum/forms.py:180 templates/authopenid/settings.html:21
msgid "Profile"
msgstr "Perfil"
-#: forum/forms.py:190 forum/forms.py:191
+#: forum/forms.py:207 forum/forms.py:208
msgid "this email has already been registered, please use another one"
msgstr "este email ya ha sido registrado, por favor use otro"
-#: forum/models.py:316 templates/badges.html:50
+#: forum/models.py:243
+#, fuzzy, python-format
+msgid "%(author)s modified the question"
+msgstr "%(author) modificó la pregunta"
+
+#: forum/models.py:247
+#, python-format
+msgid "%(people)s posted %(new_answer_count)s new answers"
+msgstr "%(people)s publicaron %(new_answer_count)s nuevas respuestas"
+
+#: forum/models.py:252
+#, python-format
+msgid "%(people)s commented the question"
+msgstr "%(people)s comentarion la pregunta"
+
+#: forum/models.py:257
+#, python-format
+msgid "%(people)s commented answers"
+msgstr "%(people)s comentaron la respuesta"
+
+#: forum/models.py:259
+#, fuzzy, python-format
+msgid "%(people)s commented an answer"
+msgstr "%(people)s comentaron la respuesta"
+
+#: forum/models.py:438 templates/badges.html:52
msgid "gold"
msgstr "oro"
-#: forum/models.py:317 templates/badges.html:58
+#: forum/models.py:439 templates/badges.html:60
msgid "silver"
msgstr "plata"
-#: forum/models.py:318 templates/badges.html:65
+#: forum/models.py:440 templates/badges.html:67
msgid "bronze"
msgstr "bronze"
-#: forum/user.py:17 templates/user_tabs.html:7
-#, fuzzy
+#: forum/user.py:16 templates/user_tabs.html:7
msgid "overview"
msgstr "vista general"
-#: forum/user.py:18
+#: forum/user.py:17
msgid "user profile"
msgstr "perfil de usuario"
-#: forum/user.py:19
+#: forum/user.py:18
msgid "user profile overview"
msgstr "vista general del perfil de usuario"
-#: forum/user.py:25 templates/user_tabs.html:9
-#, fuzzy
+#: forum/user.py:24 templates/user_tabs.html:9
msgid "recent activity"
msgstr "actividades recientes"
-#: forum/user.py:26
+#: forum/user.py:25
msgid "recent user activity"
msgstr "actividades recientes del usuario"
-#: forum/user.py:27
+#: forum/user.py:26
msgid "profile - recent activity"
msgstr "perfil - actividades recientes"
-#: forum/user.py:34 templates/user_tabs.html:13
-#, fuzzy
+#: forum/user.py:33 templates/user_tabs.html:13
msgid "responses"
msgstr "respuestas"
-#: forum/user.py:35 templates/user_tabs.html:12
+#: forum/user.py:34 templates/user_tabs.html:12
msgid "comments and answers to others questions"
msgstr "comentarios y respuestas a preguntas de otros"
-#: forum/user.py:36
+#: forum/user.py:35
msgid "profile - responses"
msgstr "perfil - respuestas"
-#: forum/user.py:43 templates/user_info.html:23 templates/users.html:25
-#, fuzzy
+#: forum/user.py:42 templates/user_info.html:23 templates/users.html:26
msgid "reputation"
msgstr "reputación"
-#: forum/user.py:44
+#: forum/user.py:43
msgid "user reputation in the community"
msgstr "reputación del usuario en la comunidad"
-#: forum/user.py:45
+#: forum/user.py:44
msgid "profile - user reputation"
msgstr "perfil - reputación del usuario"
-#: forum/user.py:51
+#: forum/user.py:50
msgid "favorite questions"
msgstr "preguntas favoritas"
-#: forum/user.py:52
-#, fuzzy
+#: forum/user.py:51
msgid "users favorite questions"
msgstr "preguntas favoritas de los usuarios"
-#: forum/user.py:53
+#: forum/user.py:52
msgid "profile - favorite questions"
msgstr "perfil - preguntas favoritas"
-#: forum/user.py:60 templates/user_tabs.html:20
+#: forum/user.py:59 templates/user_tabs.html:20
msgid "casted votes"
msgstr "votos"
-#: forum/user.py:61 templates/user_tabs.html:20
+#: forum/user.py:60 templates/user_tabs.html:20
msgid "user vote record"
msgstr "historial de votación"
-#: forum/user.py:62
+#: forum/user.py:61
msgid "profile - votes"
msgstr "perfil - votos"
-#: forum/user.py:69
-#, fuzzy
+#: forum/user.py:68
msgid "preferences"
msgstr "preferencias"
-#: forum/user.py:70 templates/user_tabs.html:28
+#: forum/user.py:69 templates/user_tabs.html:27
msgid "user preference settings"
msgstr "preferencias del usuario"
-#: forum/user.py:71
+#: forum/user.py:70
msgid "profile - user preferences"
msgstr "perfil - preferencia de "
-#: forum/views.py:1726
+#: forum/views.py:304
+msgid "/account/"
+msgstr "/cuenta/"
+
+#: forum/views.py:943
+#, python-format
+msgid "subscription saved, %(email)s needs validation"
+msgstr "subscripción guardada, %(email)s necesita validación"
+
+#: forum/views.py:1853
msgid "uploading images is limited to users with >60 reputation points"
msgstr "para subir imagenes debes tener más de 60 puntos de reputación"
-#: forum/views.py:1728
+#: forum/views.py:1855
msgid "allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"
msgstr ""
"los tipos de archivos permitidos son 'jpg', 'jpeg', 'gif', 'bmp', 'png', "
"'tiff'"
-#: forum/views.py:1730
+#: forum/views.py:1857
#, python-format
msgid "maximum upload file size is %sK"
msgstr "tamaño máximo permitido es archivo %sK"
-#: forum/views.py:1732
+#: forum/views.py:1859
#, python-format
msgid ""
"Error uploading file. Please contact the site administrator. Thank you. %s"
msgstr ""
"Error al subir el archivo. Por favor, contacte al administrador. Gracias. %s"
+#: forum/management/commands/send_email_alerts.py:35
+msgid "updates from website"
+msgstr "actualizaciones del sitio"
+
#: forum/templatetags/extra_tags.py:139 forum/templatetags/extra_tags.py:168
-#: templates/header.html:33
+#: templates/header.html:37
msgid "badges"
msgstr "distinciones"
@@ -645,6 +672,10 @@ msgstr "ver todas las preguntas"
msgid "see all tags"
msgstr "ver todas las tags"
+#: templates/500.html:22
+msgid "sorry, system error"
+msgstr "lo sentimos, ha habido un error del sistema"
+
#: templates/500.html:24
msgid "system error log is recorded, error will be fixed as soon as possible"
msgstr ""
@@ -662,24 +693,28 @@ msgstr "ver ultimas preguntas"
msgid "see tags"
msgstr "ver tags"
+#: templates/about.html:6 templates/about.html.py:11
+msgid "About"
+msgstr "Acerca de"
+
#: templates/answer_edit.html:4 templates/answer_edit.html.py:47
msgid "Edit answer"
msgstr "Editar respuesta"
#: templates/answer_edit.html:24 templates/answer_edit.html.py:27
-#: templates/ask.html:25 templates/ask.html.py:28 templates/question.html:37
-#: templates/question.html.py:40 templates/question_edit.html:27
+#: templates/ask.html:25 templates/ask.html.py:28 templates/question.html:40
+#: templates/question.html.py:43 templates/question_edit.html:27
msgid "hide preview"
msgstr "ocultar previsualización"
#: templates/answer_edit.html:27 templates/ask.html:28
-#: templates/question.html:40 templates/question_edit.html:27
+#: templates/question.html:43 templates/question_edit.html:27
msgid "show preview"
msgstr "ver previsualización"
#: templates/answer_edit.html:47 templates/question_edit.html:65
-#: templates/revisions_answer.html:36 templates/revisions_question.html:36
-#, fuzzy
+#: templates/question_retag.html:52 templates/revisions_answer.html:36
+#: templates/revisions_question.html:36
msgid "back"
msgstr "volver"
@@ -692,23 +727,25 @@ msgstr "revisión"
msgid "select revision"
msgstr "seleccionar revisión"
-#: templates/answer_edit.html:62 templates/ask.html:81
-#: templates/question.html:447 templates/question_edit.html:91
+#: templates/answer_edit.html:62 templates/ask.html:94
+#: templates/question.html:452 templates/question_edit.html:91
msgid "Toggle the real time Markdown editor preview"
msgstr "Activar la visualización en tiempo real de Markdown"
-#: templates/answer_edit.html:62 templates/ask.html:81
-#: templates/question.html:447 templates/question_edit.html:91
+#: templates/answer_edit.html:62 templates/ask.html:94
+#: templates/question.html:452 templates/question_edit.html:91
msgid "toggle preview"
msgstr "Activar previsualización"
#: templates/answer_edit.html:73 templates/question_edit.html:119
+#: templates/question_retag.html:75
msgid "Save edit"
msgstr "Guardar la edición"
#: templates/answer_edit.html:74 templates/close.html:29
-#: templates/question_edit.html:120 templates/reopen.html:30
-#: templates/user_edit.html:83
+#: templates/question_edit.html:120 templates/question_retag.html:76
+#: templates/reopen.html:30 templates/user_edit.html:83
+#: templates/authopenid/changeemail.html:34
msgid "Cancel"
msgstr "Cancelar"
@@ -753,24 +790,24 @@ msgstr "**negrita** o __negrita__"
msgid "link"
msgstr "enlace"
-#: templates/answer_edit_tips.html:34 templates/answer_edit_tips.html.py:39
+#: templates/answer_edit_tips.html:34 templates/answer_edit_tips.html.py:38
#: templates/question_edit_tips.html:31 templates/question_edit_tips.html:36
msgid "text"
msgstr "texto"
-#: templates/answer_edit_tips.html:39 templates/question_edit_tips.html:36
+#: templates/answer_edit_tips.html:38 templates/question_edit_tips.html:36
msgid "image"
msgstr "imagen"
-#: templates/answer_edit_tips.html:43 templates/question_edit_tips.html:40
+#: templates/answer_edit_tips.html:42 templates/question_edit_tips.html:40
msgid "numbered list:"
msgstr "lista numerada"
-#: templates/answer_edit_tips.html:48 templates/question_edit_tips.html:45
+#: templates/answer_edit_tips.html:47 templates/question_edit_tips.html:45
msgid "basic HTML tags are also supported"
msgstr "etiquetas básicas de HTML permitidas"
-#: templates/answer_edit_tips.html:51 templates/question_edit_tips.html:48
+#: templates/answer_edit_tips.html:50 templates/question_edit_tips.html:48
msgid "learn more about Markdown"
msgstr "aprender mas sobre Markdown"
@@ -778,32 +815,24 @@ msgstr "aprender mas sobre Markdown"
msgid "Ask a question"
msgstr "Hacer una pregunta"
-#: templates/ask.html:106
-msgid "Use"
-msgstr "Usar"
-
-#: templates/ask.html:106
-msgid "learn more about OpenID"
-msgstr "aprender mas sobre OpenID"
-
-#: templates/ask.html:106 templates/authopenid/signin.html:35
-#: templates/authopenid/signin.html:61
-msgid "Login"
-msgstr "Ingresar"
+#: templates/ask.html:67
+msgid "login to post question info"
+msgstr "Inicie sesión para publicar pregunta."
-#: templates/ask.html:109
-msgid "Get your own "
-msgstr "Obtiene tu propio "
+#: templates/ask.html:73
+#, python-format
+msgid "must have valid %(email)s to post"
+msgstr "debe de tener un %(email)s válido para publicar"
-#: templates/ask.html:117 templates/authopenid/sendpw.html:27
-msgid "User name"
-msgstr "Nombre de usuario"
+#: templates/ask.html:108
+msgid "(required)"
+msgstr "(requerido)"
-#: templates/ask.html:120
-msgid "Email: (won't be shown to anyone)"
-msgstr "Email: (no será mostrado a nadie)"
+#: templates/ask.html:115
+msgid "Login/signup to post your question"
+msgstr "Iniciar sesión/registrarse para publicar su pregunta"
-#: templates/ask.html:127
+#: templates/ask.html:117
msgid "Ask your question"
msgstr "Haz tu pregunta"
@@ -815,71 +844,54 @@ msgstr "Distinción"
msgid "The users have been awarded with badges:"
msgstr "Usuarios han sido galardonados con distinciones:"
-#: templates/badges.html:5
+#: templates/badges.html:6
msgid "Badges summary"
msgstr "Resumen de distinciones"
-#: templates/badges.html:16 templates/user_stats.html:113
+#: templates/badges.html:17 templates/user_stats.html:113
msgid "Badges"
msgstr "Distinciones"
-#: templates/badges.html:20
+#: templates/badges.html:21
+msgid "Community gives you awards for your questions, answers and votes."
+msgstr "La comunidad te da distinciones por tus preguntas, respuestas y votos."
+
+#: templates/badges.html:22
msgid ""
-"Community gives you awards for your questions, answers and votes. Below is "
-"the list of available badges and number of times each type of badge has been "
-"awarded."
+"Below is the list of available badges and number of times each type of badge "
+"has been awarded."
msgstr ""
-"La comunidad te condecora con distinciones por tus preguntas, respuestas y "
-"votos. Debajo esta la lista de las distinciones disponibles y la cantidad de "
-"veces que han sido asignadas."
+"Debajo esta la lista de las distinciones disponibles y la cantidad de veces "
+"que han sido asignadas."
-#: templates/badges.html:47
+#: templates/badges.html:49
msgid "Community badges"
msgstr "Distinciones de la comunidad"
-#: templates/badges.html:53
-msgid ""
-"Gold badge is very rare. To obtain it you have to show profound knowledge "
-"and ability in addition to actively participating in the community. Gold "
-"badge is the highest award in this community."
+#: templates/badges.html:55
+msgid "gold badge description"
msgstr ""
"Las distinciones de Oro son excepcionales. Para obtenerla debes demostrar un "
"profundo conocimiento y habilidad además de participar activamente en la "
"comunidad. La distinción de Oro es la condecoración máxima en esta comunidad"
-#: templates/badges.html:61
-msgid ""
-"Obtaining silver badge requires significant patience. If you got one, you've "
-"very significantly contributed to this community"
+#: templates/badges.html:63
+msgid "silver badge description"
msgstr ""
"Obtener una distinción de Plata requiere de paciencia. Si has logrado una, "
"quiere decir que haz significativamente aportado a esta comunidad."
-#: templates/badges.html:64
+#: templates/badges.html:66
msgid "bronze badge: often given as a special honor"
msgstr ""
"distinción de bronce: con frecuencia entregada como reconocimiento especial."
-#: templates/badges.html:68
-msgid ""
-"If you are active in this community, you will get this medal - still it is a "
-"special honor."
+#: templates/badges.html:70
+msgid "bronze badge description"
msgstr ""
"Si eres un usuario activo de esta comunidad, recibirás esta distinción - de "
"todas maneras es un honor especial."
-#: templates/base.html:61 templates/base_content.html:60
-msgid "congratulations, community gave you a badge"
-msgstr "felicitaciones, la comunidad te ha otorgado una distinción"
-
-#: templates/base.html:63 templates/base_content.html:62
-msgid "profile"
-msgstr "perfil"
-
-#: templates/base_content.html:61
-msgid "see"
-msgstr "ver"
-
#: templates/book.html:7
msgid "reading channel"
msgstr "canal de lectura"
@@ -946,7 +958,7 @@ msgstr "esta pregunta ha sido seleccionada como favorita"
msgid "number of times"
msgstr "numero de veces"
-#: templates/book.html:105 templates/index.html:47
+#: templates/book.html:105 templates/index.html:48 templates/questions.html:46
#: templates/unanswered.html:37 templates/users_questions.html:30
msgid "votes"
msgstr "votos"
@@ -955,13 +967,14 @@ msgstr "votos"
msgid "the answer has been accepted to be correct"
msgstr "la respuesta ha sido aceptada como correcta"
-#: templates/book.html:115 templates/index.html:48
+#: templates/book.html:115 templates/index.html:49 templates/questions.html:47
#: templates/unanswered.html:38 templates/users_questions.html:40
msgid "views"
msgstr "vistas"
-#: templates/book.html:125 templates/index.html:68 templates/question.html:112
-#: templates/question.html.py:479 templates/tags.html:46
+#: templates/book.html:125 templates/index.html:69 templates/question.html:115
+#: templates/question.html.py:486 templates/questions.html:84
+#: templates/questions.html.py:156 templates/tags.html:47
#: templates/unanswered.html:75 templates/unanswered.html.py:109
#: templates/users_questions.html:52
msgid "using tags"
@@ -971,7 +984,7 @@ msgstr "usando etiquetas"
msgid "subscribe to book RSS feed"
msgstr "suscribirse al RSS del libro"
-#: templates/book.html:147 templates/index.html:115
+#: templates/book.html:147 templates/index.html:116
msgid "subscribe to the questions feed"
msgstr "suscribirse al agregado de noticias"
@@ -991,185 +1004,377 @@ msgstr "Razón"
msgid "OK to close"
msgstr "OK para cerrar"
-#: templates/footer.html:5
-#, fuzzy
-msgid "About us"
-msgstr "Acerca de nosotros"
+#: templates/faq.html:11
+msgid "Frequently Asked Questions "
+msgstr "Preguntas Frecuentes"
+
+#: templates/faq.html:16
+msgid "What kinds of questions can I ask here?"
+msgstr "¿Qué clase de preguntas puedo hacer aquí?"
+
+#: templates/faq.html:17
+msgid ""
+"Most importanly - questions should be <strong>relevant</strong> to this "
+"community."
+msgstr ""
+"Por encima de todo - las preguntas deben ser <strong>relevantes</strong>a "
+"esta comunidad."
+
+#: templates/faq.html:18
+msgid ""
+"Before asking the question - please make sure to use search to see whether "
+"your question has alredy been answered."
+msgstr ""
+"Antes de hacer tu pregunta - por favor usa el buscador para asegurarte que "
+"la pregunta no este ya hecha."
+
+#: templates/faq.html:21
+msgid "What questions should I avoid asking?"
+msgstr "¿Qué preguntas debería evitar preguntar?"
+
+#: templates/faq.html:22
+msgid ""
+"Please avoid asking questions that are not relevant to this community, too "
+"subjective and argumentative."
+msgstr ""
+"Evita hacer preguntas que no son relevantes a la comunidad, demasiado "
+"subjetivas o argumentativas."
-#: templates/footer.html:6 templates/header.html:13 templates/index.html:83
+#: templates/faq.html:27
+msgid "What should I avoid in my answers?"
+msgstr "¿Que debo evitar en mis respuestas?"
+
+#: templates/faq.html:28 templates/faq.html.py:132
+msgid "site title"
+msgstr "título del sitio"
+
+#: templates/faq.html:28
+msgid ""
+"is a Q&A site, not a discussion group. Therefore - please avoid having "
+"discussions in your answers, comment facility allows some space for brief "
+"discussions."
+msgstr ""
+"es un sitio de Preguntas y Respuestas, no un grupo de discusión. Por ende, "
+"intenta evitar discusiones en tus respuestas. Los comentarios permiten "
+"realizar pequeñas discusiones."
+
+#: templates/faq.html:32
+msgid "Who moderates this community?"
+msgstr "¿Quién modera esta comunidad?"
+
+#: templates/faq.html:33
+msgid "The short answer is: <strong>you</strong>."
+msgstr "La respuesta corta es: <strong>tú</strong>"
+
+#: templates/faq.html:34
+msgid "This website is moderated by the users."
+msgstr "Este sitio es moderado por los usuarios."
+
+#: templates/faq.html:35
+msgid ""
+"The reputation system allows users earn the authorization to perform a "
+"variety of moderation tasks."
+msgstr ""
+"El sistema de reputación permite a los usuarios adquirir autorización para "
+"realizar diversas tareas de moderación."
+
+#: templates/faq.html:40
+msgid "How does reputation system work?"
+msgstr "¿Cómo funciona el sistema de reputación?"
+
+#: templates/faq.html:41
+msgid "Rep system summary"
+msgstr "resumen del sistema de reputación"
+
+#: templates/faq.html:59 templates/user_votes.html:14
+msgid "upvote"
+msgstr "votar positivo"
+
+#: templates/faq.html:63
+msgid "use tags"
+msgstr "etiquetas usadas"
+
+#: templates/faq.html:68
+msgid "add comments"
+msgstr "agregar comentarios"
+
+#: templates/faq.html:72 templates/user_votes.html:16
+msgid "downvote"
+msgstr "votar negativo"
+
+#: templates/faq.html:75
+msgid "open and close own questions"
+msgstr "abrir y cerrar sus propias preguntas"
+
+#: templates/faq.html:79
+msgid "retag questions"
+msgstr "re-etiquetar preguntas"
+
+#: templates/faq.html:83
+msgid "edit community wiki questions"
+msgstr "editar preguntas de la wiki comunitaria"
+
+#: templates/faq.html:87
+msgid "edit any answer"
+msgstr "editar cualquier pregunta"
+
+#: templates/faq.html:91
+msgid "open any closed question"
+msgstr "abrir cualquier pregunta cerrada"
+
+#: templates/faq.html:95
+msgid "delete any comment"
+msgstr "borrar cualquier comentario"
+
+#: templates/faq.html:99
+msgid "delete any questions and answers and perform other moderation tasks"
+msgstr ""
+"borrar cualquier pregunta o respuesta y realizar otras tareas de "
+"administración."
+
+#: templates/faq.html:106
+msgid "how to validate email title"
+msgstr "como validar el título del correo"
+
+#: templates/faq.html:108
+msgid "how to validate email info"
+msgstr "como validar la información del correo"
+
+#: templates/faq.html:112
+msgid "what is gravatar"
+msgstr "qué es gravatar"
+
+#: templates/faq.html:113
+msgid "gravatar faq info"
+msgstr "preguntas frecuentes sobre gravatar"
+
+#: templates/faq.html:116
+msgid "To register, do I need to create new password?"
+msgstr "¿Para registrarme, debo crearme una cuenta?"
+
+#: templates/faq.html:117
+msgid ""
+"No, you don't have to. You can login through any service that supports "
+"OpenID, e.g. Google, Yahoo, AOL, etc."
+msgstr ""
+"No tienes porqué. Puedes ingresar usando cualquiera de los servicios que "
+"soportan OpenID, ej. Google, Yahoo, AOL, MyOpenID, etc."
+
+#: templates/faq.html:118
+msgid "Login now!"
+msgstr "Ingresa ahora!"
+
+#: templates/faq.html:123
+msgid "Why other people can edit my questions/answers?"
+msgstr "¿Porqué otras personas pueden editar mis preguntas y respuestas?"
+
+#: templates/faq.html:124
+msgid "Goal of this site is..."
+msgstr "El objetivo de este sitio es..."
+
+#: templates/faq.html:124
+msgid ""
+"So questions and answers can be edited like wiki pages by experienced users "
+"of this site and this improves the overall quality of the knowledge base "
+"content."
+msgstr ""
+"Entonces, las preguntas y respuestas pueden ser editadas como wiki por "
+"usuarios con experiencia, y esto mejora la calidad general del conocimiento "
+"guardado."
+
+#: templates/faq.html:125
+msgid "If this approach is not for you, we respect your choice."
+msgstr ""
+"Si esta forma de funcionamiento no es de tu agrado, respetamos tu elección."
+
+#: templates/faq.html:129
+msgid "Still have questions?"
+msgstr "¿Aún tienes preguntas?"
+
+#: templates/faq.html:130
+msgid "Please ask your question, help make our community better!"
+msgstr "Por favor haz tu pregunta, ¡ayudanos a mejorar nuestra comunidad!"
+
+#: templates/faq.html:132 templates/header.html:33 templates/header.html.py:61
+msgid "questions"
+msgstr "preguntas"
+
+#: templates/faq.html:132 templates/index.html:121
+msgid "."
+msgstr "."
+
+#: templates/footer.html:7 templates/header.html:14 templates/index.html:83
+msgid "about"
+msgstr "acerca de nosotros"
+
+#: templates/footer.html:8 templates/header.html:15 templates/index.html:84
msgid "faq"
msgstr "preguntas frecuentes"
-#: templates/footer.html:8
-#, fuzzy
-msgid "Contact"
-msgstr "Contactenos"
-
#: templates/footer.html:9
-#, fuzzy
-msgid "Privacy"
-msgstr "Privacidad"
+msgid "blog"
+msgstr "blog"
#: templates/footer.html:10
-#, fuzzy
-msgid "Feedback"
-msgstr "Contacto"
+msgid "contact us"
+msgstr "contactenos"
+
+#: templates/footer.html:11
+msgid "privacy policy"
+msgstr "código de privacidad"
-#: templates/header.html:8
+#: templates/footer.html:12
+msgid "give feedback"
+msgstr "envía comentarios"
+
+#: templates/footer.html:18
+msgid "current revision"
+msgstr "revisión actual"
+
+#: templates/header.html:10
msgid "logout"
msgstr "salir"
-#: templates/header.html:10 templates/authopenid/signup.html:39
+#: templates/header.html:12 templates/authopenid/signup.html:41
msgid "login"
msgstr "entrar"
-#: templates/header.html:12 templates/index.html:82
-msgid "about"
-msgstr "acerca de nosotros"
-
#: templates/header.html:23
-#, fuzzy
msgid "back to home page"
msgstr "volver página principal"
-#: templates/header.html:29 templates/header.html.py:60
-msgid "questions"
-msgstr "preguntas"
+#: templates/header.html:30
+msgid "ask a question"
+msgstr "hacer una pregunta"
-#: templates/header.html:31 templates/header.html.py:62
+#: templates/header.html:35 templates/header.html.py:63
msgid "users"
msgstr "usuarios"
-#: templates/header.html:32
+#: templates/header.html:36
#, fuzzy
msgid "books"
msgstr "libros"
-#: templates/header.html:34 templates/index.html:120
+#: templates/header.html:38
msgid "unanswered questions"
msgstr "sin respuesta"
-#: templates/header.html:38
+#: templates/header.html:42
msgid "my profile"
msgstr "mi perfil"
-#: templates/header.html:42
-msgid "ask a question"
-msgstr "hacer una pregunta"
-
-#: templates/header.html:57
+#: templates/header.html:58
#, fuzzy
msgid "search"
msgstr "buscar"
-#: templates/index.html:6
+#: templates/index.html:7
msgid "Home"
msgstr "Inicio"
-#: templates/index.html:21
+#: templates/index.html:22 templates/questions.html:7
msgid "Questions"
msgstr "Preguntas"
-#: templates/index.html:23
+#: templates/index.html:24
#, fuzzy
msgid "last updated questions"
msgstr "ultimas preguntas actualizadas"
-#: templates/index.html:23 templates/unanswered.html:20
-#, fuzzy
+#: templates/index.html:24 templates/questions.html:25
+#: templates/unanswered.html:20
msgid "newest"
msgstr "más nuevas"
-#: templates/index.html:24
+#: templates/index.html:25 templates/questions.html:27
msgid "hottest questions"
msgstr "preguntas calientes"
-#: templates/index.html:24
-#, fuzzy
+#: templates/index.html:25 templates/questions.html:27
msgid "hottest"
msgstr "más calientes"
-#: templates/index.html:25
+#: templates/index.html:26 templates/questions.html:28
msgid "most voted questions"
msgstr "preguntas más votadas"
-#: templates/index.html:25
+#: templates/index.html:26 templates/questions.html:28
msgid "most voted"
msgstr "más votadas"
-#: templates/index.html:26
+#: templates/index.html:27
msgid "all questions"
msgstr "todas las preguntas"
-#: templates/index.html:46 templates/unanswered.html:36
-#: templates/users_questions.html:35
+#: templates/index.html:47 templates/questions.html:45
+#: templates/unanswered.html:36 templates/users_questions.html:35
msgid "answers"
msgstr "respuestas"
-#: templates/index.html:68 templates/question.html:112
-#: templates/question.html.py:479 templates/tags.html:46
+#: templates/index.html:69 templates/question.html:115
+#: templates/question.html.py:486 templates/questions.html:84
+#: templates/questions.html.py:156 templates/tags.html:47
#: templates/unanswered.html:75 templates/unanswered.html.py:109
#: templates/users_questions.html:52
#, fuzzy
msgid "see questions tagged"
msgstr "ver preguntas etiquetadas"
-#: templates/index.html:79
+#: templates/index.html:80
msgid "welcome to website"
msgstr "bienvenido a sitio"
-#: templates/index.html:88
+#: templates/index.html:89
msgid "Recent tags"
msgstr "Etiquetas recientes"
-#: templates/index.html:93
+#: templates/index.html:94
#, python-format
msgid "see questions tagged '%(tagname)s'"
msgstr "ver preguntas etiquetadas '%(tagname)s'"
-#: templates/index.html:96 templates/index.html.py:120
-#, fuzzy
+#: templates/index.html:97 templates/index.html.py:121
msgid "popular tags"
msgstr "etiquetas populares"
-#: templates/index.html:100
+#: templates/index.html:101
msgid "Recent awards"
msgstr "Reconocimientos recientes"
-#: templates/index.html:106
+#: templates/index.html:107
msgid "given to"
msgstr "dados a"
-#: templates/index.html:111
+#: templates/index.html:112
msgid "all awards"
msgstr "todos los reconocimientos"
-#: templates/index.html:115
+#: templates/index.html:116
msgid "subscribe to last 30 questions by RSS"
msgstr "suscribirse a las últimas 30 preguntas por RSS"
-#: templates/index.html:120
+#: templates/index.html:121
msgid "Still looking for more? See"
msgstr "¿Aún sigues buscando más? Ver"
-#: templates/index.html:120
-msgid "complete list of quesionts"
+#: templates/index.html:121
+msgid "complete list of questions"
msgstr "lista completa de preguntas"
-#: templates/index.html:120
+#: templates/index.html:121
#, fuzzy
msgid "or"
msgstr "ó"
-#: templates/index.html:120
-msgid "."
-msgstr "."
-
-#: templates/index.html:120
-#, fuzzy
+#: templates/index.html:121
msgid "Please help us answer"
msgstr "Ayudanos a responder"
+#: templates/index.html:121
+msgid "list of unanswered questions"
+msgstr "lista de preguntas sin respuesta"
+
#: templates/logout.html:6 templates/logout.html.py:17
msgid "Logout"
msgstr "Salir"
@@ -1186,15 +1391,29 @@ msgstr ""
msgid "Logout now"
msgstr "Salir ahora"
-#: templates/pagesize.html:5
-#, fuzzy
-msgid "Size per page:"
-msgstr "Entradas por página:"
+#: templates/pagesize.html:6
+msgid "posts per page"
+msgstr "entradas por página"
-#: templates/paginator.html:5
-#, fuzzy
-msgid "Previous"
-msgstr "Previa"
+#: templates/paginator.html:6 templates/paginator.html.py:7
+msgid "previous"
+msgstr "previo"
+
+#: templates/paginator.html:19
+msgid "current page"
+msgstr "página actúal"
+
+#: templates/paginator.html:22 templates/paginator.html.py:29
+msgid "page number "
+msgstr "número de página"
+
+#: templates/paginator.html:22 templates/paginator.html.py:29
+msgid "number - make blank in english"
+msgstr " "
+
+#: templates/paginator.html:33
+msgid "next page"
+msgstr "próxima página"
#: templates/privacy.html:6 templates/privacy.html.py:11
#, fuzzy
@@ -1208,7 +1427,7 @@ msgstr "mensaje de privacidad"
#: templates/privacy.html:18
#, fuzzy
msgid "Site Visitors"
-msgstr "Visitantes del sitio"
+msgstr "Visitantes del Sitio"
#: templates/privacy.html:20
msgid "what technical information is collected about visitors"
@@ -1242,75 +1461,75 @@ msgstr "Cambios de Códigos"
msgid "how privacy policies can be changed"
msgstr "como pueden ser cambiados los códigos de privacidad"
-#: templates/question.html:66 templates/question.html.py:78
+#: templates/question.html:69 templates/question.html.py:81
msgid "i like this post (click again to cancel)"
msgstr "Me gusta esta entrada (clickear devuelta para cancelar)"
-#: templates/question.html:68 templates/question.html.py:80
-#: templates/question.html:273
+#: templates/question.html:71 templates/question.html.py:83
+#: templates/question.html:276
msgid "current number of votes"
msgstr "número actual de votos"
-#: templates/question.html:73 templates/question.html.py:84
+#: templates/question.html:76 templates/question.html.py:87
msgid "i dont like this post (click again to cancel)"
msgstr "No me gusta esta entrada (clickear devuelta para cancelar)"
-#: templates/question.html:90
+#: templates/question.html:93
msgid "mark this question as favorite (click again to cancel)"
msgstr "marcar esta pregunta como favorita (clickear devuelta para cancelar)"
-#: templates/question.html:96
+#: templates/question.html:99
msgid "remove favorite mark from this question (click again to restore mark)"
msgstr ""
"remover marca de favorito a esta pregunta (clickear devuelta para volver a "
"marcar)"
-#: templates/question.html:121 templates/question.html.py:304
+#: templates/question.html:124 templates/question.html.py:307
#: templates/revisions_answer.html:53 templates/revisions_question.html:53
msgid "edit"
msgstr "editar"
-#: templates/question.html:125 templates/question.html.py:314
+#: templates/question.html:128 templates/question.html.py:317
msgid "delete"
msgstr "borrar"
-#: templates/question.html:130
+#: templates/question.html:133
msgid "reopen"
msgstr "re-abrir"
-#: templates/question.html:135
+#: templates/question.html:138
msgid "close"
msgstr "cerrar"
-#: templates/question.html:141 templates/question.html.py:327
+#: templates/question.html:144 templates/question.html.py:330
msgid ""
"report as offensive (i.e containing spam, advertising, malicious text, etc.)"
msgstr ""
"reportar como ofensivo (ej. contiene spam, publicidad, texto malicioso, etc.)"
-#: templates/question.html:142 templates/question.html.py:328
+#: templates/question.html:145 templates/question.html.py:331
msgid "flag offensive"
msgstr "marcar como ofensivo"
-#: templates/question.html:154 templates/question.html.py:337
+#: templates/question.html:157 templates/question.html.py:340
#: templates/revisions_answer.html:65 templates/revisions_question.html:65
msgid "updated"
msgstr "actualizado"
-#: templates/question.html:203 templates/question.html.py:384
+#: templates/question.html:206 templates/question.html.py:387
#: templates/revisions_answer.html:63 templates/revisions_question.html:63
msgid "asked"
msgstr "preguntado"
-#: templates/question.html:233 templates/question.html.py:411
+#: templates/question.html:236 templates/question.html.py:414
msgid "comments"
msgstr "comentarios"
-#: templates/question.html:234 templates/question.html.py:412
+#: templates/question.html:237 templates/question.html.py:415
msgid "add comment"
msgstr "agregar comentario"
-#: templates/question.html:247
+#: templates/question.html:250
#, python-format
msgid ""
"The question has been closed for the following reason \"%(question."
@@ -1319,108 +1538,117 @@ msgstr ""
"La pregunta ha sido cerrada por el siguiente motivo \"%(question."
"get_close_reason_display)s\" por"
-#: templates/question.html:249
+#: templates/question.html:252
#, python-format
msgid "close date %(question.closed_at)s"
msgstr "fecha de cerrada %(question.closed_at)s"
-#: templates/question.html:256 templates/questions.html:44
-#: templates/user_stats.html:28
+#: templates/question.html:259 templates/user_stats.html:28
msgid "Answers"
msgstr "Respuestas"
-#: templates/question.html:258
+#: templates/question.html:261
msgid "oldest answers will be shown first"
msgstr "la respuesta mas vieja será mostrada primero"
-#: templates/question.html:258
+#: templates/question.html:261
msgid "oldest answers"
msgstr "pregunta más vieja"
-#: templates/question.html:259
+#: templates/question.html:262
msgid "newest answers will be shown first"
msgstr "preguntas más nuevas serán mostradas primero"
-#: templates/question.html:259
+#: templates/question.html:262
msgid "newest answers"
msgstr "más nuevas"
-#: templates/question.html:260
+#: templates/question.html:263
msgid "most voted answers will be shown first"
msgstr "las preguntas más votadas serán mostradas primero"
-#: templates/question.html:260
+#: templates/question.html:263
msgid "popular answers"
msgstr "respuestas populares serán mostradas primero"
-#: templates/question.html:272
+#: templates/question.html:275
msgid "i like this answer (click again to cancel)"
msgstr "me gusta esta respuesta (clickear devuelta para cancelar)"
-#: templates/question.html:278
+#: templates/question.html:281
msgid "i dont like this answer (click again to cancel)"
msgstr "no me gusta esta respuesta (clickear devuelta para cancelar)"
-#: templates/question.html:284
+#: templates/question.html:287
msgid "mark this answer as favorite (click again to undo)"
msgstr "marcar esta respuesta como favorita (clickear devuelta para deshacer)"
-#: templates/question.html:289
+#: templates/question.html:292
msgid "the author of the question has selected this answer as correct"
msgstr "el autor de esta pregunta ha seleccionado esta respuesta como correcta"
-#: templates/question.html:311
+#: templates/question.html:314
msgid "undelete"
msgstr "deshacer eliminar"
-#: templates/question.html:321
+#: templates/question.html:324
msgid "answer permanent link"
msgstr "enlace permanente a respuesta"
-#: templates/question.html:322
+#: templates/question.html:325
msgid "permanent link"
msgstr "enlace permanente"
-#: templates/question.html:436
+#: templates/question.html:438
msgid "Your answer"
msgstr "Tu respuesta"
-#: templates/question.html:460
+#: templates/question.html:441
+msgid "you can answer anonymously and then login"
+msgstr "puedes responder de forma anónima y luego ingresar"
+
+#: templates/question.html:465
msgid "Answer the question"
msgstr "Responde la pregunta"
-#: templates/question.html:462
-msgid "Login to answer"
-msgstr "Ingresa para responder"
+#: templates/question.html:467
+msgid "Notify me daily if there are any new answers."
+msgstr "Notificarme diariamente si hay nuevas respuestas."
-#: templates/question.html:474
+#: templates/question.html:469
+msgid "once you sign in you will be able to subscribe for any updates here"
+msgstr ""
+"una vez que hayas ingresado podrás suscribirte a cualquiera de las "
+"actualizaciones aquí."
+
+#: templates/question.html:481
#, fuzzy
msgid "Question tags"
msgstr "Tags de la pregunta"
-#: templates/question.html:484
+#: templates/question.html:491
msgid "question asked"
msgstr "pregunta preguntada"
-#: templates/question.html:484 templates/question.html.py:490
+#: templates/question.html:491 templates/question.html.py:497
#: templates/user_info.html:51
+#, fuzzy
msgid "ago"
-msgstr "atrás"
+msgstr " atras"
-#: templates/question.html:487
+#: templates/question.html:494
msgid "question was seen"
msgstr "la pregunta fue vista"
-#: templates/question.html:487
+#: templates/question.html:494
msgid "times"
msgstr "veces"
-#: templates/question.html:490
+#: templates/question.html:497
msgid "last updated"
msgstr "última vez actualizada"
-#: templates/question.html:495
-#, fuzzy
+#: templates/question.html:502
msgid "Related questions"
msgstr "Preguntas relacionadas"
@@ -1434,90 +1662,138 @@ msgstr "sugerencias sobre pregunta"
#: templates/question_edit_tips.html:7
msgid "please ask a relevant question"
-msgstr "por favor hace preguntas relevantes"
+msgstr "por favor hacer preguntas relevantes"
#: templates/question_edit_tips.html:10
msgid "please try provide enough details"
msgstr "intente proveer suficientes detalles"
-#: templates/questions.html:6
-#, fuzzy
-msgid "Question list"
-msgstr "Lista de preguntas"
+#: templates/question_retag.html:3 templates/question_retag.html.py:52
+msgid "Change tags"
+msgstr "Cambiar etiquetas"
-#: templates/questions.html:22
-#, fuzzy
-msgid "Tagged questions"
-msgstr "Preguntas etiquetadas"
+#: templates/question_retag.html:39
+msgid "up to 5 tags, less than 20 characters each"
+msgstr "hasta 5 etiquetas, menos de 20 caracteres cada una"
-#: templates/questions.html:22
-msgid "Query result"
-msgstr "Resultado de la búsqueda"
+#: templates/question_retag.html:86
+msgid "Why use and modify tags?"
+msgstr "¿Porqué usar y modificar etiquetas?"
-#: templates/questions.html:22
-msgid "All questions"
-msgstr "Todas las preguntas"
+#: templates/question_retag.html:89
+msgid "tags help us keep Questions organized"
+msgstr ""
-#: templates/questions.html:24
-#, fuzzy
-msgid "New questions"
-msgstr "Preguntas nuevas"
+#: templates/question_retag.html:95
+msgid "tag editors receive special awards from the community"
+msgstr ""
+"los editores de etiquetas reciben distinciones especiales de la comunidad"
-#: templates/questions.html:24
-#, fuzzy
-msgid "Newest"
-msgstr "Más recientes"
+#: templates/questions.html:23
+msgid "Found by tags"
+msgstr "Encontradas por etiqueta"
-#: templates/questions.html:25
-#, fuzzy
-msgid "Newest updated questions"
-msgstr "Ultimas preguntas actualizadas"
+#: templates/questions.html:23
+msgid "Found by title"
+msgstr "Encontradas por título"
-#: templates/questions.html:25
-#, fuzzy
-msgid "Active"
-msgstr "Actividad"
+#: templates/questions.html:23
+msgid "All questions"
+msgstr "Todas las preguntas"
+
+#: templates/questions.html:25 templates/unanswered.html:20
+msgid "most recently asked questions"
+msgstr "preguntas hechas más recientemente"
#: templates/questions.html:26
-#, fuzzy
-msgid "Questions with most answers"
-msgstr "Preguntas con más respuestas"
+msgid "most recently updated questions"
+msgstr "preguntas actualizadas más recientemente"
#: templates/questions.html:26
-#, fuzzy
-msgid "Hottest"
-msgstr "Más calientes"
+msgid "active"
+msgstr "actividad"
-#: templates/questions.html:27
-msgid "Questions with most votes"
-msgstr "Preguntas con más votos"
+#: templates/questions.html:109
+#, python-format
+msgid ""
+"\n"
+"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
+"\t\t\t"
+msgid_plural ""
+"\n"
+"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
+"\t\t\t"
+msgstr[0] ""
+"\n"
+"\t\t\ttiene un total de %(q_num)s preguntas etiquetadas con %(tagname)s\n"
+"\t\t\t"
+msgstr[1] ""
+"\n"
+"\t\t\ttiene un total de %(q_num)s preguntas etiquetadas con %(tagname)s\n"
+"\t\t\t"
+
+#: templates/questions.html:116
+#, fuzzy, python-format
+msgid ""
+"\n"
+"\t\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
+"\t\t\t\t"
+msgid_plural ""
+"\n"
+"\t\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
+"\t\t\t\t"
+msgstr[0] ""
+"\n"
+"\t\t\thay un total de %(q_num)s preguntas que contienen %(searchtitle)s\n"
+"\t\t\t"
+msgstr[1] ""
+"\n"
+"\t\t\thay un total de %(q_num)s pregunta que contiene %(searchtitle)s\n"
+"\t\t\t"
+
+#: templates/questions.html:122
+#, fuzzy, python-format
+msgid ""
+"\n"
+"\t\t\t\thave total %(q_num)s questions\n"
+"\t\t\t\t"
+msgid_plural ""
+"\n"
+"\t\t\t\thave total %(q_num)s questions\n"
+"\t\t\t\t"
+msgstr[0] "ver preguntas etiquetadas '%(tagname)s'"
+msgstr[1] "ver pregunta etiquetada '%(tagname)s'"
+
+#: templates/questions.html:131
+msgid "latest questions info"
+msgstr "<strong>Más recientes</strong> preguntas son mostradas primero."
+
+#: templates/questions.html:135
+msgid "Questions are sorted by the <strong>time of last update</strong>."
+msgstr ""
+"Las preguntas estan ordenadas por <strong>fecha de último update</strong>."
-#: templates/questions.html:27
-#, fuzzy
-msgid "Best"
-msgstr "Mejor"
+#: templates/questions.html:136
+msgid "Most recently answered ones are shown first."
+msgstr "Las más recientemente respondidas son mostradas primero."
-#: templates/questions.html:45
-#, fuzzy
-msgid "Votes"
-msgstr "Votos"
+#: templates/questions.html:140
+msgid "Questions sorted by <strong>number of responses</strong>."
+msgstr "Preguntas ordenadas por <strong>número de respuestas</strong>."
-#: templates/questions.html:46
-#, fuzzy
-msgid "Visits"
-msgstr "Visitas"
+#: templates/questions.html:141
+msgid "Most answered questions are shown first."
+msgstr "Preguntas más respondidas aparecen primero."
-#: templates/questions.html:57 templates/questions.html.py:69
-#, fuzzy
-msgid "Community wiki"
-msgstr "Wiki de comunidad"
+#: templates/questions.html:145
+msgid "Questions are sorted by the <strong>number of votes</strong>."
+msgstr "Las preguntas son ordenadas por el <strong>número de votos</strong>."
-#: templates/questions.html:83
-#, fuzzy
-msgid "Browse questions with tag of "
-msgstr "Ver preguntas etiquetadas con "
+#: templates/questions.html:146
+msgid "Most voted questions are shown first."
+msgstr "Las preguntas más votadas son mostradas primero."
-#: templates/questions.html:125 templates/unanswered.html:105
+#: templates/questions.html:153 templates/unanswered.html:105
msgid "Related tags"
msgstr "Etiquetas relacionadas"
@@ -1535,7 +1811,7 @@ msgstr "La pregunta fue cerrada por el siguiente motivo "
#: templates/reopen.html:22
msgid "reason - leave blank in english"
-msgstr "razón -"
+msgstr "razón - "
#: templates/reopen.html:22
msgid "on "
@@ -1553,37 +1829,37 @@ msgstr "Re-abrir esta pregunta"
#: templates/revisions_question.html:8 templates/revisions_question.html:36
#, fuzzy
msgid "Revision history"
-msgstr "Historial de reputación"
+msgstr "Historial de revisiones"
-#: templates/tags.html:5 templates/tags.html.py:28
+#: templates/tags.html:6 templates/tags.html.py:29
msgid "Tag list"
msgstr "Lista de etiquetas"
-#: templates/tags.html:30
+#: templates/tags.html:31
msgid "sorted alphabetically"
msgstr "ordenar alfabéticamente"
-#: templates/tags.html:30
+#: templates/tags.html:31
msgid "by name"
msgstr "por nombre"
-#: templates/tags.html:31
+#: templates/tags.html:32
msgid "sorted by frequency of tag use"
msgstr "ordenar por frecuencia de uso de la etiqueta"
-#: templates/tags.html:31
+#: templates/tags.html:32
msgid "by popularity"
msgstr "por popularidad"
-#: templates/tags.html:37
+#: templates/tags.html:38
msgid "All tags matching query"
msgstr "Todas las etiquetas que coincidan con la busqueda"
-#: templates/tags.html:37
+#: templates/tags.html:38
msgid "all tags - make this empty in english"
msgstr "todas las tags"
-#: templates/tags.html:40
+#: templates/tags.html:41
msgid "Nothing found"
msgstr "Nada encontrado"
@@ -1591,10 +1867,6 @@ msgstr "Nada encontrado"
msgid "Unanswered questions"
msgstr "Preguntas sin respuesta"
-#: templates/unanswered.html:20
-msgid "most recently asked questions"
-msgstr "preguntas hechas más recientemente"
-
#: templates/unanswered.html:97
#, python-format
msgid "have %(num_q)s unanswered questions"
@@ -1664,14 +1936,38 @@ msgstr "edad"
msgid "age unit"
msgstr "unidad de edad"
-#: templates/user_info.html:75
+#: templates/user_info.html:76
msgid "todays unused votes"
msgstr "votos de hoy no usados"
-#: templates/user_info.html:76
+#: templates/user_info.html:77
msgid "votes left"
msgstr "votos restantes"
+#: templates/user_preferences.html:10
+msgid "Connect with Twitter"
+msgstr "Conectar con Twitter"
+
+#: templates/user_preferences.html:12
+msgid "Twitter account name:"
+msgstr "Nombre de usuario en Twitter:"
+
+#: templates/user_preferences.html:14
+msgid "Twitter password:"
+msgstr "Contraseña de Twitter:"
+
+#: templates/user_preferences.html:16
+msgid "Send my Questions to Twitter"
+msgstr "Enviar mis preguntas a Twitter"
+
+#: templates/user_preferences.html:17
+msgid "Send my Answers to Twitter"
+msgstr "Enviar mis respuestas a Twitter"
+
+#: templates/user_preferences.html:18
+msgid "Save"
+msgstr "Guardar"
+
#: templates/user_stats.html:15
msgid "User questions"
msgstr "Preguntas del usuario"
@@ -1730,41 +2026,32 @@ msgstr "historial de reputación"
msgid "favorites"
msgstr "favoritos"
-#: templates/user_tabs.html:29
+#: templates/user_tabs.html:28
msgid "settings"
msgstr "preferencias"
-#: templates/user_votes.html:14
-msgid "upvote"
-msgstr "votar positivo"
-
-#: templates/user_votes.html:16
-msgid "downvote"
-msgstr "votar negativo"
-
-#: templates/users.html:5 templates/users.html.py:23
+#: templates/users.html:6 templates/users.html.py:24
msgid "Users"
msgstr "Usuarios"
-#: templates/users.html:26
-#, fuzzy
+#: templates/users.html:27
msgid "recent"
msgstr "reciente"
-#: templates/users.html:27
+#: templates/users.html:28
msgid "oldest"
msgstr "más viejo"
-#: templates/users.html:28
+#: templates/users.html:29
msgid "by username"
msgstr "por nombre de usuario"
-#: templates/users.html:34
+#: templates/users.html:35
#, python-format
msgid "users matching query %(suser)s:"
msgstr "usuarios que coincidan con la busqueda %(suser)s:"
-#: templates/users.html:38
+#: templates/users.html:39
msgid "Nothing found."
msgstr "Nada encontrado."
@@ -1776,118 +2063,149 @@ msgstr "esta pregunta ha sido seleccionada como favorita"
msgid "this answer has been accepted to be correct"
msgstr "esta respuesta ha sido aceptada como correcta"
-#: templates/authopenid/changeemail.html:6
-msgid "Account: change email"
-msgstr "Cuenta: cambiar el email"
+#: templates/authopenid/changeemail.html:7
+#: templates/authopenid/changeemail.html:33
+msgid "Change email"
+msgstr "Cambiar dirección email"
+
+#: templates/authopenid/changeemail.html:10
+#, fuzzy, python-format
+msgid "change %(email)s info"
+msgstr "Cambiar información del correo electrónico "
-#: templates/authopenid/changeemail.html:9
-msgid ""
-"This is where you can change the email address associated with your account. "
-"Please keep this email address up to date so we can send you a password-"
-"reset email if you request one."
-msgstr ""
-"Aquí es donde puedes cambiar el email asociado a tu cuenta de usuario.Por "
-"favor manten esta dirección de correo al día de forma que podamos mandarte "
-"un cambio de contraseña si tu así lo requieres."
-
-#: templates/authopenid/changeemail.html:11
-#: templates/authopenid/changeopenid.html:13
-#: templates/authopenid/changepw.html:18 templates/authopenid/delete.html:14
-#: templates/authopenid/delete.html:24
+#: templates/authopenid/changeemail.html:13
+#: templates/authopenid/changeopenid.html:14
+#: templates/authopenid/changepw.html:19 templates/authopenid/delete.html:15
+#: templates/authopenid/delete.html:25
msgid "Please correct errors below:"
msgstr "Por favor corrija los errores debajo: "
-#: templates/authopenid/changeemail.html:28
-msgid "Email"
-msgstr "Email"
+#: templates/authopenid/changeemail.html:30
+msgid "Your new Email"
+msgstr "Tu nuevo Email"
-#: templates/authopenid/changeemail.html:29
-#: templates/authopenid/signin.html:60
+#: templates/authopenid/changeemail.html:31
+#: templates/authopenid/signin.html:138
msgid "Password"
msgstr "Contraseña"
-#: templates/authopenid/changeemail.html:31
-msgid "Change email"
+#: templates/authopenid/changeemail.html:42
+#, fuzzy
+msgid "Validate email"
msgstr "Cambiar dirección email"
-#: templates/authopenid/changeopenid.html:7
+#: templates/authopenid/changeemail.html:45
+#, python-format
+msgid "validate %(email)s info"
+msgstr "validar información de %(email)s "
+
+#: templates/authopenid/changeemail.html:50
+msgid "Email not changed"
+msgstr "Email no modificado."
+
+#: templates/authopenid/changeemail.html:53
+#, python-format
+msgid "old %(email)s kept"
+msgstr "se ha conservado el viejo email %(email)s "
+
+#: templates/authopenid/changeemail.html:58
+msgid "Email changed"
+msgstr "Email modificado."
+
+#: templates/authopenid/changeemail.html:61
+#, python-format
+msgid "your current %(email)s can be used for this"
+msgstr "tu email actual %(email)s puede ser usado para esto"
+
+#: templates/authopenid/changeemail.html:66
+msgid "Email verified"
+msgstr "Email verificado"
+
+#: templates/authopenid/changeemail.html:69
+msgid "thanks for verifying email"
+msgstr "gracias por verificar su correo"
+
+#: templates/authopenid/changeemail.html:74
+msgid "email key not sent"
+msgstr "llave de correo no enviada"
+
+#: templates/authopenid/changeemail.html:77
+#, python-format
+msgid "email key not sent %(email)s change email here %(change_link)s"
+msgstr "email no enviado %(email)s cambiar email aquí %(change_link)s"
+
+#: templates/authopenid/changeopenid.html:8
msgid "Account: change OpenID URL"
msgstr "Cuenta: cambiar la URL de OpenID"
-#: templates/authopenid/changeopenid.html:11
+#: templates/authopenid/changeopenid.html:12
msgid ""
"This is where you can change your OpenID URL. Make sure you remember it!"
msgstr "Aquí es donde puedes cambiar tu OpenID URL. Asegurate de recordarla!"
-#: templates/authopenid/changeopenid.html:28
+#: templates/authopenid/changeopenid.html:29
msgid "OpenID URL:"
msgstr "URL de OpenID:"
-#: templates/authopenid/changeopenid.html:29
+#: templates/authopenid/changeopenid.html:30
msgid "Change OpenID"
msgstr "Cambiar OpenID"
-#: templates/authopenid/changepw.html:13
+#: templates/authopenid/changepw.html:14
msgid "Account: change password"
msgstr "Cuenta: cambiar contraseña"
-#: templates/authopenid/changepw.html:16
+#: templates/authopenid/changepw.html:17
msgid "This is where you can change your password. Make sure you remember it!"
msgstr "Aquí es donde puedes cambiar tu contraseña. Asegurate de recordarlo!"
-#: templates/authopenid/changepw.html:26
+#: templates/authopenid/changepw.html:27
msgid "Current password"
msgstr "Contraseña actual"
-#: templates/authopenid/changepw.html:27
+#: templates/authopenid/changepw.html:28
msgid "New password"
msgstr "Nueva contraseña"
-#: templates/authopenid/changepw.html:28
+#: templates/authopenid/changepw.html:29
msgid "New password again"
msgstr "Nueva contraseña nuevamente"
-#: templates/authopenid/changepw.html:29 templates/authopenid/settings.html:28
+#: templates/authopenid/changepw.html:30 templates/authopenid/settings.html:29
msgid "Change password"
msgstr "Cambiar contraseña"
-#: templates/authopenid/complete.html:4
+#: templates/authopenid/complete.html:5
msgid "Connect your OpenID with this site"
msgstr "Vincular tu OpenID con este sitio"
-#: templates/authopenid/complete.html:7
+#: templates/authopenid/complete.html:8
msgid "Connect your OpenID with your account on this site"
msgstr "Vincular tu OpenID con tu cuenta en este sitio"
-#: templates/authopenid/complete.html:10
-msgid "Your OpenID is accepted. Please complete this to finish registration."
-msgstr ""
-"Tu OpenID es aceptada. Por favor completa lo siguiente para finalizar el "
-"registro."
+#: templates/authopenid/complete.html:12
+#, python-format
+msgid "register new %(provider)s account info"
+msgstr "Registrar una nueva cuenta %(provider)s."
-#: templates/authopenid/complete.html:11
+#: templates/authopenid/complete.html:14
msgid "This account already exists, please use another."
msgstr "Esta cuenta ya existe, por favor usar otra."
-#: templates/authopenid/complete.html:16 templates/authopenid/complete.html:29
-#: templates/authopenid/signin.html:43
+#: templates/authopenid/complete.html:19 templates/authopenid/complete.html:32
+#: templates/authopenid/signin.html:121
msgid "Sorry, looks like we have some errors:"
msgstr "Ups, parece que hay errores:"
-#: templates/authopenid/complete.html:45
-msgid "New account"
-msgstr "Nueva cuenta"
-
-#: templates/authopenid/complete.html:46
-msgid "User name (<i>will be shown to others, cannot be modified</i>)"
-msgstr ""
-"Nombre de usuario (<i>será mostrado a otros, no puede ser modificado</i>)"
-
#: templates/authopenid/complete.html:47
-msgid "Email (<i>not shared with anyone</i>)"
-msgstr "Email (<i>no será compartido con nadie</i>)"
+msgid "Screen name label"
+msgstr ""
#: templates/authopenid/complete.html:48
+msgid "Email address label"
+msgstr "su email (correo electrónico)"
+
+#: templates/authopenid/complete.html:49
msgid "create account"
msgstr "crear cuenta"
@@ -1907,55 +2225,53 @@ msgstr "contraseña"
msgid "Register"
msgstr "Registrarse"
-#: templates/authopenid/complete.html:62 templates/authopenid/signin.html:62
+#: templates/authopenid/complete.html:62 templates/authopenid/signin.html:140
msgid "Forgot your password?"
msgstr "¿Olvidaste tu contraseña?"
-#: templates/authopenid/delete.html:8
+#: templates/authopenid/delete.html:9
msgid "Account: delete account"
msgstr "Cuenta: borrar cuenta"
-#: templates/authopenid/delete.html:12
+#: templates/authopenid/delete.html:13
msgid ""
"Note: After deleting your account, anyone will be able to register this "
"username."
msgstr ""
-"Nota: Luego de borrar tu cuenta, cualquiera podrá registrarse con este "
-"nombre de usuario."
-#: templates/authopenid/delete.html:16
+#: templates/authopenid/delete.html:17
msgid "Check confirm box, if you want delete your account."
msgstr "Marca caja de confirmación, si deseas borrar tu cuenta."
-#: templates/authopenid/delete.html:19
+#: templates/authopenid/delete.html:20
msgid "Password:"
msgstr "Contraseña"
-#: templates/authopenid/delete.html:31
+#: templates/authopenid/delete.html:32
msgid "I am sure I want to delete my account."
msgstr "Estoy seguro que quiero borrar mi cuenta."
-#: templates/authopenid/delete.html:32
+#: templates/authopenid/delete.html:33
msgid "Password/OpenID URL"
msgstr "Contraseña/OpenID URL"
-#: templates/authopenid/delete.html:32
+#: templates/authopenid/delete.html:33
msgid "(required for your security)"
msgstr "(requerido por tu seguridad)"
-#: templates/authopenid/delete.html:34
+#: templates/authopenid/delete.html:35
msgid "Delete account permanently"
msgstr "Borrar la cuenta de forma permanente"
-#: templates/authopenid/sendpw.html:3 templates/authopenid/sendpw.html.py:7
+#: templates/authopenid/sendpw.html:4 templates/authopenid/sendpw.html.py:8
msgid "Send new password"
msgstr "Enviar nueva contraseña"
-#: templates/authopenid/sendpw.html:11
+#: templates/authopenid/sendpw.html:12
msgid "Lost your password? No problem - here you can reset it."
msgstr "¿Haz perdido tu contraseña? No hay problema - aquí puedes re-crearla."
-#: templates/authopenid/sendpw.html:12
+#: templates/authopenid/sendpw.html:13
msgid ""
"Please enter your username below and new password will be sent to your "
"registered e-mail"
@@ -1963,15 +2279,19 @@ msgstr ""
"Por favor, ingresa tu nombre de usuario y una nueva contraseña será enviada "
"a la dirección de email registrada."
-#: templates/authopenid/sendpw.html:29
+#: templates/authopenid/sendpw.html:28
+msgid "User name"
+msgstr "Nombre de usuario"
+
+#: templates/authopenid/sendpw.html:30
msgid "Reset password"
msgstr "Re-crear contraseña"
-#: templates/authopenid/sendpw.html:29
+#: templates/authopenid/sendpw.html:30
msgid "return to login"
msgstr "volver a 'Ingresar'"
-#: templates/authopenid/sendpw.html:32
+#: templates/authopenid/sendpw.html:33
msgid ""
"Note: your new password will be activated only after you click the "
"activation link in the email message"
@@ -1979,104 +2299,132 @@ msgstr ""
"Nota: tu nueva contraseña solo será activada luego de que hagas click en el "
"link de activación en el email enviado."
-#: templates/authopenid/settings.html:29
+#: templates/authopenid/settings.html:30
msgid "Give your account a new password."
msgstr "Crea una nueva contraseña para tu cuenta."
-#: templates/authopenid/settings.html:30
+#: templates/authopenid/settings.html:31
msgid "Change email "
msgstr "Cambiar email "
-#: templates/authopenid/settings.html:31
+#: templates/authopenid/settings.html:32
msgid "Add or update the email address associated with your account."
msgstr "Agrega o actualiza el email asociado a tu cuenta."
-#: templates/authopenid/settings.html:34
+#: templates/authopenid/settings.html:35
msgid "Change openid associated to your account"
msgstr "Cambia el OpenID asociado a tu cuenta"
-#: templates/authopenid/settings.html:37
+#: templates/authopenid/settings.html:38
msgid "Delete account"
msgstr "Eliminar cuenta"
-#: templates/authopenid/settings.html:38
+#: templates/authopenid/settings.html:39
msgid "Erase your username and all your data from website"
msgstr "Eliminar tu nombre de usuario y toda tu información del sitio"
-#: templates/authopenid/signin.html:3 templates/authopenid/signin.html:16
+#: templates/authopenid/signin.html:4 templates/authopenid/signin.html:21
msgid "User login"
msgstr "Ingreso de usuario"
-#: templates/authopenid/signin.html:21
-msgid "we support two login modes"
+#: templates/authopenid/signin.html:28
+#, python-format
+msgid ""
+"\n"
+" Your answer to %(title)s %(summary)s will be posted once you "
+"log in\n"
+" "
msgstr ""
-"Puedes ingresar por cualquiera de los siguientes servicios, o "
-"tradicionalmente- usando nombre de usuario y contraseña locales"
+"\n"
+" Tu respuesta a %(title)s %(summary)s será publicada una vez "
+"que ingreses \n"
+" "
-#: templates/authopenid/signin.html:26 templates/authopenid/signup.html:49
-msgid "Login with your OpenID"
-msgstr "Ingresar con tu OpenID"
+#: templates/authopenid/signin.html:35
+#, python-format
+msgid ""
+"Your question \n"
+" %(title)s %(summary)s will be posted once you log in\n"
+" "
+msgstr ""
+"Tu pregunta \n"
+" %(title)s %(summary)s será publicada una vez que ingreses\n"
+" "
-#: templates/authopenid/signin.html:28
-msgid "select openid provider"
-msgstr "1) Selecciona tu proveedor de OpenID"
+#: templates/authopenid/signin.html:40
+msgid "Click to sign in through any of these services."
+msgstr ""
-#: templates/authopenid/signin.html:32
-msgid "verify openid link and login"
+#: templates/authopenid/signin.html:103
+msgid "Enter your <span id=\"enter_your_what\">Provider user name</span>"
msgstr ""
-"2) Varifica la URL de tu OpenID (escribe tu nombre de usuario donde dice "
-"{nombre de usuario} si lo ves) y luego clickea 'ingresar'"
-#: templates/authopenid/signin.html:58
+#: templates/authopenid/signin.html:110
+msgid ""
+"Enter your <a class=\"openid_logo\" href=\"http://openid.net\">OpenID</a> "
+"web address"
+msgstr ""
+"Ingresa tu dirección (URL) de <a class=\"openid_logo\" href=\"http://openid."
+"net\">OpenID</a>"
+
+#: templates/authopenid/signin.html:112 templates/authopenid/signin.html:139
+msgid "Login"
+msgstr "Ingresar"
+
+#: templates/authopenid/signin.html:116
+msgid "we support two login modes"
+msgstr "soportamos dos tipos de ingreso"
+
+#: templates/authopenid/signin.html:136
msgid "Use login name and password"
msgstr "Nombre de usuario y contraseña"
-#: templates/authopenid/signin.html:59
+#: templates/authopenid/signin.html:137
msgid "Login name"
msgstr "Nombre de usuario"
-#: templates/authopenid/signin.html:63
-msgid "Create new acccount"
+#: templates/authopenid/signin.html:141
+msgid "Create new account"
msgstr "Crear cuenta nueva"
-#: templates/authopenid/signin.html:72
+#: templates/authopenid/signin.html:150
msgid "Why use OpenID?"
msgstr "¿Porqué usar OpenID?"
-#: templates/authopenid/signin.html:76
+#: templates/authopenid/signin.html:154
msgid "with openid it is easier"
msgstr "Con OpenID no necesitas crear un nuevo nombre de usuario y contraseña."
-#: templates/authopenid/signin.html:79
+#: templates/authopenid/signin.html:157
msgid "reuse openid"
msgstr ""
"Puedes de forma segura re-usar el mismo nombre de usuario para todos los "
"sitios que acepten OpenID."
-#: templates/authopenid/signin.html:82
+#: templates/authopenid/signin.html:160
msgid "openid is widely adopted"
msgstr ""
"OpenID es extensamente usado. Hay más de 160,000,000 cuentas de OpenID en "
"uso en el mundo. Mas de 10,000 sitios aceptan OpenID."
-#: templates/authopenid/signin.html:85
+#: templates/authopenid/signin.html:163
msgid "openid is supported open standard"
msgstr ""
"OpenID es basado en un standard abierto, apoyado por muchas organizaciones."
-#: templates/authopenid/signin.html:89
+#: templates/authopenid/signin.html:167
msgid "Find out more"
msgstr "Averigua más"
-#: templates/authopenid/signin.html:90
+#: templates/authopenid/signin.html:168
msgid "Get OpenID"
msgstr "Adquiere una OpenID"
-#: templates/authopenid/signup.html:2 templates/authopenid/signup.html.py:6
+#: templates/authopenid/signup.html:4 templates/authopenid/signup.html.py:8
msgid "Signup"
msgstr "Registrate"
-#: templates/authopenid/signup.html:10
+#: templates/authopenid/signup.html:12
msgid ""
"We support two types of user registration: conventional username/password, "
"and"
@@ -2084,26 +2432,30 @@ msgstr ""
"Soportamos dos formas de registro de usuario: convencional usuario/"
"contraseña, y"
-#: templates/authopenid/signup.html:10
+#: templates/authopenid/signup.html:12
msgid "the OpenID method"
msgstr "OpenID"
-#: templates/authopenid/signup.html:15
+#: templates/authopenid/signup.html:17
msgid "Sorry, looks like we have some errors"
msgstr "Ups, parece que hay errores."
-#: templates/authopenid/signup.html:33
+#: templates/authopenid/signup.html:35
msgid "Conventional registration"
msgstr "Registro clásico"
-#: templates/authopenid/signup.html:34
+#: templates/authopenid/signup.html:36
msgid "choose a user name"
msgstr "elije un nombre de usuario"
-#: templates/authopenid/signup.html:40
+#: templates/authopenid/signup.html:42
msgid "back to login"
msgstr "volver al ingreso de usuario"
-#: templates/authopenid/signup.html:46
+#: templates/authopenid/signup.html:48
msgid "Register with your OpenID"
msgstr "Registrate con tu OpenID"
+
+#: templates/authopenid/signup.html:51
+msgid "Login with your OpenID"
+msgstr "Ingresar con tu OpenID"
diff --git a/locale/zh_CN/LC_MESSAGES/django.mo b/locale/zh_CN/LC_MESSAGES/django.mo
index 3d45237b..5173c858 100644
--- a/locale/zh_CN/LC_MESSAGES/django.mo
+++ b/locale/zh_CN/LC_MESSAGES/django.mo
Binary files differ
diff --git a/locale/zh_CN/LC_MESSAGES/django.po b/locale/zh_CN/LC_MESSAGES/django.po
index ba32e84a..4d8524b6 100644
--- a/locale/zh_CN/LC_MESSAGES/django.po
+++ b/locale/zh_CN/LC_MESSAGES/django.po
@@ -1,5 +1,3 @@
-# author Evgeny Fadeev (evgeny.fadeev@gmail.com)
-# site-specific messages
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
@@ -66,7 +64,6 @@ msgid "this name is already in use - please try anoter"
msgstr "该用户名已被注册,请换一个试试"
#: django_authopenid/forms.py:184
-#, fuzzy
msgid ""
"This email is already registered in our database. Please "
"choose another."
@@ -78,7 +75,6 @@ msgid ""
msgstr ""
#: django_authopenid/forms.py:253
-#, fuzzy
msgid "choose a username"
msgstr "选择一个用户名"
@@ -105,7 +101,6 @@ msgid "new passwords do not match"
msgstr ""
#: django_authopenid/forms.py:434
-#, fuzzy
msgid "Incorrect username."
msgstr "选择一个用户名"
@@ -132,7 +127,6 @@ msgid "sendpw/"
msgstr ""
#: django_authopenid/urls.py:26
-#, fuzzy
msgid "delete/"
msgstr "删除"
@@ -385,7 +379,7 @@ msgstr "多个标签请用空格间隔-最多5个标签。(优先使用自动
#: forum/forms.py:53
msgid "tags are required"
-msgstr "标签不能为空。"
+msgstr " 标签不能为空。"
#: forum/forms.py:57
msgid "please use 5 tags or less"
@@ -698,6 +692,9 @@ msgstr "预览开关"
msgid "Save edit"
msgstr "现在修改"
+msgid "Save"
+msgstr "保存"
+
#: templates/answer_edit.html:74 templates/close.html:29
#: templates/question_edit.html:120 templates/reopen.html:30
#: templates/user_edit.html:83
@@ -768,18 +765,16 @@ msgid "Community badges"
msgstr "社区奖牌"
#: templates/badges.html:53
-#, fuzzy
-msgid ""
+msgid "gold badge description"
"Gold badge is very rare. To obtain it you have to show profound knowledge "
"and ability in addition to actively participating in the community. Gold "
"badge is the highest award in this community."
msgstr ""
-"你不仅要参与社区的提问、回答、投票等活动,而且需要有高深的知识和能力才能获"
+"金牌是十分罕见的。你不仅要参与社区的提问、回答、投票等活动,而且需要有高深的知识和能力才能获"
"得。"
#: templates/badges.html:61
-#, fuzzy
-msgid ""
+msgid "silver badge description"
"Obtaining silver badge requires significant patience. If you got one, you've "
"very significantly contributed to this community"
msgstr "它是不同寻常的荣誉,只要你付出足够的努力就会得到。"
@@ -788,9 +783,24 @@ msgstr "它是不同寻常的荣誉,只要你付出足够的努力就会得到
msgid "bronze badge: often given as a special honor"
msgstr "铜牌:时常授予之特殊荣誉"
+msgid "Connect with Twitter"
+msgstr "同步Twitter消息"
+
+msgid "Twitter account name:"
+msgstr "账号:"
+
+msgid "Twitter password:"
+msgstr "密码:"
+
+msgid "Send my Questions to Twitter"
+msgstr "发布我的提问到我的Twitter"
+
+msgid "Send my Answers to Twitter"
+msgstr "发布我的回答到我的Twitter"
+
#: templates/badges.html:68
#, fuzzy
-msgid ""
+msgid "bronze badge description"
"If you are active in this community, you will get this medal - still it is a "
"special honor."
msgstr "铜牌会在你活跃于社区时产生,它相对容易获得,但也是一种特殊的荣誉。"
@@ -1020,6 +1030,8 @@ msgid "most voted questions"
msgstr "投票次数最多的问题"
#: templates/index.html:25
+#"最有价值问题"
+#"最新问题"
msgid "most voted"
msgstr "最有价值的问题"
@@ -1041,7 +1053,6 @@ msgid "see questions tagged"
msgstr "查看有关"
#: templates/index.html:79
-#, fuzzy
msgid "welcome to website"
msgstr "CNProg欢迎您!"
@@ -1053,7 +1064,7 @@ msgstr "最新标签"
#: templates/index.html:93
#, fuzzy, python-format
msgid "see questions tagged '%(tagname)s'"
-msgstr "查看有关'%s'的问题"
+msgstr "查看有关'%(tagname)s'的问题"
#: templates/index.html:96 templates/index.html.py:120
msgid "popular tags"
@@ -1092,6 +1103,26 @@ msgstr "或者"
msgid "."
msgstr "。"
+#: templates/pagesize.html:5
+msgid "posts per page"
+msgstr "每页显示:"
+
+#: templates/paginator.html:5
+msgid "previous"
+msgstr "上一页"
+
+#: templates/questions.html:22
+msgid "Found by tags"
+msgstr "标签问题"
+
+#: templates/questions.html:25
+msgid "Active"
+msgstr "活跃问题"
+
+#: templates/authopenid/changeemail.html:10
+msgid "Account: change email"
+msgstr "修改电子邮件"
+
#: templates/index.html:120
msgid "Please help us answer"
msgstr "请帮助我们回答"
@@ -1117,12 +1148,6 @@ msgstr "点击退出登录"
msgid "Size per page:"
msgstr "每页显示"
-# paginator.html
-#: templates/paginator.html:5
-#, fuzzy
-msgid "Previous"
-msgstr "上一页"
-
#: templates/privacy.html:6 templates/privacy.html.py:11
msgid "Privacy policy"
msgstr "隐私政策"
@@ -1432,76 +1457,21 @@ msgstr "基本的HTML标签也是支持的"
msgid "learn more about Markdown"
msgstr "有关Markdown详细说明"
-#: templates/questions.html:6
-#, fuzzy
-msgid "Question list"
-msgstr "问题列表"
-
-#: templates/questions.html:22
-#, fuzzy
-msgid "Tagged questions"
-msgstr "相似的问题"
-
-#: templates/questions.html:22
-msgid "Query result"
-msgstr ""
-
#: templates/questions.html:22
msgid "All questions"
msgstr "所有问题"
-#: templates/questions.html:24
-#, fuzzy
-msgid "New questions"
-msgstr "问题"
-
-#: templates/questions.html:24
-#, fuzzy
-msgid "Newest"
-msgstr "最新问题"
-
-#: templates/questions.html:25
-#, fuzzy
-msgid "Newest updated questions"
-msgstr "最新更新的问题"
-
-#: templates/questions.html:25
-#, fuzzy
-msgid "Active"
-msgstr "活跃问题"
-
#: templates/questions.html:26
#, fuzzy
msgid "Questions with most answers"
msgstr "您正在浏览的问题含有以下标签"
-#: templates/questions.html:26
-#, fuzzy
-msgid "Hottest"
-msgstr "热门问题"
-
#: templates/questions.html:27
msgid "Questions with most votes"
msgstr ""
-#: templates/questions.html:27
-#, fuzzy
-msgid "Best"
-msgstr "最新问题"
-
-#: templates/questions.html:45
-#, fuzzy
-msgid "Votes"
-msgstr "票"
-
-#: templates/questions.html:46
-#, fuzzy
-msgid "Visits"
-msgstr "网站访问者"
-
# index.html
#: templates/questions.html:57 templates/questions.html.py:69
-#, fuzzy
msgid "Community wiki"
msgstr "社区Wiki"
@@ -1706,6 +1676,9 @@ msgstr "用户投的反对票总数"
msgid "Tags"
msgstr "个标签"
+msgid "Tags help us keep Questions organized"
+msgstr "CNProg用标签来分类系统的信息"
+
#: templates/user_stats.html:94
#, fuzzy, python-format
msgid "see other questions tagged '%(tag)s' "
@@ -1775,11 +1748,6 @@ msgstr "这个问题被"
msgid "this answer has been accepted to be correct"
msgstr "有答案已被接受为正确答案"
-#: templates/authopenid/changeemail.html:6
-#, fuzzy
-msgid "Account: change email"
-msgstr "更换电子邮件"
-
#: templates/authopenid/changeemail.html:9
msgid ""
"This is where you can change the email address associated with your account. "
@@ -1983,7 +1951,6 @@ msgid "Give your account a new password."
msgstr ""
#: templates/authopenid/settings.html:30
-#, fuzzy
msgid "Change email "
msgstr "更换电子邮件"
@@ -2102,350 +2069,350 @@ msgstr "返回登录"
msgid "Register with your OpenID"
msgstr "使用OpenID注册"
-#~ msgid "meta site keywords, comma separated"
-#~ msgstr ""
-#~ "技术问答社区,中国程序员,编程技术社区,程序员社区,程序员论坛,程序员"
-#~ "wiki,程序员博客"
+msgid "meta site keywords, comma separated"
+msgstr ""
+"技术问答社区,中国程序员,编程技术社区,程序员社区,程序员论坛,程序员"
+"wiki,程序员博客"
-#~ msgid "what is this website"
-#~ msgstr ""
-#~ "CNProg是一个<strong>面向程序员</strong>的可协作编辑的<strong>开放源代码问"
-#~ "答社区</strong>。"
+msgid "what is this website"
+msgstr ""
+"CNProg是一个<strong>面向程序员</strong>的可协作编辑的<strong>开放源代码问"
+"答社区</strong>。"
-#~ msgid "what can one do on this website"
-#~ msgstr ""
-#~ "您可以在这里提问各类<strong>程序技术问题</strong> - 问题不分语言和平台。 "
-#~ "同时也希望您对力所能及的问题,给予您的宝贵答案。"
+msgid "what can one do on this website"
+msgstr ""
+"您可以在这里提问各类<strong>程序技术问题</strong> - 问题不分语言和平台。 "
+"同时也希望您对力所能及的问题,给予您的宝贵答案。"
-#~ msgid "Goal of this site is..."
-#~ msgstr "CNProg 是为了帮助程序员解决更多问题,更加方便的解决问题。"
+msgid "Goal of this site is..."
+msgstr "CNProg 是为了帮助程序员解决更多问题,更加方便的解决问题。"
-#~ msgid "Community gives you awards for your questions, answers and votes."
-#~ msgstr ""
-#~ "提出问题,给予回答,投出你的票 - CNProg 会针对你在社区的表现,授予你各类奖"
-#~ "牌。"
+msgid "Community gives you awards for your questions, answers and votes."
+msgstr ""
+"提出问题,给予回答,投出你的票 - CNProg 会针对你在社区的表现,授予你各类奖"
+"牌。"
-#~ msgid "please make your answer relevant to this community"
-#~ msgstr "您的问题与编程相关吗?"
+msgid "please make your answer relevant to this community"
+msgstr "您的问题与编程相关吗?"
-#~ msgid "book technical Q&A"
-#~ msgstr "图书相关的技术答疑"
+msgid "book technical Q&A"
+msgstr "图书相关的技术答疑"
-#~ msgid "blog"
-#~ msgstr "Blog"
+msgid "blog"
+msgstr "Blog"
-#~ msgid "privacy policy"
-#~ msgstr "隐私政策"
+msgid "privacy policy"
+msgstr "隐私政策"
-#~ msgid "current revision"
-#~ msgstr "当前版本"
+msgid "current revision"
+msgstr "当前版本"
-#~ msgid "number of votes"
-#~ msgstr "票数"
+msgid "number of votes"
+msgstr "票数"
-#~ msgid "current page"
-#~ msgstr "当前页"
+msgid "current page"
+msgstr "当前页"
-#~ msgid "next page"
-#~ msgstr "下一页"
+msgid "next page"
+msgstr "下一页"
-#~ msgid "page number "
-#~ msgstr "第"
+msgid "page number "
+msgstr "第"
-#~ msgid "number - make blank in english"
-#~ msgstr "页"
+msgid "number - make blank in english"
+msgstr "页"
-#~ msgid "Change tags"
-#~ msgstr "修改问题标签"
+msgid "Change tags"
+msgstr "修改问题标签"
# todo: remove magic numbers from this file
-#~ msgid "up to 5 tags, less than 20 characters each"
-#~ msgstr "最多5个标签,每个标签长度小于20个字符。"
-
-#~ msgid "Change now"
-#~ msgstr "现在修改"
-
-#~ msgid "uses tags for the classification of questions"
-#~ msgstr "用标签来分类系统的信息"
-
-#~ msgid "tag editors receive special awards from the community"
-#~ msgstr "修改标签的用户将授予特殊的社区奖牌"
-
-#~ msgid "Why use and modify tags?"
-#~ msgstr "为什么我只能修改问题标签?"
-
-#~ msgid "Found by tag"
-#~ msgstr "标签问题"
-
-#~ msgid "Found by title"
-#~ msgstr "查询结果"
-
-#~ msgid "most recently updated questions"
-#~ msgstr "最近被更新的问题"
-
-#~ msgid "latest questions info"
-#~ msgstr ""
-#~ "问题按<strong>提问时间</strong>显示排序。新加入的问题将显示在最前面。"
-
-#~ msgid ""
-#~ "\n"
-#~ "\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
-#~ "\t\t\t"
-#~ msgid_plural ""
-#~ "\n"
-#~ "\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
-#~ "\t\t\t"
-#~ msgstr[0] ""
-#~ "\n"
-#~ "您正在浏览所有<div class=\"questions-count\">%(q_num)s</div>个标记为<span "
-#~ "class=\"tag\">%(tagname)s</span></p>"
-#~ msgstr[1] ""
-#~ "\n"
-#~ "您正在浏览所有<div class=\"questions-count\">%(q_num)s</div>个标记为<span "
-#~ "class=\"tag\">%(tagname)s</span></p>"
-
-#~ msgid ""
-#~ "\n"
-#~ "\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
-#~ "\t\t\t"
-#~ msgid_plural ""
-#~ "\n"
-#~ "\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
-#~ "\t\t\t"
-#~ msgstr[0] ""
-#~ "\n"
-#~ "您正在浏览所有<div class=\"questions-count\">%(q_num)s</div>个标题含有"
-#~ "<span class=\"tag\">%(searchtitle)s</span></p>"
-#~ msgstr[1] ""
-#~ "\n"
-#~ "您正在浏览所有<div class=\"questions-count\">%(q_num)s</div>个标题含有"
-#~ "<span class=\"tag\">%(searchtitle)s</span></p>"
-
-#~ msgid "number of questions"
-#~ msgstr "个"
-
-#~ msgid "number of <strong>unanswered</strong> questions"
-#~ msgstr ""
-#~ "个 <span class=\"darkred\"><strong>没有回答的</strong></span> 问题。"
-
-#~ msgid "tagged with"
-#~ msgstr "标记为"
-
-#~ msgid "whose title contains"
-#~ msgstr "标题含有"
-
-#~ msgid "number of questions end of sentence"
-#~ msgstr "的问题。"
-
-#~ msgid "Questions are sorted by the <strong>time of last update</strong>."
-#~ msgstr "问题按<strong>最后更新时间</strong>显示排序。"
-
-#~ msgid "Most recently answered ones are shown first."
-#~ msgstr "最后被回答或者>更新的问题将显示在最前面。"
-
-#~ msgid "Questions sorted by <strong>number of responses</strong>."
-#~ msgstr "问题按<strong>回复数量</strong>显示排序。"
-
-#~ msgid "Most answered questions are shown first"
-#~ msgstr "回复最多的问题将显示在最前面。"
-
-#~ msgid "Questions are sorted by the <strong>number of votes</strong>."
-#~ msgstr "问题按<strong>投票数量</strong>显示排序。"
-
-#~ msgid "Most voted questions are shown first"
-#~ msgstr "投票最多的问题将显示在最前面。"
-
-#~ msgid "questions that user selected as his/her favorite"
-#~ msgstr "用户收藏的问题"
-
-#~ msgid "Frequently Asked Questions "
-#~ msgstr "常见问题"
-
-#~ msgid "What kinds of questions can I ask here?"
-#~ msgstr "我可以在这里提问什么样的问题?"
-
-#~ msgid "What questions should I avoid asking?"
-#~ msgstr "什么样的问题我不该在这里提问?"
-
-#~ msgid ""
-#~ "Most importanly - questions should be <strong>relevant</strong> to this "
-#~ "community."
-#~ msgstr "毫无疑问,首先必须是<span class=\"yellowbg\">技术编程问题!</span>"
-
-#~ msgid ""
-#~ "Before asking the question - please make sure to use search to see "
-#~ "whether your question has alredy been answered."
-#~ msgstr ""
-#~ "提问之前,充分利用系统的自动查找、标签和搜索,看看是否已经有一样的问题并有"
-#~ "了答案。"
-
-#~ msgid "What should I avoid in my answers?"
-#~ msgstr "什么样的回答是不受欢迎的?"
-
-#~ msgid "Who moderates this community?"
-#~ msgstr "谁是社区的管理员?"
-
-#~ msgid ""
-#~ "Please avoid asking questions that are not relevant to this community, "
-#~ "too subjective and argumentative."
-#~ msgstr ""
-#~ "<span class=\"yellowbg\">与程序员或技术无关的,引起争吵或太过于主观性等违"
-#~ "背社区宗旨的内容。</span>本站建立是为了帮助大众程序员解决实际技术问题,我"
-#~ "们需要实际的问题!"
-
-#~ msgid ""
-#~ "is a Q&A site, not a discussion group. Therefore - please avoid having "
-#~ "discussions in your answers, comment facility allows some space for brief "
-#~ "discussions."
-#~ msgstr ""
-#~ "希望用户提供针对提问的技术回答,可以是进一步了解问题实质,给予参考方案,或"
-#~ "完全解决问题的回答。我们希望通过问答的形式解决用户的实际问题。因此,<span "
-#~ "class=\"yellowbg\">我们不>欢迎在回答中出现不是回答问题的内容,包括针对他人"
-#~ "回答的讨论,和其他无意义的浪费网络资源行为</span>。CNProg建议您使用<span "
-#~ "class=\"yellowbg\">评论</span>功能来讨论你的意见和想法。"
-
-#~ msgid "The short answer is: <strong>you</strong>."
-#~ msgstr "答案是:<span class=\"yellowbg\">每个用户。</span>"
-
-#~ msgid ""
-#~ "The reputation system allows users earn the authorization to perform a "
-#~ "variety of moderation tasks."
-#~ msgstr ""
-#~ "通过积分运作,<span class=\"yellowbg\">每个用户都有权限创建标签,进行对所"
-#~ "有问题、回答的投票、编辑、关闭等操作。</span>"
-
-#~ msgid "This website is moderated by the users."
-#~ msgstr "社区没有严格意义上的管理员身份"
-
-#~ msgid "How does reputation system work?"
-#~ msgstr "什么是社区积分?"
-
-#~ msgid ""
-#~ "Anyone can ask questions and give answers, points are not necessary for "
-#~ "that."
-#~ msgstr "对于正常使用社区进行提问、回答而言,积分不是必须的。"
-
-#~ msgid ""
-#~ "As we've said before, users help running this site. Point system helps "
-#~ "select users who can administer this community."
-#~ msgstr ""
-#~ "我们一再声明,CNProg由你来运行和维护。如果你想帮助我们来运作CNProg,你需要"
-#~ "一定的积分等级。"
-
-#~ msgid ""
-#~ "Reputation points roughly measure how community trusts you. These points "
-#~ "are given to you directly by other members of the community."
-#~ msgstr ""
-#~ "<span class=\"yellowbg\">积分是一种用来粗略衡量社区对你有多信任的数据。</"
-#~ "span>积分不是有谁来支付或直接给予你的,而是你通过获得其他用户的支持和信"
-#~ "任“赚得”的。"
-
-#~ msgid ""
-#~ "For example, if you ask an interesting question or give a helpful answer, "
-#~ "your input will be upvoted and you will gain more trust in the community."
-#~ msgstr ""
-#~ "举例来说,如果你提了一个非常有帮助的问题或者做了很有用的回答,你将会被其他"
-#~ "用户投赞成票。"
-
-#~ msgid ""
-#~ "If on the other hand someone gives a misleading answer, the answer will "
-#~ "be voted down and he/she loses some points."
-#~ msgstr ""
-#~ "相反,你提了不受欢迎的问题,或者误导用户的回答,你将可能被其他用户投反对"
-#~ "票。每个赞成"
-
-#~ msgid ""
-#~ "Each vote in favor will generate <strong>10</strong> points, each vote "
-#~ "against will subtract <strong>2</strong> points."
-#~ msgstr ""
-#~ "票会帮你产生<strong>10</strong>个社区积分,每个反对票会相应扣除你"
-#~ "<strong>2</strong>个积分。"
-
-#~ msgid ""
-#~ "Through the votes of other people you can accumulate a maximum of "
-#~ "<strong>200</strong> points."
-#~ msgstr ""
-#~ "每天通过别人投赞成票,你最多只能产生<strong>200</strong>个积分,这是上限。"
-
-#~ msgid "After accumulating certain number of points, you can do more:"
-#~ msgstr "当你累计到一定>积分,你可以在社区做更多的事情:"
+msgid "up to 5 tags, less than 20 characters each"
+msgstr "最多5个标签,每个标签长度小于20个字符。"
+
+msgid "Change now"
+msgstr "现在修改"
+
+msgid "uses tags for the classification of questions"
+msgstr "用标签来分类系统的信息"
+
+msgid "tag editors receive special awards from the community"
+msgstr "修改标签的用户将授予特殊的社区奖牌"
+
+msgid "Why use and modify tags?"
+msgstr "为什么我只能修改问题标签?"
+
+msgid "Found by tag"
+msgstr "标签问题"
+
+msgid "Found by title"
+msgstr "查询结果"
+
+msgid "most recently updated questions"
+msgstr "最近被更新的问题"
+
+msgid "latest questions info"
+msgstr ""
+"问题按<strong>提问时间</strong>显示排序。新加入的问题将显示在最前面。"
+
+msgid ""
+"\n"
+"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
+"\t\t\t"
+msgid_plural ""
+"\n"
+"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
+"\t\t\t"
+msgstr[0] ""
+"\n"
+"您正在浏览所有<div class=\"questions-count\">%(q_num)s</div>个标记为<span "
+"class=\"tag\">%(tagname)s</span></p>"
+msgstr[1] ""
+"\n"
+"您正在浏览所有<div class=\"questions-count\">%(q_num)s</div>个标记为<span "
+"class=\"tag\">%(tagname)s</span></p>"
+
+msgid ""
+"\n"
+"\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
+"\t\t\t"
+msgid_plural ""
+"\n"
+"\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
+"\t\t\t"
+msgstr[0] ""
+"\n"
+"您正在浏览所有<div class=\"questions-count\">%(q_num)s</div>个标题含有"
+"<span class=\"tag\">%(searchtitle)s</span></p>"
+msgstr[1] ""
+"\n"
+"您正在浏览所有<div class=\"questions-count\">%(q_num)s</div>个标题含有"
+"<span class=\"tag\">%(searchtitle)s</span></p>"
+
+msgid "number of questions"
+msgstr "个"
+
+msgid "number of <strong>unanswered</strong> questions"
+msgstr ""
+"个 <span class=\"darkred\"><strong>没有回答的</strong></span> 问题。"
+
+msgid "tagged with"
+msgstr "标记为"
+
+msgid "whose title contains"
+msgstr "标题含有"
+
+msgid "number of questions end of sentence"
+msgstr "的问题。"
+
+msgid "Questions are sorted by the <strong>time of last update</strong>."
+msgstr "问题按<strong>最后更新时间</strong>显示排序。"
+
+msgid "Most recently answered ones are shown first."
+msgstr "最后被回答或者>更新的问题将显示在最前面。"
+
+msgid "Questions sorted by <strong>number of responses</strong>."
+msgstr "问题按<strong>回复数量</strong>显示排序。"
+
+msgid "Most answered questions are shown first"
+msgstr "回复最多的问题将显示在最前面。"
+
+msgid "Questions are sorted by the <strong>number of votes</strong>."
+msgstr "问题按<strong>投票数量</strong>显示排序。"
+
+msgid "Most voted questions are shown first"
+msgstr "投票最多的问题将显示在最前面。"
+
+msgid "questions that user selected as his/her favorite"
+msgstr "用户收藏的问题"
+
+msgid "Frequently Asked Questions "
+msgstr "常见问题"
+
+msgid "What kinds of questions can I ask here?"
+msgstr "我可以在这里提问什么样的问题?"
+
+msgid "What questions should I avoid asking?"
+msgstr "什么样的问题我不该在这里提问?"
+
+msgid ""
+"Most importanly - questions should be <strong>relevant</strong> to this "
+"community."
+msgstr "毫无疑问,首先必须是<span class=\"yellowbg\">技术编程问题!</span>"
+
+msgid ""
+"Before asking the question - please make sure to use search to see "
+"whether your question has alredy been answered."
+msgstr ""
+"提问之前,充分利用系统的自动查找、标签和搜索,看看是否已经有一样的问题并有"
+"了答案。"
+
+msgid "What should I avoid in my answers?"
+msgstr "什么样的回答是不受欢迎的?"
+
+msgid "Who moderates this community?"
+msgstr "谁是社区的管理员?"
+
+msgid ""
+"Please avoid asking questions that are not relevant to this community, "
+"too subjective and argumentative."
+msgstr ""
+"<span class=\"yellowbg\">与程序员或技术无关的,引起争吵或太过于主观性等违"
+"背社区宗旨的内容。</span>本站建立是为了帮助大众程序员解决实际技术问题,我"
+"们需要实际的问题!"
+
+msgid ""
+"is a Q&A site, not a discussion group. Therefore - please avoid having "
+"discussions in your answers, comment facility allows some space for brief "
+"discussions."
+msgstr ""
+"希望用户提供针对提问的技术回答,可以是进一步了解问题实质,给予参考方案,或"
+"完全解决问题的回答。我们希望通过问答的形式解决用户的实际问题。因此,<span "
+"class=\"yellowbg\">我们不>欢迎在回答中出现不是回答问题的内容,包括针对他人"
+"回答的讨论,和其他无意义的浪费网络资源行为</span>。CNProg建议您使用<span "
+"class=\"yellowbg\">评论</span>功能来讨论你的意见和想法。"
+
+msgid "The short answer is: <strong>you</strong>."
+msgstr "答案是:<span class=\"yellowbg\">每个用户。</span>"
+
+msgid ""
+"The reputation system allows users earn the authorization to perform a "
+"variety of moderation tasks."
+msgstr ""
+"通过积分运作,<span class=\"yellowbg\">每个用户都有权限创建标签,进行对所"
+"有问题、回答的投票、编辑、关闭等操作。</span>"
+
+msgid "This website is moderated by the users."
+msgstr "社区没有严格意义上的管理员身份"
+
+msgid "How does reputation system work?"
+msgstr "什么是社区积分?"
+
+msgid ""
+"Anyone can ask questions and give answers, points are not necessary for "
+"that."
+msgstr "对于正常使用社区进行提问、回答而言,积分不是必须的。"
+
+msgid ""
+"As we've said before, users help running this site. Point system helps "
+"select users who can administer this community."
+msgstr ""
+"我们一再声明,CNProg由你来运行和维护。如果你想帮助我们来运作CNProg,你需要"
+"一定的积分等级。"
+
+msgid ""
+"Reputation points roughly measure how community trusts you. These points "
+"are given to you directly by other members of the community."
+msgstr ""
+"<span class=\"yellowbg\">积分是一种用来粗略衡量社区对你有多信任的数据。</"
+"span>积分不是有谁来支付或直接给予你的,而是你通过获得其他用户的支持和信"
+"任“赚得”的。"
+
+msgid ""
+"For example, if you ask an interesting question or give a helpful answer, "
+"your input will be upvoted and you will gain more trust in the community."
+msgstr ""
+"举例来说,如果你提了一个非常有帮助的问题或者做了很有用的回答,你将会被其他"
+"用户投赞成票。"
+
+msgid ""
+"If on the other hand someone gives a misleading answer, the answer will "
+"be voted down and he/she loses some points."
+msgstr ""
+"相反,你提了不受欢迎的问题,或者误导用户的回答,你将可能被其他用户投反对"
+"票。每个赞成"
+
+msgid ""
+"Each vote in favor will generate <strong>10</strong> points, each vote "
+"against will subtract <strong>2</strong> points."
+msgstr ""
+"票会帮你产生<strong>10</strong>个社区积分,每个反对票会相应扣除你"
+"<strong>2</strong>个积分。"
+
+msgid ""
+"Through the votes of other people you can accumulate a maximum of "
+"<strong>200</strong> points."
+msgstr ""
+"每天通过别人投赞成票,你最多只能产生<strong>200</strong>个积分,这是上限。"
+
+msgid "After accumulating certain number of points, you can do more:"
+msgstr "当你累计到一定>积分,你可以在社区做更多的事情:"
# todo - check if it's indeed plural
-#~ msgid "add comments"
-#~ msgstr "添加评论"
+msgid "add comments"
+msgstr "添加评论"
-#~ msgid "retag questions"
-#~ msgstr "给任何问题整理标签"
+msgid "retag questions"
+msgstr "给任何问题整理标签"
-#~ msgid "edit community wiki questions"
-#~ msgstr "编辑wiki类问题"
+msgid "edit community wiki questions"
+msgstr "编辑wiki类问题"
-#~ msgid "edit any answer"
-#~ msgstr "编辑任何问题或答案"
+msgid "edit any answer"
+msgstr "编辑任何问题或答案"
-#~ msgid "reopen any closed questions"
-#~ msgstr "打开关闭任何人的问题"
+msgid "reopen any closed questions"
+msgstr "打开关闭任何人的问题"
-#~ msgid "delete any comment"
-#~ msgstr "删除任何一个评论"
+msgid "delete any comment"
+msgstr "删除任何一个评论"
-#~ msgid "delete any questions and answers and perform other moderation tasks"
-#~ msgstr "删除任何一个问题或答案,及其他管理功能"
+msgid "delete any questions and answers and perform other moderation tasks"
+msgstr "删除任何一个问题或答案,及其他管理功能"
-#~ msgid "To register, do I need to create new password?"
-#~ msgstr "我需要注册一个新用户吗?"
+msgid "To register, do I need to create new password?"
+msgstr "我需要注册一个新用户吗?"
-#~ msgid "Why other people can edit my questions/answers?"
-#~ msgstr "为什么其他人可以修改我的问题/回答?"
+msgid "Why other people can edit my questions/answers?"
+msgstr "为什么其他人可以修改我的问题/回答?"
-#~ msgid "Still have questions?"
-#~ msgstr "还有其他问题?"
+msgid "Still have questions?"
+msgstr "还有其他问题?"
-#~ msgid "Please ask your question, help make our community better!"
-#~ msgstr "如果您对社区还有其他疑问,请一起来完善我们的"
+msgid "Please ask your question, help make our community better!"
+msgstr "如果您对社区还有其他疑问,请一起来完善我们的"
-#~ msgid ""
-#~ "No, you don't have to. You can login through any service that supports "
-#~ "OpenID, e.g. Google, Yahoo, AOL, etc."
-#~ msgstr ""
-#~ "不需要。社区提供了OpenID的登录支持,你要用Google、Yahoo等任何支持OpenID登"
-#~ "录的帐号就可以使用系统。"
+msgid ""
+"No, you don't have to. You can login through any service that supports "
+"OpenID, e.g. Google, Yahoo, AOL, etc."
+msgstr ""
+"不需要。社区提供了OpenID的登录支持,你要用Google、Yahoo等任何支持OpenID登"
+"录的帐号就可以使用系统。"
-#~ msgid "Login now!"
-#~ msgstr "马上登录"
+msgid "Login now!"
+msgstr "马上登录"
-#~ msgid ""
-#~ "So questions and answers can be edited like wiki pages by experienced "
-#~ "users of this site and this improves the overall quality of the knowledge "
-#~ "base content."
-#~ msgstr ""
-#~ "所以问题和答案都是如Wiki一样可编辑的,我们希望社区能帮助用户沉淀、积累更多"
-#~ "有用的知识和经验。"
+msgid ""
+"So questions and answers can be edited like wiki pages by experienced "
+"users of this site and this improves the overall quality of the knowledge "
+"base content."
+msgstr ""
+"所以问题和答案都是如Wiki一样可编辑的,我们希望社区能帮助用户沉淀、积累更多"
+"有用的知识和经验。"
-#~ msgid "If this approach is not for you, we respect your choice."
-#~ msgstr "如果您不喜欢这种方式,我们尊重你的选择。"
+msgid "If this approach is not for you, we respect your choice."
+msgstr "如果您不喜欢这种方式,我们尊重你的选择。"
-#~ msgid "answer tips"
-#~ msgstr "受欢迎的提问"
+msgid "answer tips"
+msgstr "受欢迎的提问"
-#~ msgid "try to give an answer, rather than engage into a discussion"
-#~ msgstr "建议您提的问题是可以被答复的,而不仅仅是可以讨论。"
+msgid "try to give an answer, rather than engage into a discussion"
+msgstr "建议您提的问题是可以被答复的,而不仅仅是可以讨论。"
-#~ msgid "gold badge: the highest honor and is very rare"
-#~ msgstr "金牌:十分罕见之最高荣耀"
+msgid "gold badge: the highest honor and is very rare"
+msgstr "金牌:十分罕见之最高荣耀"
-#~ msgid ""
-#~ "silver badge: occasionally awarded for the very high quality contributions"
-#~ msgstr "银牌:偶尔颁发之优质奖章"
+msgid ""
+"silver badge: occasionally awarded for the very high quality contributions"
+msgstr "银牌:偶尔颁发之优质奖章"
-#~ msgid "Gold badge is very rare."
-#~ msgstr "金牌是十分罕见的。"
+msgid "Gold badge is very rare."
+msgstr "金牌是十分罕见的。"
-#~ msgid "Gold badge is the highest award in this community."
-#~ msgstr "获得金牌意味着你在某个层次上已经达到了顶峰。"
+msgid "Gold badge is the highest award in this community."
+msgstr "获得金牌意味着你在某个层次上已经达到了顶峰。"
-#~ msgid "Obtaining silver badge requires significant patience."
-#~ msgstr "银牌需要经过长时间的奋斗才能获得。"
+msgid "Obtaining silver badge requires significant patience."
+msgstr "银牌需要经过长时间的奋斗才能获得。"
-#~ msgid "%s ago"
-#~ msgstr "于%s<font class=\"darkred\">关闭</font>"
+msgid "%s ago"
+msgstr "于%s<font class=\"darkred\">关闭</font>"
diff --git a/manage.py b/manage.py
index b8c4be8e..5e78ea97 100644
--- a/manage.py
+++ b/manage.py
@@ -1,11 +1,11 @@
-#!/usr/bin/env python
-from django.core.management import execute_manager
-try:
- import settings # Assumed to be in the same directory.
-except ImportError:
- import sys
- sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
- sys.exit(1)
-
-if __name__ == "__main__":
- execute_manager(settings)
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/rmpyc b/rmpyc
new file mode 100644
index 00000000..014575f6
--- /dev/null
+++ b/rmpyc
@@ -0,0 +1 @@
+rm `find . -name '*.pyc'`
diff --git a/settings.py b/settings.py
index 985de51a..fac3c0d5 100644
--- a/settings.py
+++ b/settings.py
@@ -1,99 +1,107 @@
-# encoding:utf-8
-# Django settings for lanai project.
-import os.path
-
-#DEBUG SETTINGS
-DEBUG = True
-TEMPLATE_DEBUG = DEBUG
-INTERNAL_IPS = ('127.0.0.1',)
-
-#for OpenID auth
-ugettext = lambda s: s
-LOGIN_URL = '/%s%s' % (ugettext('account/'), ugettext('signin/'))
-
-#EMAIL AND ADMINS
-ADMINS = (
- ('CNProg team', 'team@cnprog.com'),
-)
-MANAGERS = ADMINS
-
-SERVER_EMAIL = 'webmaster@cnprog.com'
-DEFAULT_FROM_EMAIL = 'webmaster@cnprog.com'
-EMAIL_HOST_USER = ''
-EMAIL_HOST_PASSWORD = ''
-EMAIL_SUBJECT_PREFIX = '[cnprog.com]'
-EMAIL_HOST='smtp.gmail.com'
-EMAIL_PORT='587'
-EMAIL_USE_TLS=True
-
-#LOCALIZATIONS
-TIME_ZONE = 'Asia/Chongqing Asia/Chungking'
-# LANGUAGE_CODE = 'en-us'
-LANGUAGE_CODE = 'zh-cn'
-SITE_ID = 1
-USE_I18N = True
-
-#OTHER SETTINS
-APP_TITLE = u'CNProg.com 程序员问答社区'
-APP_URL = 'http://www.cnprog.com'
-APP_KEYWORDS = u'技术问答社区,中国程序员,编程技术社区,程序员社区,程序员论坛,程序员wiki,程序员博客'
-APP_DESCRIPTION = u'中国程序员的编程技术问答社区。我们做专业的、可协作编辑的技术问答社区。'
-APP_INTRO = u' <p>CNProg是一个<strong>面向程序员</strong>的可协作编辑的<strong>开放源代码问答社区</strong>。</p><p> 您可以在这里提问各类<strong>程序技术问题</strong> - 问题不分语言和平台。 同时也希望您对力所能及的问题,给予您的宝贵答案。</p>'
-ADMIN_MEDIA_PREFIX = '/admin/media/'
-SECRET_KEY = '$oo^&_m&qwbib=(_4m_n*zn-d=g#s0he5fx9xonnym#8p6yigm'
-# List of callables that know how to import templates from various sources.
-TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.load_template_source',
- 'django.template.loaders.app_directories.load_template_source',
-# 'django.template.loaders.eggs.load_template_source',
-)
-
-MIDDLEWARE_CLASSES = (
- 'django.middleware.gzip.GZipMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.locale.LocaleMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.middleware.transaction.TransactionMiddleware',
- #'django.middleware.sqlprint.SqlPrintingMiddleware',
- 'middleware.pagesize.QuestionsPageSizeMiddleware',
- #'debug_toolbar.middleware.DebugToolbarMiddleware',
-)
-
-TEMPLATE_CONTEXT_PROCESSORS = (
- 'django.core.context_processors.request',
- 'django.core.context_processors.auth',
- 'context.application_settings'
-)
-
-ROOT_URLCONF = 'urls'
-
-TEMPLATE_DIRS = (
- os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
-)
-
-#UPLOAD SETTINGS
-FILE_UPLOAD_TEMP_DIR = os.path.join(os.path.dirname(__file__), 'tmp').replace('\\','/')
-FILE_UPLOAD_HANDLERS = ("django.core.files.uploadhandler.MemoryFileUploadHandler",
- "django.core.files.uploadhandler.TemporaryFileUploadHandler",)
-DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
-# for user upload
-ALLOW_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
-# unit byte
-ALLOW_MAX_FILE_SIZE = 1024 * 1024
-
-INSTALLED_APPS = (
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- 'django.contrib.admin',
- 'django.contrib.humanize',
- 'forum',
- 'django_authopenid',
- 'debug_toolbar' ,
-)
-
-# User settings
-from settings_local import *
-
+# encoding:utf-8
+# Django settings for lanai project.
+import os.path
+
+#DEBUG SETTINGS
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+INTERNAL_IPS = ('127.0.0.1',)
+
+#for OpenID auth
+ugettext = lambda s: s
+LOGIN_URL = '/%s%s' % (ugettext('account/'), ugettext('signin/'))
+
+#EMAIL AND ADMINS
+ADMINS = (
+ ('CNProg team', 'team@cnprog.com'),
+)
+MANAGERS = ADMINS
+
+#email server settings
+SERVER_EMAIL = ''
+DEFAULT_FROM_EMAIL = 'team@cnprog.com'
+EMAIL_HOST_USER = ''
+EMAIL_HOST_PASSWORD = ''
+EMAIL_SUBJECT_PREFIX = '[cnprog.com]'
+EMAIL_HOST='smtp.gmail.com'
+EMAIL_PORT='587'
+EMAIL_USE_TLS=True
+
+#LOCALIZATIONS
+TIME_ZONE = 'Asia/Chongqing Asia/Chungking'
+# LANGUAGE_CODE = 'en-us'
+SITE_ID = 1
+
+#OTHER SETTINGS
+APP_TITLE = u'CNProg.com 程序员问答社区'
+APP_KEYWORDS = u'技术问答社区,中国程序员,编程技术社区,程序员社区,程序员论坛,程序员wiki,程序员博客'
+APP_DESCRIPTION = u'中国程序员的编程技术问答社区。我们做专业的、可协作编辑的技术问答社区。'
+APP_INTRO = u' <p>CNProg是一个<strong>面向程序员</strong>的可协作编辑的<strong>开放源代码问答社区</strong>。</p><p> 您可以在这里提问各类<strong>程序技术问题</strong> - 问题不分语言和平台。 同时也希望您对力所能及的问题,给予您的宝贵答案。</p>'
+APP_COPYRIGHT = 'Copyright CNPROG.COM 2009'
+ADMIN_MEDIA_PREFIX = '/admin/media/'
+SECRET_KEY = '$oo^&_m&qwbib=(_4m_n*zn-d=g#s0he5fx9xonnym#8p6yigm'
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.gzip.GZipMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ #'django.middleware.locale.LocaleMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.middleware.transaction.TransactionMiddleware',
+ #'django.middleware.sqlprint.SqlPrintingMiddleware',
+ #'middleware.pagesize.QuestionsPageSizeMiddleware',
+ #'debug_toolbar.middleware.DebugToolbarMiddleware',
+)
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+ 'django.core.context_processors.request',
+ 'context.auth_processor',
+ 'context.application_settings',
+ #'django.core.context_processors.i18n',
+)
+
+ROOT_URLCONF = 'urls'
+
+TEMPLATE_DIRS = (
+ os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
+)
+
+#UPLOAD SETTINGS
+FILE_UPLOAD_TEMP_DIR = os.path.join(os.path.dirname(__file__), 'tmp').replace('\\','/')
+FILE_UPLOAD_HANDLERS = ("django.core.files.uploadhandler.MemoryFileUploadHandler",
+ "django.core.files.uploadhandler.TemporaryFileUploadHandler",)
+DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
+# for user upload
+ALLOW_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
+# unit byte
+ALLOW_MAX_FILE_SIZE = 1024 * 1024
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.admin',
+ 'django.contrib.humanize',
+ 'forum',
+ 'django_authopenid',
+ 'debug_toolbar' ,
+)
+
+# User settings
+from settings_local import *
+
+USE_I18N = True
+LANGUAGE_CODE = 'en'
+EMAIL_VALIDATION = 'off'
+MIN_USERNAME_LENGTH = 1
+EMAIL_UNIQUE = False
+APP_URL = 'http://server.com' #used by email notif system and RSS
+GOOGLE_SITEMAP_CODE = '55uGNnQVJW8p1bbXeF/Xbh9I7nZBM/wLhRz6N/I1kkA='
+GOOGLE_ANALYTICS_KEY = ''
diff --git a/sql_scripts/update_2009_07_05_EF.sql b/sql_scripts/update_2009_07_05_EF.sql
new file mode 100644
index 00000000..43c7c2f0
--- /dev/null
+++ b/sql_scripts/update_2009_07_05_EF.sql
@@ -0,0 +1,3 @@
+ALTER TABLE auth_user ADD COLUMN email_isvalid TINYINT(1) NOT NULL;
+UPDATE auth_user SET email_isvalid=1;
+ALTER TABLE auth_user ADD COLUMN email_key varchar(32);
diff --git a/templates/404.html b/templates/404.html
index d24edaf0..cd23a36b 100644
--- a/templates/404.html
+++ b/templates/404.html
@@ -1,5 +1,5 @@
-<!-- template 404.html -->
{% extends "base_content.html" %}
+<!-- template 404.html -->
{% load i18n %}
{% block title %}{% spaceless %}404 Error{% endspaceless %}{% endblock %}
{% block forestyle%}
diff --git a/templates/500.html b/templates/500.html
index c99774b3..52dcafae 100644
--- a/templates/500.html
+++ b/templates/500.html
@@ -1,5 +1,5 @@
-<!-- template 500.html -->
{% extends "base_content.html" %}
+<!-- template 500.html -->
{% load i18n %}
{% block title %}{% spaceless %}500 Error{% endspaceless %}{% endblock %}
{% block forejs %}
@@ -19,7 +19,7 @@
</div>
<div id="main-body" class="headNormal">
<div style="padding:5px 0px 10px 0;line-height:25px">
- <h3>{% trans "sorry, system error"</h3>
+ <h3>{% trans "sorry, system error" %}</h3>
<br>
{% trans "system error log is recorded, error will be fixed as soon as possible" %}<br>
{% trans "please report the error to the site administrators if you wish" %}
diff --git a/templates/about.html b/templates/about.html
index 6638060e..eaf0d591 100644
--- a/templates/about.html
+++ b/templates/about.html
@@ -1,71 +1,21 @@
{% extends "base_content.html" %}
+<!-- template about.html -->
+{% load i18n %}
{% load extra_tags %}
{% load humanize %}
-{% block title %}{% spaceless %}关于本站{% endspaceless %}{% endblock %}
+{% block title %}{% spaceless %}{% trans "About" %}{% endspaceless %}{% endblock %}
{% block forejs %}
{% endblock %}
{% block content %}
<div class="headNormal">
-关于本站
+{% trans "About" %}
</div>
<div class="content">
- <p>
- CNProg 是<strong>一个面向中国程序员的免费技术问答社区</strong>。它是一个介于论坛、博客、维基和Digg之间的社区系统,基于Python和Django开发。<br>
- 创办CNProg的灵感来自于国外知名QA社区<a href="http://www.stackoverflow.com">StackOverflow</a>,但是CNProg不仅仅是一个汉化版的SO。<br>
- 我们通过开源社区来维护和更新源代码,你可以访问<strong><a href="http://code.google.com/p/cnprog/">这里</a></strong>获取本站的所有源代码(请注意源代码使用的授权许可)。<br>
- </p>
- <br>
- <p>
- <strong>我们不运作社区,由你来运作。</strong>CNProg 是<strong><a href="http://blog.cnprog.com/2009/01/%e7%94%a8%e6%88%b7%e9%a9%b1%e5%8a%a8%e7%9a%84%e6%8a%80%e6%9c%af%e7%a4%be%e5%8c%ba/">一个由用户来驱动的社区</a></strong>。每个用户不仅是管理员,也是社区功能需求的提出者。<br>
- 社区内容是协作的,系统越信任你,你就在社区获得更多的管理权限,可以开始编辑问题或回答,
- 帮助我们组织问题和答案,帮助需要帮助的广大程序员用户。<br>
- 透明、开放、全民管理的运作模式是本网站的特点,我们希望通过CNProg让用户能够更加容易地寻求帮助,找到问题答案,解决实际的技术问题。
- </p>
- <p>
- 我们关注国内程序员的成长!
- </p>
-
- <br>
- <h3 class="subtitle">你们是谁?</h3>
- <p>
- 我们是一群奋斗在互联网的编程爱好者,和你一样,也是希望自己编写高质量软件的开发人员。
- 我们的团队:
- </p>
- <table cellspacing="5" cellpadding="3">
- <tr>
- <td width="150px"><img src="/content/images/nophoto.png"></td>
- <td width="150px"><img src="/content/images/nophoto.png"></td>
- <td width="150px"><img src="/content/images/nophoto.png"></td>
- <td width="150px"></td>
- </tr>
- <tr>
- <td> </td>
- <td> </td>
- <td> </td>
- <td><a ></a></td>
- </tr>
- <tr>
- <td colspan="4" height="5"></td>
- </tr>
- <tr>
- <td width="150px"><img src="/content/images/nophoto.png"></td>
- <td width="150px"><img src="/content/images/nophoto.png"></td>
- <td width="150px"></td>
- <td width="150px"></td>
- </tr>
- <tr>
- <td> </td>
- <td> </td>
- <td> </td>
- <td> </td>
- </tr>
- </table>
- <p>
- 欢迎访问我们的<strong><a href="http://blog.cnprog.com">团队Blog</a></strong>或查看更多介绍的<a href="/faq" class="big">CNProg FAQ</a>。
- </p>
+ <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>
</div>
{% endblock %}
-
-
-
+<!-- end template about.html -->
diff --git a/templates/answer_edit.html b/templates/answer_edit.html
index 008d9f78..9f0f1bec 100644
--- a/templates/answer_edit.html
+++ b/templates/answer_edit.html
@@ -1,5 +1,5 @@
-<!-- template answer_edit.html -->
{% extends "base.html" %}
+<!-- template answer_edit.html -->
{% load i18n %}
{% block title %}{% spaceless %}{% trans "Edit answer" %}{% endspaceless %}{% endblock %}
{% block forejs %}
diff --git a/templates/answer_edit_tips.html b/templates/answer_edit_tips.html
index 3ab63f70..31f71e34 100644
--- a/templates/answer_edit_tips.html
+++ b/templates/answer_edit_tips.html
@@ -1,54 +1,53 @@
-<!-- template answer_edit_tips.html -->
-{% load i18n %}
-<div class="boxC">
- <p class="subtitle darkred">{% trans "answer tips" %}</p>
- <div>
- <ul class="list-item">
- <li> <b>{% trans "please make your answer relevant to this community" %}</b>
- </li>
- <li>
- {% trans "try to give an answer, rather than engage into a discussion" %}
- </li>
- <li>
- {% trans "please try to provide details" %}
- </li>
- <li>
-{% trans "be clear and concise" %}
- </li>
- </ul>
- <a href="/faq/" target="_blank" title="{% trans "see frequently asked questions" %}" style="float:right;position:relative">faq »</a>
- <br>
- </div>
-</div>
-
-<div class="boxC">
- <p class="subtitle">{% trans "Markdown tips" %}</p>
- <ul class="list-item">
- <li>
- {% trans "*italic* or __italic__" %}
- </li>
- <li>
-{% trans "**bold** or __bold__" %}
- </li>
- <li>
- <b>{% trans "link" %}</b>:[{% trans "text" %}](http://url.com/ "{% trans "title" %}")
-
- </li>
-
+<!-- template answer_edit_tips.html -->
+{% load i18n %}
+<div class="boxC">
+ <p class="subtitle darkred">{% trans "answer tips" %}</p>
+ <div>
+ <ul class="list-item">
+ <li> <b>{% trans "please make your answer relevant to this community" %}</b>
+ </li>
+ <li>
+ {% trans "try to give an answer, rather than engage into a discussion" %}
+ </li>
+ <li>
+ {% trans "please try to provide details" %}
+ </li>
+ <li>
+ {% trans "be clear and concise" %}
+ </li>
+ </ul>
+ <a href="/faq/" target="_blank" title="{% trans "see frequently asked questions" %}" style="float:right;position:relative">faq »</a>
+ <br>
+ </div>
+</div>
+
+<div class="boxC">
+ <p class="subtitle">{% trans "Markdown tips" %}</p>
+ <ul class="list-item">
+ <li>
+ {% trans "*italic* or __italic__" %}
+ </li>
+ <li>
+ {% trans "**bold** or __bold__" %}
+ </li>
+ <li>
+ <b>{% trans "link" %}</b>:[{% trans "text" %}](http://url.com/ "{% trans "title" %}")
+
+ </li>
<li>
- <b>{% trans "image" %}</b>:![alt {% trans "text" %}](/path/img.jpg "{% trans "title" %}")
+ <b>{% trans "image" %}</b>:![alt {% trans "text" %}](/path/img.jpg "{% trans "title" %}")
</li>
<li>
-{% trans "numbered list:" %}
- 1. Foo
- 2. Bar
+ {% trans "numbered list:" %}
+ 1. Foo
+ 2. Bar
</li>
<li>
-{% trans "basic HTML tags are also supported" %}
+ {% trans "basic HTML tags are also supported" %}
</li>
</ul>
<a href="http://en.wikipedia.org/wiki/Markdown" target="_blank" style="float:right;position:relative">{% trans "learn more about Markdown" %} »</a>
- <br>
+ <br>
</div>
-<!-- end template answer_edit_tips.html --> \ No newline at end of file
+<!-- end template answer_edit_tips.html -->
diff --git a/templates/ask.html b/templates/ask.html
index 1b00a701..4aa18dd5 100644
--- a/templates/ask.html
+++ b/templates/ask.html
@@ -1,5 +1,5 @@
-<!-- template ask.html -->
{% extends "base.html" %}
+<!-- template ask.html -->
{% load i18n %}
{% block title %}{% spaceless %}{% trans "Ask a question" %}{% endspaceless %}{% endblock %}
{% block forejs %}
@@ -62,6 +62,19 @@
<div id="main-body" class="ask-body">
<div id="askform">
<form id="fmask" action="" method="post" >
+ {% if not request.user.is_authenticated %}
+ <div class="message">
+ <p>{% trans "login to post question info" %}</p>
+ </div>
+ {% else %}
+ {% ifequal settings.EMAIL_VALIDATION 'on' %}
+ {% if not request.user.email_isvalid %}
+ <div class="message">
+ {% blocktrans with request.user.email as email %}must have valid {{email}} to post{% endblocktrans %}
+ </div>
+ {% endif %}
+ {% endifequal %}
+ {% endif %}
<p class="form-item">
<label for="id_title" ><strong>{{ form.title.label_tag }}:</strong></label> <span class="form-error"></span><br>
{{ form.title }} {{ form.title.errors }}
@@ -92,40 +105,17 @@
<br>
</p>
<p class="form-item">
- <strong>{{ form.tags.label_tag }}:</strong> <span class="form-error"></span><br>
+ <strong>{{ form.tags.label_tag }}:</strong> {% trans "(required)" %} <span class="form-error"></span><br>
{{ form.tags }} {{ form.tags.errors }}
- <div class="title-desc">
- {{ form.tags.help_text }}
- </div>
</p>
- <br>
+ <p class="title-desc">
+ {{ form.tags.help_text }}
+ </p>
{% if not request.user.is_authenticated %}
- <table id="login-box">
- <tr>
- <td style="vertical-align:middle;">
- <strong>{% trans "Use" %} <a href="http://openid.net/" title="{% trans "learn more about OpenID" %}">OpenID</a> {% trans "Login" %}:</strong><br>
- {{ form.openid }}
- <div class="title-desc">
- {% trans "Get your own "%} <a href="https://www.myopenid.com/" target=="_blank">OpenID</a>。
- </div>
- </td>
- <td style="vertical-align:middle; padding: 0px 40px 0px 40px">
- <div style="position: absolute; margin-top:40px; background-color:white; margin-left:-10px; padding:5px;">或</div>
- <div style="width:1px; border-left:solid 1px #999; height:8em; margin:auto;"></div>
- </td>
- <td style="vertical-align:middle;">
- <strong>{% trans "User name" %}:</strong><br>
- {{ form.user }}
- <p>
- <strong>{% trans "Email: (won't be shown to anyone)" %}:</strong><br>
- {{ form.email }}
- </p>
- </td>
- </tr>
- </table>
- {% endif %}
+ <input type="submit" value="{% trans "Login/signup to post your question" %}" class="submit" />
+ {% else %}
<input type="submit" value="{% trans "Ask your question" %}" class="submit" />
- <br><br>
+ {% endif %}
</form>
</div>
</div>
diff --git a/templates/authopenid/changeemail.html b/templates/authopenid/changeemail.html
index 99984b3f..d5acb6c9 100644
--- a/templates/authopenid/changeemail.html
+++ b/templates/authopenid/changeemail.html
@@ -1,35 +1,81 @@
{% extends "base_content.html" %}
{% load i18n %}
{% block content %}
-<div id="main-bar" class="">
- <h3>
- {% trans "Account: change email" %}
- </h3>
-</div>
-<p class="settings-descr">{% blocktrans %}This is where you can change the email address associated with your account. Please keep this email address up to date so we can send you a password-reset email if you request one.{% endblocktrans %}</p>
-{% if form.errors %}
-<p class="errors">{% trans "Please correct errors below:" %}<br />
- {% if form.email.errors %}
- <span class="error">{{ form.email.errors|join:", " }}</span>
- {% endif %}
- {% if form.password.errors %}
- <span class="error">{{ form.password.errors|join:", " }}</span>
- {% endif %}
-</p>
-{% endif %}
+<!-- changeemail.html action_type={{action_type}}-->
+{% ifequal action_type "change" %}
+ <div id="main-bar" class="headNormal">
+ {% trans "Change email" %}
+ </div>
+ <p class="message">
+ {% blocktrans %}change {{email}} info{% endblocktrans %}
+ </p>
+ {% if form.errors %}
+ <p class="errors">{% trans "Please correct errors below:" %}<br />
+ {% if form.email.errors %}
+ <span class="error">{{ form.email.errors|join:", " }}</span>
+ {% endif %}
+ {% if form.password.errors %}
+ <span class="error">{{ form.password.errors|join:", " }}</span>
+ {% endif %}
+ </p>
+ {% endif %}
-{% if msg %}
-<p class="errors">{{ msg }}</p>
-{% endif %}
+ {% if msg %}
+ <p class="errors">{{ msg }}</p>
+ {% endif %}
-<div class="aligned">
- <form action="." method="post" accept-charset="utf-8">
+ <div class="aligned">
+ <form action="." method="post" accept-charset="utf-8">
- <div id="form-row"><label for="id_email">{% trans "Email" %}</label>{{ form.email }}</div>
- <div id="form-row"><label for="id_password">{% trans "Password" %}</label>{{ form.password }}</div>
+ <div class="form-row"><label for="id_email">{% trans "Your new Email" %}</label><br>{{ form.email }}</div>
+ <!--<div class="form-row"><label for="id_password">{% trans "Password" %}</label>{{ form.password }}</div>-->
+ <div class="submit-row">
+ <input class="submit" type="submit" name="change_email" value="{% trans "Change email" %}">
+ <input class="submit" type="submit" name="cancel" value="{% trans "Cancel" %}">
+ </div>
- <p><input type="submit" value="{% trans "Change email" %}"></p>
-
- </form>
- </div>
+ </form>
+ </div>
+{% endifequal %}
+{% ifequal action_type "validate" %}
+ <div id="main-bar" class="headNormal">
+ {% trans "Validate email" %}
+ </div>
+ <p class="message">
+ {% blocktrans %}validate {{email}} info{% endblocktrans %}
+ </p>
+{% endifequal %}
+{% ifequal action_type "keep" %}
+ <div id="main-bar" class="headNormal">
+ {% trans "Email not changed" %}
+ </div>
+ <p class="message">
+ {% blocktrans %}old {{email}} kept{% endblocktrans %}
+ </p>
+{% endifequal %}
+{% ifequal action_type "done_novalidate" %}
+ <div id="main-bar" class="headNormal">
+ {% trans "Email changed" %}
+ </div>
+ <p class="message">
+ {% blocktrans %}your current {{email}} can be used for this{% endblocktrans %}
+ </p>
+{% endifequal %}
+{% ifequal action_type "validation_complete" %}
+ <div id="main-bar" class="headNormal">
+ {% trans "Email verified" %}
+ </div>
+ <p class="message">
+ {% trans "thanks for verifying email" %}
+ </p>
+{% endifequal %}
+{% ifequal action_type "key_not_sent" %}
+ <div id="main-bar" class="headNormal">
+ {% trans "email key not sent" %}
+ </div>
+ <p class="message">
+ {% blocktrans %}email key not sent {{email}} change email here {{change_link}}{% endblocktrans %}
+ </p>
+{% endifequal %}
{% endblock %}
+<!-- end changeemail.html -->
diff --git a/templates/authopenid/changeopenid.html b/templates/authopenid/changeopenid.html
index c1f3d180..9b5a196a 100644
--- a/templates/authopenid/changeopenid.html
+++ b/templates/authopenid/changeopenid.html
@@ -1,4 +1,5 @@
{% extends "base.html" %}
+<!-- changeopenid.html -->
{% load i18n %}
{% block content %}
@@ -31,3 +32,4 @@
</form>
</div>
{% endblock %}
+<!-- end changeopenid.html -->
diff --git a/templates/authopenid/changepw.html b/templates/authopenid/changepw.html
index f3cf4be0..0e90b172 100644
--- a/templates/authopenid/changepw.html
+++ b/templates/authopenid/changepw.html
@@ -1,4 +1,5 @@
{% extends "base.html" %}
+<!-- changepw.html -->
{% load i18n %}
{% block head %}
@@ -31,3 +32,4 @@
</form>
</div>
{% endblock %}
+<!-- end changepw.html -->
diff --git a/templates/authopenid/complete.html b/templates/authopenid/complete.html
index 28c38a04..b9a14e16 100644
--- a/templates/authopenid/complete.html
+++ b/templates/authopenid/complete.html
@@ -1,4 +1,5 @@
-{% extends "base.html" %}
+{% extends "base_content.html" %}
+<!-- complete.html -->
{% load i18n %}
{% block head %}{% endblock %}
{% block title %}{% spaceless %}{% trans "Connect your OpenID with this site" %}{% endspaceless %}{% endblock %}
@@ -7,7 +8,9 @@
{% trans "Connect your OpenID with your account on this site" %}
</div>
<p id="completetxt" >
- <h3>{% trans "Your OpenID is accepted. Please complete this to finish registration." %}</h3>
+ <div class="message">
+ {% blocktrans %}register new {{provider}} account info{% endblocktrans %}
+ </div>
<p style="display:none">{% trans "This account already exists, please use another." %}</p>
</p>
@@ -41,12 +44,9 @@
<div class="login">
<form name="fregister" action="{% url user_register %}" method="POST">
{{ form.next }}
- <fieldset style="padding:10px">
- <legend class="big">{% trans "New account" %}</legend>
- <div class="form-row"><label for="id_username">{% trans "User name (<i>will be shown to others, cannot be modified</i>)" %}</label><br />{{ form1.username }}</div>
- <div class="form-row"><label for="id_email">{% trans "Email (<i>not shared with anyone</i>)" %}</label><br />{{ form1.email }}</div>
- <div class="submit-row"><input type="submit" class="submit" name="bnewaccount" value="{% trans "create account" %}"></div>
- </fieldset>
+ <div class="form-row"><label for="id_username">{% trans "Screen name label" %}</label><br />{{ form1.username }}</div>
+ <div class="form-row"><label for="id_email">{% trans "Email address label" %}</label><br />{{ form1.email }}</div>
+ <div class="submit-row"><input type="submit" class="submit" name="bnewaccount" value="{% trans "create account" %}"></div>
</form>
</div>
<div class="login" style="display:none">
@@ -65,3 +65,4 @@
</form>
</div>
{% endblock %}
+<!-- end complete.html -->
diff --git a/templates/authopenid/confirm_email.txt b/templates/authopenid/confirm_email.txt
index 9af177ed..202db0fc 100644
--- a/templates/authopenid/confirm_email.txt
+++ b/templates/authopenid/confirm_email.txt
@@ -1,12 +1,13 @@
-Thank you for registering.
+{% load i18n %}
+{% trans "Thank you for registering at our Q&A forum!" %}
-Your account details are:
+{% trans "Your account details are:" %}
-Username: {{ username }}
-Password: {{ password }}
-
-
-You may sign in here:
-{{ site_url }}signin/
+{% trans "Username:" %} {{ username }}
+{% trans "Password:" %} {{ password }}
+{% trans "Please sign in here:" %}
+{{ site_url }}{% trans "signin/" %}
+{% blocktrans %}Sincerely,
+Forum Administrator{% endblocktrans %}
diff --git a/templates/authopenid/delete.html b/templates/authopenid/delete.html
index 19e0884a..d39bc962 100644
--- a/templates/authopenid/delete.html
+++ b/templates/authopenid/delete.html
@@ -1,4 +1,5 @@
{% extends "base.html" %}
+<!-- delete.html -->
{% load i18n %}
@@ -36,3 +37,4 @@
</form>
</div>
{% endblock %}
+<!-- end delete.html -->
diff --git a/templates/authopenid/email_validation.txt b/templates/authopenid/email_validation.txt
new file mode 100644
index 00000000..5b166a9b
--- /dev/null
+++ b/templates/authopenid/email_validation.txt
@@ -0,0 +1,15 @@
+{% load i18n %}
+{% trans "Greetings from the Q&A forum" %},
+
+{% trans "To make use of the Forum, please follow the link below:" %}
+
+{{validation_link}}
+
+{% trans "Following the link above will help us verify your email address." %}
+
+{% blocktrans %}If you beleive that this message was sent in mistake -
+no further action is needed. Just ingore this email, we apologize
+for any inconvenience{% endblocktrans %}
+
+{% blocktrans %}Sincerely,
+Forum Administrator{% endblocktrans %}
diff --git a/templates/authopenid/failure.html b/templates/authopenid/failure.html
index 87839ab2..d075d6b0 100644
--- a/templates/authopenid/failure.html
+++ b/templates/authopenid/failure.html
@@ -1,5 +1,6 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
+<!-- failure.html -->
<html>
<head>
<title>OpenID failed</title>
@@ -10,4 +11,4 @@
<p>{{ message|escape }}</p>
</body>
-</html> \ No newline at end of file
+</html><!-- end failure.html -->
diff --git a/templates/authopenid/sendpw.html b/templates/authopenid/sendpw.html
index a9488c4c..ba81dba4 100644
--- a/templates/authopenid/sendpw.html
+++ b/templates/authopenid/sendpw.html
@@ -1,4 +1,5 @@
{% extends "base.html" %}
+<!-- sendpw.html -->
{% load i18n %}
{% block title %}{% spaceless %}{% trans "Send new password" %}{% endspaceless %}{% endblock %}
@@ -32,3 +33,4 @@
<span class="darkred">{% trans "Note: your new password will be activated only after you click the activation link in the email message" %}</span>
</div>
{% endblock %}
+<!-- end sendpw.html -->
diff --git a/templates/authopenid/sendpw_email.txt b/templates/authopenid/sendpw_email.txt
index dec062a8..2024061c 100644
--- a/templates/authopenid/sendpw_email.txt
+++ b/templates/authopenid/sendpw_email.txt
@@ -1,14 +1,14 @@
-Someone has requested to reset your password on {{ site_url }}.
-If this is not you, it is safe to ignore this email.
+{% load i18n %}
+{% blocktrans %}Someone has requested to reset your password on {{ site_url }}.
+If it were not you, it is safe to ignore this email.{% endblocktrans %}
-Your new account details are:
+{% trans "Your new account details are:" %}
-Username: {{ username }}
-New password: {{ password }}
+{% trans "Username:" %} {{ username }}
+{% trans "New password:" %} {{ password }}
-To confirm reset of your password go to this address:
+{% trans "To confirm that you wanted to reset your password please visit:" %}
{{ site_url }}{{ url_confirm }}?key={{ confirm_key }}
-Regards,
-
-
+{% blocktrans %}Sincerely,
+Forum Administrator{% endblocktrans %}
diff --git a/templates/authopenid/settings.html b/templates/authopenid/settings.html
index ffd5dd8f..a373324e 100644
--- a/templates/authopenid/settings.html
+++ b/templates/authopenid/settings.html
@@ -1,4 +1,5 @@
{% extends "base_content.html" %}
+<!-- settings.html -->
{% load i18n %}
{% block head %}
@@ -39,3 +40,4 @@
</dl>
</div>
{% endblock %}
+<!-- end settings.html -->
diff --git a/templates/authopenid/signin.html b/templates/authopenid/signin.html
index aff2f06f..9a1a6780 100644
--- a/templates/authopenid/signin.html
+++ b/templates/authopenid/signin.html
@@ -1,41 +1,119 @@
{% extends "base.html" %}
+<!-- signin.html -->
{% load i18n %}
{% block title %}{% spaceless %}{% trans "User login" %}{% endspaceless %}{% endblock %}
{% block forejs %}
- <script type="text/javascript" src="/content/js/jquery.openid.js?"></script>
+ <!--<script type="text/javascript" src="/content/js/jquery.openid.js?"></script>-->
<script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
- <script type="text/javascript">
+
+ <link rel="stylesheet" type="text/css" media="screen" href="/content/jquery-openid/openid.css"/>
+ <script type="text/javascript" src="/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>
+ </script>-->
{% endblock %}
{% block content %}
<div class="headNormal">
{% trans "User login" %}
</div>
-<div class="login">
- <form name="openid_form" action="{% url user_signin %}" method="post">
- {{ form2.next }}
- <p style="display:none">{% trans "we support two login modes" %}</p>
{% if msg %}
<p class="warning">{{ msg }}</p>
{% endif %}
- <fieldset class="fieldset">
- <legend ><strong>{% trans "Login with your OpenID" %}</strong></legend>
- <div id="openid_choice">
- <p>{% trans "select openid provider" %}</p>
- <div id="openid_btns">
- </div>
- <br><br><br>
- <p>{% trans "verify openid link and login" %}</p>
+ {% 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>
- <p>
- {{ form2.openid_url }} <input id="bsignin" name="bsignin" type="submit" value="{% trans "Login" %}" class="openid-login-submit" />
- </p>
- </fieldset>
- </form>
+ {% endif %}
+ <div style="width:600px;float:left;margin-bottom:5px;">{% trans "Click to sign in through any of these services." %}</div>
+ <form id="openid_form" name="openid_form" class="openid" method="post" action="{% url user_signin %}">
+ <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="direct" title="Google">
+ <div class="logo_box google_box">
+ <img src="/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="/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="/content/jquery-openid/images/aol.gif" alt="icon" /><span>http://openid.aol.com/<strong>username</strong></span>
+ </div>
+ </li>
+ <li class="username first_tiny_li" title="MyOpenID user name">
+ <img src="/content/jquery-openid/images/myopenid.ico" alt="icon" />
+ <span>http://<strong>username</strong>.myopenid.com/</span>
+ </li>
+ <li class="username" title="Flickr user name">
+ <img src="/content/jquery-openid/images/flickr.ico" alt="icon" />
+ <span>http://flickr.com/<strong>username</strong>/</span>
+ </li>
+ <li class="username" title="Technorati user name">
+ <img src="/content/jquery-openid/images/technorati.ico" alt="icon" />
+ <span>http://technorati.com/people/technorati/<strong>username</strong>/</span>
+ </li>
+ <li class="username" title="Wordpress blog name">
+ <img src="/content/jquery-openid/images/wordpress.ico" alt="icon" />
+ <span>http://<strong>username</strong>.wordpress.com</span>
+ </li>
+ <li class="username" title="Blogger blog name">
+ <img src="/content/jquery-openid/images/blogger.ico" alt="icon" />
+ <span>http://<strong>username</strong>.blogspot.com/</span>
+ </li>
+ <li class="username" title="LiveJournal blog name">
+ <img src="/content/jquery-openid/images/livejournal.ico" alt="icon" />
+ <span>http://<strong>username</strong>.livejournal.com</span>
+ </li>
+ <li class="username" title="ClaimID user name">
+ <img src="/content/jquery-openid/images/claimid.ico" alt="icon" />
+ <span>http://claimid.com/<strong>username</strong></span>
+ </li>
+ <li class="username" title="Vidoop user name">
+ <img src="/content/jquery-openid/images/vidoop.ico" alt="icon" />
+ <span>http://<strong>username</strong>.myvidoop.com/</span>
+ </li>
+ <li class="username" title="Verisign user name">
+ <img src="/content/jquery-openid/images/verisign.ico" 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>
+ </form>
+<div class="login">
+ <p style="display:none">{% trans "we support two login modes" %}</p>
<div style="display:none">
<br>
{% if form1.errors %}
@@ -60,7 +138,7 @@
<div class="form-row"><label for="id_password">{% trans "Password" %}:</label><br />{{ form1.password }}</div>
<div class="submit-row"><input type="submit" class="submit" name="blogin" value="{% trans "Login" %}">
<a href="">{% trans "Forgot your password?" %}</a>
- <a href="">{% trans "Create new acccount" %}</a></div>
+ <a href="">{% trans "Create new account" %}</a></div>
</fieldset>
</form>
</div>
@@ -93,3 +171,5 @@
</div>
{% 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 a4460aa3..a65dafdc 100644
--- a/templates/authopenid/signup.html
+++ b/templates/authopenid/signup.html
@@ -1,4 +1,6 @@
{% extends "base.html" %}
+<!--signup.html-->
+{% load i18n %}
{% block title %}{% spaceless %}{% trans "Signup" %}{% endspaceless %}{% endblock %}
{% block content %}
@@ -50,3 +52,4 @@
</form>
</div>
{% endblock %}
+<!--end signup.html-->
diff --git a/templates/badge.html b/templates/badge.html
index d0906918..029ba0d9 100644
--- a/templates/badge.html
+++ b/templates/badge.html
@@ -1,5 +1,5 @@
-<!-- template badge.html -->
{% extends "base_content.html" %}
+<!-- template badge.html -->
{% load i18n %}
{% load extra_tags %}
{% load humanize %}
diff --git a/templates/badges.html b/templates/badges.html
index ef0a5451..69ddc39f 100644
--- a/templates/badges.html
+++ b/templates/badges.html
@@ -1,4 +1,5 @@
{% extends "base.html" %}
+<!-- template badges.html -->
{% load extra_tags %}
{% load humanize %}
{% load i18n %}
@@ -15,9 +16,10 @@
<div class="headlineA">
<span class="headMedals">{% trans "Badges" %}</span>
</div>
-<div id="main-body" style="width:100%">
+<div class="badges" id="main-body" style="width:100%">
<p>
- {% trans "Community gives you awards for your questions, answers and votes. Below is the list of available badges and number of times each type of badge has been awarded." %}
+ {% trans "Community gives you awards for your questions, answers and votes." %}<br>
+ {% trans "Below is the list of available badges and number of times each type of badge has been awarded." %}
</p>
<br>
<div id="medalList">
@@ -43,14 +45,14 @@
{% endblock %}
{% block sidebar %}
-<div class="boxB">
+<div class="boxC">
<h3>{% trans "Community badges" %}</h3>
<div class="body">
<p>
<a style="cursor:default;" title="gold badge: the highest honor and is very rare" class="medal"><span class="badge1">&#9679;</span>&nbsp;{% trans "gold" %}</a>
</p>
<p>
- {% trans "Gold badge is very rare. To obtain it you have to show profound knowledge and ability in addition to actively participating in the community. Gold badge is the highest award in this community." %}
+ {% trans "gold badge description" %}
</p>
<p>
<a style="cursor:default;"
@@ -58,14 +60,14 @@
class="medal"><span class="badge2">&#9679;</span>&nbsp;{% trans "silver" %}</a>
</p>
<p>
- {% trans "Obtaining silver badge requires significant patience. If you got one, you've very significantly contributed to this community" %}
+ {% trans "silver badge description" %}
</p>
<p>
<a style="cursor:default;" title="{% trans "bronze badge: often given as a special honor" %}" class="medal">
<span class="badge3">&#9679;</span>&nbsp;{% trans "bronze" %}</a>
</p>
<p>
- {% trans "If you are active in this community, you will get this medal - still it is a special honor." %}
+ {% trans "bronze badge description" %}
</p>
</div>
</div>
diff --git a/templates/base.html b/templates/base.html
index bf24c840..ae389255 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -4,18 +4,18 @@
{% load i18n %}
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
- <title>{% block title %}{% endblock %} - {{ APP_TITLE }}</title>
+ <title>{% block title %}{% endblock %} - {{ settings.APP_TITLE }}</title>
{% spaceless %}
{% block meta %}{% endblock %}
{% endspaceless %}
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <meta name="verify-v1" content="{{verify_v1_code}}" />
+ <meta name="verify-v1" content="{{settings.GOOGLE_SITEMAP_CODE}}" />
<link rel="shortcut icon" href="/content/images/favicon.ico" >
<link href="/content/style/style.css" rel="stylesheet" type="text/css" />
<script src="http://www.google.com/jsapi"></script>
<script>google.load("jquery", "1.2.6");</script>
<script type="text/javascript">
- var i18nLang = 'en';
+ var i18nLang = '{{settings.LANGUAGE_CODE}}';
</script>
<script type='text/javascript' src='/content/js/com.cnprog.i18n.js'></script>
<script type='text/javascript' src='/content/js/jquery.i18n.js'></script>
@@ -26,9 +26,8 @@
</script>
<script type="text/javascript">
UserVoice.Tab.show({
- //EDIT!!!
- key: 'key',
- host: 'where.uservoice.com',
+ key: 'cnprog',
+ host: 'cnprog.uservoice.com',
forum: 'general',
alignment: 'left', /* 'left', 'right' */
background_color:'#777',
@@ -38,30 +37,31 @@
})
</script>-->
<!-- todo move this to settings -->
- {% with request.user.get_messages as messages%}
{% if messages %}
<style type="text/css">
body { margin-top:2.4em; }
</style>
<script type="text/javascript">
$().ready(function() {
+ $('#validate_email_alert').click(function(){notify.close(true)})
notify.show();
});
</script>
-
{% endif %}
- {% endwith %}
{% block forejs %}
{% endblock %}
</head>
<body>
<div class="notify" style="display:none">
- <span>{% if request.user.get_messages %}
- {% trans "congratulations, community gave you a badge" %}: {% for message in request.user.get_messages %}
- <font class="darkred">{{ message }}</font>, {% endfor %}查看
- <a href="{{ request.user.get_profile_url }}">{% trans "profile" %}</a>{% endif %}</span>
- <a class="close-notify" onclick="notify.close(true)">&times;</a>
+ {% autoescape off %}
+ {% if messages %}
+ {% for message in messages %}
+ <p class="darkred">{{ message }}<p>
+ {% endfor %}
+ {% endif %}
+ {% endautoescape %}
+ <a id="close-notify" onclick="notify.close(true)">&times;</a>
</div>
{% include "header.html" %}
<div id="wrapper">
@@ -89,3 +89,4 @@
{% endblock %}
</body>
</html>
+<!-- end template base.html -->
diff --git a/templates/base_content.html b/templates/base_content.html
index 39627a0e..4ee9675f 100644
--- a/templates/base_content.html
+++ b/templates/base_content.html
@@ -1,10 +1,11 @@
-{% load i18n %}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<!-- base_content.html -->
+{% load i18n %}
<html>
<head>
- <title>{% block title %}{% endblock %} - {{ APP_TITLE }}</title>
+ <title>{% block title %}{% endblock %} - {{ settings.APP_TITLE }}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <meta name="verify-v1" content="55uGNnQVJW8p1bbXeF/Xbh9I7nZBM/wLhRz6N/I1kkA=" />
+ <meta name="verify-v1" content="{{ settings.GOOGLE_SITEMAP_CODE }}" />
<link rel="shortcut icon" href="/content/images/favicon.ico" >
<link href="/content/style/style.css" rel="stylesheet" type="text/css" />
{% spaceless %}
@@ -13,7 +14,7 @@
<script src="http://www.google.com/jsapi"></script>
<script>google.load("jquery", "1.2.6");</script>
<script type="text/javascript">
- var i18nLang = 'en';
+ var i18nLang = '{{ settings.LANGUAGE_CODE }}';
</script>
<script type='text/javascript' src='/content/js/com.cnprog.i18n.js'></script>
<script type='text/javascript' src='/content/js/jquery.i18n.js'></script>
@@ -38,29 +39,32 @@
</script>-->
<!-- todo move this to settings-->
- {% with request.user.get_messages as messages%}
{% if messages %}
<style type="text/css">
body { margin-top:2.4em; }
</style>
<script type="text/javascript">
$().ready(function() {
+ var element = $('#validate_email_alert')
+ element.click(function(){notify.close(true);setTimeout(function(){},1000)})
notify.show();
});
</script>
{% endif %}
- {% endwith %}
{% block forejs %}
{% endblock %}
</head>
<body>
<div class="notify" style="display:none">
- <span>{% if request.user.get_messages %}
- {% trans "congratulations, community gave you a badge" %}:{% for message in request.user.get_messages %}
- <font class="darkred">{{ message }}</font>, {% endfor %}{% trans "see" %}
- <a href="{{ request.user.get_profile_url }}">{% trans "profile" %}</a>{% endif %}</span>
- <a class="close-notify" onclick="notify.close(true)">&times;</a>
+ {% autoescape off %}
+ {% if messages %}
+ {% for message in messages %}
+ <p class="darkred">{{ message }}<p>
+ {% endfor %}
+ {% endif %}
+ {% endautoescape %}
+ <a id="close-notify" onclick="notify.close(true)">&times;</a>
</div>
{% include "header.html" %}
<div id="wrapper">
@@ -82,3 +86,4 @@
{% endblock %}
</body>
</html>
+<!-- end template base_content.html -->
diff --git a/templates/book.html b/templates/book.html
index a58a09f2..dddd13e6 100644
--- a/templates/book.html
+++ b/templates/book.html
@@ -1,5 +1,5 @@
-<!-- template book.html -->
{% extends "base_content.html" %}
+<!-- template book.html -->
{% load i18n %}
{% load extra_tags %}
{% load extra_filters %}
diff --git a/templates/close.html b/templates/close.html
index 32df3e82..d9e73507 100644
--- a/templates/close.html
+++ b/templates/close.html
@@ -1,5 +1,5 @@
-<!-- template close.html -->
{% extends "base_content.html" %}
+<!-- template close.html -->
{% load i18n %}
{% load extra_tags %}
{% load humanize %}
diff --git a/templates/content/jquery-openid/images/aol.gif b/templates/content/jquery-openid/images/aol.gif
new file mode 100644
index 00000000..decc4f12
--- /dev/null
+++ b/templates/content/jquery-openid/images/aol.gif
Binary files differ
diff --git a/templates/content/jquery-openid/images/blogger.ico b/templates/content/jquery-openid/images/blogger.ico
new file mode 100644
index 00000000..1b9730b0
--- /dev/null
+++ b/templates/content/jquery-openid/images/blogger.ico
Binary files differ
diff --git a/templates/content/jquery-openid/images/claimid.ico b/templates/content/jquery-openid/images/claimid.ico
new file mode 100644
index 00000000..2b80f491
--- /dev/null
+++ b/templates/content/jquery-openid/images/claimid.ico
Binary files differ
diff --git a/templates/content/jquery-openid/images/facebook.gif b/templates/content/jquery-openid/images/facebook.gif
new file mode 100644
index 00000000..b997b358
--- /dev/null
+++ b/templates/content/jquery-openid/images/facebook.gif
Binary files differ
diff --git a/templates/content/jquery-openid/images/flickr.ico b/templates/content/jquery-openid/images/flickr.ico
new file mode 100644
index 00000000..11f6e07f
--- /dev/null
+++ b/templates/content/jquery-openid/images/flickr.ico
Binary files differ
diff --git a/templates/content/jquery-openid/images/google.gif b/templates/content/jquery-openid/images/google.gif
new file mode 100644
index 00000000..1b6cd07b
--- /dev/null
+++ b/templates/content/jquery-openid/images/google.gif
Binary files differ
diff --git a/templates/content/jquery-openid/images/livejournal.ico b/templates/content/jquery-openid/images/livejournal.ico
new file mode 100644
index 00000000..f3d21ec5
--- /dev/null
+++ b/templates/content/jquery-openid/images/livejournal.ico
Binary files differ
diff --git a/templates/content/jquery-openid/images/myopenid.ico b/templates/content/jquery-openid/images/myopenid.ico
new file mode 100644
index 00000000..ceb06e6a
--- /dev/null
+++ b/templates/content/jquery-openid/images/myopenid.ico
Binary files differ
diff --git a/templates/content/jquery-openid/images/openid-inputicon.gif b/templates/content/jquery-openid/images/openid-inputicon.gif
new file mode 100644
index 00000000..cde836c8
--- /dev/null
+++ b/templates/content/jquery-openid/images/openid-inputicon.gif
Binary files differ
diff --git a/templates/content/jquery-openid/images/openid.gif b/templates/content/jquery-openid/images/openid.gif
new file mode 100644
index 00000000..c718b0e6
--- /dev/null
+++ b/templates/content/jquery-openid/images/openid.gif
Binary files differ
diff --git a/templates/content/jquery-openid/images/openidico.png b/templates/content/jquery-openid/images/openidico.png
new file mode 100644
index 00000000..ab622669
--- /dev/null
+++ b/templates/content/jquery-openid/images/openidico.png
Binary files differ
diff --git a/templates/content/jquery-openid/images/technorati.ico b/templates/content/jquery-openid/images/technorati.ico
new file mode 100644
index 00000000..fa1083c1
--- /dev/null
+++ b/templates/content/jquery-openid/images/technorati.ico
Binary files differ
diff --git a/templates/content/jquery-openid/images/verisign.ico b/templates/content/jquery-openid/images/verisign.ico
new file mode 100644
index 00000000..3953af93
--- /dev/null
+++ b/templates/content/jquery-openid/images/verisign.ico
Binary files differ
diff --git a/templates/content/jquery-openid/images/vidoop.ico b/templates/content/jquery-openid/images/vidoop.ico
new file mode 100644
index 00000000..bbd9a0d5
--- /dev/null
+++ b/templates/content/jquery-openid/images/vidoop.ico
Binary files differ
diff --git a/templates/content/jquery-openid/images/wordpress.ico b/templates/content/jquery-openid/images/wordpress.ico
new file mode 100644
index 00000000..31b7d2c2
--- /dev/null
+++ b/templates/content/jquery-openid/images/wordpress.ico
Binary files differ
diff --git a/templates/content/jquery-openid/images/yahoo.gif b/templates/content/jquery-openid/images/yahoo.gif
new file mode 100644
index 00000000..42adbfa5
--- /dev/null
+++ b/templates/content/jquery-openid/images/yahoo.gif
Binary files differ
diff --git a/templates/content/jquery-openid/jquery.openid.js b/templates/content/jquery-openid/jquery.openid.js
new file mode 100644
index 00000000..eec29ab2
--- /dev/null
+++ b/templates/content/jquery-openid/jquery.openid.js
@@ -0,0 +1,92 @@
+//jQuery OpenID Plugin 1.1 Copyright 2009 Jarrett Vance http://jvance.com/pages/jQueryOpenIdPlugin.xhtml
+$.fn.openid = function() {
+ var $this = $(this);
+
+ //name input value - needed for name based OpenID
+ var $usr = $this.find('input[name=openid_username]');
+
+ //final url input value
+ var $id = $this.find('input[name=openid_url]');
+
+ //beginning and end of name OpenID url (name being the middle)
+ var $front = $this.find('p:has(input[name=openid_username])>span:eq(0)');
+ var $end = $this.find('p:has(input[name=openid_username])>span:eq(1)');
+
+ //needed for special effects only
+ var $usrfs = $this.find('fieldset:has(input[name=openid_username])');
+ var $idfs = $this.find('fieldset:has(input[name=openid_url])');
+
+ var submitusr = function() {
+ if ($usr.val().length < 1) {
+ $usr.focus();
+ return false;
+ }
+ $id.val($front.text() + $usr.val() + $end.text());
+ return true;
+ };
+
+ var submitid = function() {
+ if ($id.val().length < 1) {
+ $id.focus();
+ return false;
+ }
+ return true;
+
+ };
+ var direct = function() {
+ var $li = $(this);
+ $li.parent().find('li').removeClass('highlight');
+ $li.addClass('highlight');
+ $usrfs.fadeOut('slow');
+ $idfs.fadeOut('slow');
+ $id.val($this.find("li.highlight span").text());
+ setTimeout(function(){$('#bsignin').click()},1000);
+ return false;
+ };
+
+ var openid = function() {
+ var $li = $(this);
+ $li.parent().find('li').removeClass('highlight');
+ $li.addClass('highlight');
+ $usrfs.hide();
+ $idfs.show();
+ $id.focus();
+ $this.unbind('submit').submit(submitid);
+ return false;
+ };
+
+ var username = function() {
+ var $li = $(this);
+ $li.parent().find('li').removeClass('highlight');
+ $li.addClass('highlight');
+ $idfs.hide();
+ $usrfs.show();
+ $this.find('#enter_your_what').text($li.attr("title"));
+ $front.text($li.find("span").text().split("username")[0]);
+ $end.text("").text($li.find("span").text().split("username")[1]);
+ $id.focus();
+ $this.unbind('submit').submit(submitusr);
+ return false;
+ };
+
+ $this.find('li.direct').click(direct);
+ $this.find('li.openid').click(openid);
+ $this.find('li.username').click(username);
+ $id.keypress(function(e) {
+ if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
+ return submitid();
+ }
+ });
+ $usr.keypress(function(e) {
+ if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
+ return submitusr();
+ }
+ });
+ $this.find('li span').hide();
+ $this.find('li').css('line-height', 0).css('cursor', 'pointer');
+ $this.find('li:eq(0)').click();
+
+ return this;
+};
+// submitting next=%2F&openid_username=&openid_url=http%3A%2F%2Fyahoo.com%2F
+// submitting next=%2F&openid_username=&openid_url=http%3A%2F%2Fyahoo.com%2F
diff --git a/templates/content/jquery-openid/openid.css b/templates/content/jquery-openid/openid.css
new file mode 100644
index 00000000..e53c1823
--- /dev/null
+++ b/templates/content/jquery-openid/openid.css
@@ -0,0 +1,33 @@
+fieldset {border-style:none;}
+img {border-style:none;}
+
+.logo_box {width:90px;height:40px;background:white;border:1px solid #dddddd;}
+.openid_box img {margin-top:6px;}
+.aol_box img {margin-top:6px;}
+.yahoo_box img {margin-top:13px;}
+.google_box img {margin-top:6px;}
+
+form.openid ul{ margin:0;padding:0;text-align:center; list-style-type:none; display:block;}
+form.openid ul li {float:left; padding:4px;}
+form.openid ul li span {padding:0 1em 0 3px}
+form.openid ul li.first_tiny_li {clear:left;}
+form.openid fieldset {clear:both;padding:10px 0px 0px 0px;}
+form.openid div+fieldset {display:none}
+form.openid label {display:block; font-weight:bold; margin-bottom:.5em}
+input[name=openid_username] {width:8em}
+input[name=openid_identifier] {width:18em}
+form.openid ul li.highlight { -moz-border-radius:4px; -webkit-border-radius:4px; background-color: #FD6}
+form.openid fieldset div {-moz-border-radius:4px; -webkit-border-radius:4px;
+ background: #DCDCDC;
+ padding:10px;display:inline-block}
+form.openid fieldset div p {padding:0px;margin:0px;}
+/*form.openid input[type='submit'] {margin-left:1em;}*/
+#openid_username {background:#ffffa0;}
+#openid_url {background:#ffffa0;}
+
+.openid_logo{color:#F7931E;padding:6px 0px 8px 28px;
+background: url(images/openidico.png) no-repeat;
+}
+
+#openid_login {float:left; width:30%; margin:2em 1em; text-align:center}
+#openid_login div{margin-top:0.5em}
diff --git a/templates/content/js/com.cnprog.editor.js b/templates/content/js/com.cnprog.editor.js
index 289d9866..6cfa2c74 100644
--- a/templates/content/js/com.cnprog.editor.js
+++ b/templates/content/js/com.cnprog.editor.js
@@ -1,68 +1,68 @@
-/*
- jQuery TextAreaResizer plugin
- Created on 17th January 2008 by Ryan O'Dell
- Version 1.0.4
-*/(function($){var textarea,staticOffset;var iLastMousePos=0;var iMin=32;var grip;$.fn.TextAreaResizer=function(){return this.each(function(){textarea=$(this).addClass('processed'),staticOffset=null;$(this).wrap('<div class="resizable-textarea"><span></span></div>').parent().append($('<div class="grippie"></div>').bind("mousedown",{el:this},startDrag));var grippie=$('div.grippie',$(this).parent())[0];grippie.style.marginRight=(grippie.offsetWidth-$(this)[0].offsetWidth)+'px'})};function startDrag(e){textarea=$(e.data.el);textarea.blur();iLastMousePos=mousePosition(e).y;staticOffset=textarea.height()-iLastMousePos;textarea.css('opacity',0.25);$(document).mousemove(performDrag).mouseup(endDrag);return false}function performDrag(e){var iThisMousePos=mousePosition(e).y;var iMousePos=staticOffset+iThisMousePos;if(iLastMousePos>=(iThisMousePos)){iMousePos-=5}iLastMousePos=iThisMousePos;iMousePos=Math.max(iMin,iMousePos);textarea.height(iMousePos+'px');if(iMousePos<iMin){endDrag(e)}return false}function endDrag(e){$(document).unbind('mousemove',performDrag).unbind('mouseup',endDrag);textarea.css('opacity',1);textarea.focus();textarea=null;staticOffset=null;iLastMousePos=0}function mousePosition(e){return{x:e.clientX+document.documentElement.scrollLeft,y:e.clientY+document.documentElement.scrollTop}}})(jQuery);
-/*
- * Autocomplete - jQuery plugin 1.0.2
- * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
- */;(function($){$.fn.extend({autocomplete:function(urlOrData,options){var isUrl=typeof urlOrData=="string";options=$.extend({},$.Autocompleter.defaults,{url:isUrl?urlOrData:null,data:isUrl?null:urlOrData,delay:isUrl?$.Autocompleter.defaults.delay:10,max:options&&!options.scroll?10:150},options);options.highlight=options.highlight||function(value){return value;};options.formatMatch=options.formatMatch||options.formatItem;return this.each(function(){new $.Autocompleter(this,options);});},result:function(handler){return this.bind("result",handler);},search:function(handler){return this.trigger("search",[handler]);},flushCache:function(){return this.trigger("flushCache");},setOptions:function(options){return this.trigger("setOptions",[options]);},unautocomplete:function(){return this.trigger("unautocomplete");}});$.Autocompleter=function(input,options){var KEY={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8};var $input=$(input).attr("autocomplete","off").addClass(options.inputClass);var timeout;var previousValue="";var cache=$.Autocompleter.Cache(options);var hasFocus=0;var lastKeyPressCode;var config={mouseDownOnSelect:false};var select=$.Autocompleter.Select(options,input,selectCurrent,config);var blockSubmit;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){lastKeyPressCode=event.keyCode;switch(event.keyCode){case KEY.UP:event.preventDefault();if(select.visible()){select.prev();}else{onChange(0,true);}break;case KEY.DOWN:event.preventDefault();if(select.visible()){select.next();}else{onChange(0,true);}break;case KEY.PAGEUP:event.preventDefault();if(select.visible()){select.pageUp();}else{onChange(0,true);}break;case KEY.PAGEDOWN:event.preventDefault();if(select.visible()){select.pageDown();}else{onChange(0,true);}break;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;}break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){var fn=(arguments.length>1)?arguments[1]:null;function findValueCallback(q,data){var result;if(data&&data.length){for(var i=0;i<data.length;i++){if(data[i].result.toLowerCase()==q.toLowerCase()){result=data[i];break;}}}if(typeof fn=="function")fn(result);else $input.trigger("result",result&&[result.data,result.value]);}$.each(trimWords($input.val()),function(i,value){request(value,findValueCallback,findValueCallback);});}).bind("flushCache",function(){cache.flush();}).bind("setOptions",function(){$.extend(options,arguments[1]);if("data"in arguments[1])cache.populate();}).bind("unautocomplete",function(){select.unbind();$input.unbind();$(input.form).unbind(".autocomplete");});function selectCurrent(){var selected=select.selected();if(!selected)return false;var v=selected.result;previousValue=v;if(options.multiple){var words=trimWords($input.val());if(words.length>1){v=words.slice(0,words.length-1).join(options.multipleSeparator)+options.multipleSeparator+v;}v+=options.multipleSeparator;}$input.val(v);hideResultsNow();$input.trigger("result",[selected.data,selected.value]);return true;}function onChange(crap,skipPrevCheck){if(lastKeyPressCode==KEY.DEL){select.hide();return;}var currentValue=$input.val();if(!skipPrevCheck&&currentValue==previousValue)return;previousValue=currentValue;currentValue=lastWord(currentValue);if(currentValue.length>=options.minChars){$input.addClass(options.loadingClass);if(!options.matchCase)currentValue=currentValue.toLowerCase();request(currentValue,receiveData,hideResultsNow);}else{stopLoading();select.hide();}};function trimWords(value){if(!value){return[""];}var words=value.split(options.multipleSeparator);var result=[];$.each(words,function(i,value){if($.trim(value))result[i]=$.trim(value);});return result;}function lastWord(value){if(!options.multiple)return value;var words=trimWords(value);return words[words.length-1];}function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$.Autocompleter.Selection(input,previousValue.length,previousValue.length+sValue.length);}};function hideResults(){clearTimeout(timeout);timeout=setTimeout(hideResultsNow,200);};function hideResultsNow(){var wasVisible=select.visible();select.hide();clearTimeout(timeout);stopLoading();if(options.mustMatch){$input.search(function(result){if(!result){if(options.multiple){var words=trimWords($input.val()).slice(0,-1);$input.val(words.join(options.multipleSeparator)+(words.length?options.multipleSeparator:""));}else
-$input.val("");}});}if(wasVisible)$.Autocompleter.Selection(input,input.value.length,input.value.length);};function receiveData(q,data){if(data&&data.length&&hasFocus){stopLoading();select.display(data,q);autoFill(q,data[0].value);select.show();}else{hideResultsNow();}};function request(term,success,failure){if(!options.matchCase)term=term.toLowerCase();var data=cache.load(term);if(data&&data.length){success(term,data);}else if((typeof options.url=="string")&&(options.url.length>0)){var extraParams={timestamp:+new Date()};$.each(options.extraParams,function(key,param){extraParams[key]=typeof param=="function"?param():param;});$.ajax({mode:"abort",port:"autocomplete"+input.name,dataType:options.dataType,url:options.url,data:$.extend({q:lastWord(term),limit:options.max},extraParams),success:function(data){var parsed=options.parse&&options.parse(data)||parse(data);cache.add(term,parsed);success(term,parsed);}});}else{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i<rows.length;i++){var row=$.trim(rows[i]);if(row){row=row.split("|");parsed[parsed.length]={data:row,value:row[0],result:options.formatResult&&options.formatResult(row,row[0])||row[0]};}}return parsed;};function stopLoading(){$input.removeClass(options.loadingClass);};};$.Autocompleter.defaults={inputClass:"ac_input",resultsClass:"ac_results",loadingClass:"ac_loading",minChars:1,delay:400,matchCase:false,matchSubset:true,matchContains:false,cacheLength:10,max:100,mustMatch:false,extraParams:{},selectFirst:true,formatItem:function(row){return row[0];},formatMatch:null,autoFill:false,width:0,multiple:false,multipleSeparator:", ",highlight:function(value,term){return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"<strong>$1</strong>");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase)s=s.toLowerCase();var i=s.indexOf(sub);if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();}if(!data[q]){length++;}data[q]=value;}function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i<ol;i++){var rawValue=options.data[i];rawValue=(typeof rawValue=="string")?[rawValue]:rawValue;var value=options.formatMatch(rawValue,i+1,options.data.length);if(value===false)continue;var firstChar=value.charAt(0).toLowerCase();if(!stMatchSets[firstChar])stMatchSets[firstChar]=[];var row={value:value,data:rawValue,result:options.formatResult&&options.formatResult(rawValue)||value};stMatchSets[firstChar].push(row);if(nullData++<options.max){stMatchSets[""].push(row);}};$.each(stMatchSets,function(i,value){options.cacheLength++;add(i,value);});}setTimeout(populate,25);function flush(){data={};length=0;}return{flush:flush,add:add,populate:populate,load:function(q){if(!options.cacheLength||!length)return null;if(!options.url&&options.matchContains){var csub=[];for(var k in data){if(k.length>0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}}return csub;}else
-if(data[q]){return data[q];}else
-if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}}return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit)return;element=$("<div/>").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("<ul/>").appendTo(element).mouseover(function(event){if(target(event).nodeName&&target(event).nodeName.toUpperCase()=='LI'){active=$("li",list).removeClass(CLASSES.ACTIVE).index(target(event));$(target(event)).addClass(CLASSES.ACTIVE);}}).click(function(event){$(target(event)).addClass(CLASSES.ACTIVE);select();input.focus();return false;}).mousedown(function(){config.mouseDownOnSelect=true;}).mouseup(function(){config.mouseDownOnSelect=false;});if(options.width>0)element.css("width",options.width);needsInit=false;}function target(event){var element=event.target;while(element&&element.tagName!="LI")element=element.parentNode;if(!element)return[];return element;}function moveSelect(step){listItems.slice(active,active+1).removeClass(CLASSES.ACTIVE);movePosition(step);var activeItem=listItems.slice(active,active+1).addClass(CLASSES.ACTIVE);if(options.scroll){var offset=0;listItems.slice(0,active).each(function(){offset+=this.offsetHeight;});if((offset+activeItem[0].offsetHeight-list.scrollTop())>list[0].clientHeight){list.scrollTop(offset+activeItem[0].offsetHeight-list.innerHeight());}else if(offset<list.scrollTop()){list.scrollTop(offset);}}};function movePosition(step){active+=step;if(active<0){active=listItems.size()-1;}else if(active>=listItems.size()){active=0;}}function limitNumberOfItems(available){return options.max&&options.max<available?options.max:available;}function fillList(){list.empty();var max=limitNumberOfItems(data.length);for(var i=0;i<max;i++){if(!data[i])continue;var formatted=options.formatItem(data[i].data,i+1,max,data[i].value,term);if(formatted===false)continue;var li=$("<li/>").html(options.highlight(formatted,term)).addClass(i%2==0?"ac_even":"ac_odd").appendTo(list)[0];$.data(li,"ac_data",data[i]);}listItems=list.find("li");if(options.selectFirst){listItems.slice(0,1).addClass(CLASSES.ACTIVE);active=0;}if($.fn.bgiframe)list.bgiframe();}return{display:function(d,q){init();data=d;term=q;fillList();},next:function(){moveSelect(1);},prev:function(){moveSelect(-1);},pageUp:function(){if(active!=0&&active-8<0){moveSelect(-active);}else{moveSelect(-8);}},pageDown:function(){if(active!=listItems.size()-1&&active+8>listItems.size()){moveSelect(listItems.size()-1-active);}else{moveSelect(8);}},hide:function(){element&&element.hide();listItems&&listItems.removeClass(CLASSES.ACTIVE);active=-1;},visible:function(){return element&&element.is(":visible");},current:function(){return this.visible()&&(listItems.filter("."+CLASSES.ACTIVE)[0]||options.selectFirst&&listItems[0]);},show:function(){var offset=$(input).offset();element.css({width:typeof options.width=="string"||options.width>0?options.width:$(input).width(),top:offset.top+input.offsetHeight,left:offset.left}).show();if(options.scroll){list.scrollTop(0);list.css({maxHeight:options.scrollHeight,overflow:'auto'});if($.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var listHeight=0;listItems.each(function(){listHeight+=this.offsetHeight;});var scrollbarsVisible=listHeight>options.scrollHeight;list.css('height',scrollbarsVisible?options.scrollHeight:listHeight);if(!scrollbarsVisible){listItems.width(list.width()-parseInt(listItems.css("padding-left"))-parseInt(listItems.css("padding-right")));}}}},selected:function(){var selected=listItems&&listItems.filter("."+CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);return selected&&selected.length&&$.data(selected[0],"ac_data");},emptyList:function(){list&&list.empty();},unbind:function(){element&&element.remove();}};};$.Autocompleter.Selection=function(field,start,end){if(field.createTextRange){var selRange=field.createTextRange();selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}else if(field.setSelectionRange){field.setSelectionRange(start,end);}else{if(field.selectionStart){field.selectionStart=start;field.selectionEnd=end;}}field.focus();};})(jQuery);
-/*
- * TypeWatch 2.0 - Original by Denny Ferrassoli / Refactored by Charles Christolini
- * Copyright(c) 2007 Denny Ferrassoli - DennyDotNet.com
- * Coprright(c) 2008 Charles Christolini - BinaryPie.com
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
-*/(function(jQuery){jQuery.fn.typeWatch=function(o){var options=jQuery.extend({wait:750,callback:function(){},highlight:true,captureLength:2},o);function checkElement(timer,override){var elTxt=jQuery(timer.el).val();if((elTxt.length>options.captureLength&&elTxt.toUpperCase()!=timer.text)||(override&&elTxt.length>options.captureLength)){timer.text=elTxt.toUpperCase();timer.cb(elTxt)}};function watchElement(elem){if(elem.type.toUpperCase()=="TEXT"||elem.nodeName.toUpperCase()=="TEXTAREA"){var timer={timer:null,text:jQuery(elem).val().toUpperCase(),cb:options.callback,el:elem,wait:options.wait};if(options.highlight){jQuery(elem).focus(function(){this.select()})}var startWatch=function(evt){var timerWait=timer.wait;var overrideBool=false;if(evt.keyCode==13&&this.type.toUpperCase()=="TEXT"){timerWait=1;overrideBool=true}var timerCallbackFx=function(){checkElement(timer,overrideBool)};clearTimeout(timer.timer);timer.timer=setTimeout(timerCallbackFx,timerWait)};jQuery(elem).keydown(startWatch)}};return this.each(function(index){watchElement(this)})}})(jQuery);
-/*
-Ajax upload
-*/jQuery.extend({createUploadIframe:function(d,b){var a="jUploadFrame"+d;if(window.ActiveXObject){var c=document.createElement('<iframe id="'+a+'" name="'+a+'" />');if(typeof b=="boolean"){c.src="javascript:false"}else{if(typeof b=="string"){c.src=b}}}else{var c=document.createElement("iframe");c.id=a;c.name=a}c.style.position="absolute";c.style.top="-1000px";c.style.left="-1000px";document.body.appendChild(c);return c},createUploadForm:function(g,b){var e="jUploadForm"+g;var a="jUploadFile"+g;var d=$('<form action="" method="POST" name="'+e+'" id="'+e+'" enctype="multipart/form-data"></form>');var c=$("#"+b);var f=$(c).clone();$(c).attr("id",a);$(c).before(f);$(c).appendTo(d);$(d).css("position","absolute");$(d).css("top","-1200px");$(d).css("left","-1200px");$(d).appendTo("body");return d},ajaxFileUpload:function(k){k=jQuery.extend({},jQuery.ajaxSettings,k);var a=new Date().getTime();var b=jQuery.createUploadForm(a,k.fileElementId);var i=jQuery.createUploadIframe(a,k.secureuri);var h="jUploadFrame"+a;var j="jUploadForm"+a;if(k.global&&!jQuery.active++){jQuery.event.trigger("ajaxStart")}var c=false;var f={};if(k.global){jQuery.event.trigger("ajaxSend",[f,k])}var d=function(l){var p=document.getElementById(h);try{if(p.contentWindow){f.responseText=p.contentWindow.document.body?p.contentWindow.document.body.innerText:null;f.responseXML=p.contentWindow.document.XMLDocument?p.contentWindow.document.XMLDocument:p.contentWindow.document}else{if(p.contentDocument){f.responseText=p.contentDocument.document.body?p.contentDocument.document.body.textContent||document.body.innerText:null;f.responseXML=p.contentDocument.document.XMLDocument?p.contentDocument.document.XMLDocument:p.contentDocument.document}}}catch(o){jQuery.handleError(k,f,null,o)}if(f||l=="timeout"){c=true;var m;try{m=l!="timeout"?"success":"error";if(m!="error"){var n=jQuery.uploadHttpData(f,k.dataType);if(k.success){k.success(n,m)}if(k.global){jQuery.event.trigger("ajaxSuccess",[f,k])}}else{jQuery.handleError(k,f,m)}}catch(o){m="error";jQuery.handleError(k,f,m,o)}if(k.global){jQuery.event.trigger("ajaxComplete",[f,k])}if(k.global&&!--jQuery.active){jQuery.event.trigger("ajaxStop")}if(k.complete){k.complete(f,m)}jQuery(p).unbind();setTimeout(function(){try{$(p).remove();$(b).remove()}catch(q){jQuery.handleError(k,f,null,q)}},100);f=null}};if(k.timeout>0){setTimeout(function(){if(!c){d("timeout")}},k.timeout)}try{var b=$("#"+j);$(b).attr("action",k.url);$(b).attr("method","POST");$(b).attr("target",h);if(b.encoding){b.encoding="multipart/form-data"}else{b.enctype="multipart/form-data"}$(b).submit()}catch(g){jQuery.handleError(k,f,null,g)}if(window.attachEvent){document.getElementById(h).attachEvent("onload",d)}else{document.getElementById(h).addEventListener("load",d,false)}return{abort:function(){}}},uploadHttpData:function(r,type){var data=!type;data=type=="xml"||data?r.responseXML:r.responseText;if(type=="script"){jQuery.globalEval(data)}if(type=="json"){eval("data = "+data)}if(type=="html"){jQuery("<div>").html(data).evalScripts()}return data}});
-/*Upload call*/
-function ajaxFileUpload(imageUrl)
-{
- $("#loading").ajaxStart(function(){
- $(this).show();
- }).ajaxComplete(function(){
- $(this).hide();
- });
-
- $("#upload").ajaxStart(function(){
- $(this).hide();
- }).ajaxComplete(function(){
- $(this).show();
- });
-
- $.ajaxFileUpload
- (
- {
- url:'/upload/',
- secureuri:false,
- fileElementId:'file-upload',
- dataType: 'xml',
- success: function (data, status)
- {
- var fileURL = $(data).find('file_url').text();
- var error = $(data).find('error').text();
- if(error != ''){
- alert(error);
- }else{
- imageUrl.attr('value', fileURL);
- }
-
- },
- error: function (data, status, e)
- {
- alert(e);
- }
- }
- )
-
- return false;
+/*
+ jQuery TextAreaResizer plugin
+ Created on 17th January 2008 by Ryan O'Dell
+ Version 1.0.4
+*/(function($){var textarea,staticOffset;var iLastMousePos=0;var iMin=32;var grip;$.fn.TextAreaResizer=function(){return this.each(function(){textarea=$(this).addClass('processed'),staticOffset=null;$(this).wrap('<div class="resizable-textarea"><span></span></div>').parent().append($('<div class="grippie"></div>').bind("mousedown",{el:this},startDrag));var grippie=$('div.grippie',$(this).parent())[0];grippie.style.marginRight=(grippie.offsetWidth-$(this)[0].offsetWidth)+'px'})};function startDrag(e){textarea=$(e.data.el);textarea.blur();iLastMousePos=mousePosition(e).y;staticOffset=textarea.height()-iLastMousePos;textarea.css('opacity',0.25);$(document).mousemove(performDrag).mouseup(endDrag);return false}function performDrag(e){var iThisMousePos=mousePosition(e).y;var iMousePos=staticOffset+iThisMousePos;if(iLastMousePos>=(iThisMousePos)){iMousePos-=5}iLastMousePos=iThisMousePos;iMousePos=Math.max(iMin,iMousePos);textarea.height(iMousePos+'px');if(iMousePos<iMin){endDrag(e)}return false}function endDrag(e){$(document).unbind('mousemove',performDrag).unbind('mouseup',endDrag);textarea.css('opacity',1);textarea.focus();textarea=null;staticOffset=null;iLastMousePos=0}function mousePosition(e){return{x:e.clientX+document.documentElement.scrollLeft,y:e.clientY+document.documentElement.scrollTop}}})(jQuery);
+/*
+ * Autocomplete - jQuery plugin 1.0.2
+ * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ */;(function($){$.fn.extend({autocomplete:function(urlOrData,options){var isUrl=typeof urlOrData=="string";options=$.extend({},$.Autocompleter.defaults,{url:isUrl?urlOrData:null,data:isUrl?null:urlOrData,delay:isUrl?$.Autocompleter.defaults.delay:10,max:options&&!options.scroll?10:150},options);options.highlight=options.highlight||function(value){return value;};options.formatMatch=options.formatMatch||options.formatItem;return this.each(function(){new $.Autocompleter(this,options);});},result:function(handler){return this.bind("result",handler);},search:function(handler){return this.trigger("search",[handler]);},flushCache:function(){return this.trigger("flushCache");},setOptions:function(options){return this.trigger("setOptions",[options]);},unautocomplete:function(){return this.trigger("unautocomplete");}});$.Autocompleter=function(input,options){var KEY={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8};var $input=$(input).attr("autocomplete","off").addClass(options.inputClass);var timeout;var previousValue="";var cache=$.Autocompleter.Cache(options);var hasFocus=0;var lastKeyPressCode;var config={mouseDownOnSelect:false};var select=$.Autocompleter.Select(options,input,selectCurrent,config);var blockSubmit;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){lastKeyPressCode=event.keyCode;switch(event.keyCode){case KEY.UP:event.preventDefault();if(select.visible()){select.prev();}else{onChange(0,true);}break;case KEY.DOWN:event.preventDefault();if(select.visible()){select.next();}else{onChange(0,true);}break;case KEY.PAGEUP:event.preventDefault();if(select.visible()){select.pageUp();}else{onChange(0,true);}break;case KEY.PAGEDOWN:event.preventDefault();if(select.visible()){select.pageDown();}else{onChange(0,true);}break;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;}break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){var fn=(arguments.length>1)?arguments[1]:null;function findValueCallback(q,data){var result;if(data&&data.length){for(var i=0;i<data.length;i++){if(data[i].result.toLowerCase()==q.toLowerCase()){result=data[i];break;}}}if(typeof fn=="function")fn(result);else $input.trigger("result",result&&[result.data,result.value]);}$.each(trimWords($input.val()),function(i,value){request(value,findValueCallback,findValueCallback);});}).bind("flushCache",function(){cache.flush();}).bind("setOptions",function(){$.extend(options,arguments[1]);if("data"in arguments[1])cache.populate();}).bind("unautocomplete",function(){select.unbind();$input.unbind();$(input.form).unbind(".autocomplete");});function selectCurrent(){var selected=select.selected();if(!selected)return false;var v=selected.result;previousValue=v;if(options.multiple){var words=trimWords($input.val());if(words.length>1){v=words.slice(0,words.length-1).join(options.multipleSeparator)+options.multipleSeparator+v;}v+=options.multipleSeparator;}$input.val(v);hideResultsNow();$input.trigger("result",[selected.data,selected.value]);return true;}function onChange(crap,skipPrevCheck){if(lastKeyPressCode==KEY.DEL){select.hide();return;}var currentValue=$input.val();if(!skipPrevCheck&&currentValue==previousValue)return;previousValue=currentValue;currentValue=lastWord(currentValue);if(currentValue.length>=options.minChars){$input.addClass(options.loadingClass);if(!options.matchCase)currentValue=currentValue.toLowerCase();request(currentValue,receiveData,hideResultsNow);}else{stopLoading();select.hide();}};function trimWords(value){if(!value){return[""];}var words=value.split(options.multipleSeparator);var result=[];$.each(words,function(i,value){if($.trim(value))result[i]=$.trim(value);});return result;}function lastWord(value){if(!options.multiple)return value;var words=trimWords(value);return words[words.length-1];}function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$.Autocompleter.Selection(input,previousValue.length,previousValue.length+sValue.length);}};function hideResults(){clearTimeout(timeout);timeout=setTimeout(hideResultsNow,200);};function hideResultsNow(){var wasVisible=select.visible();select.hide();clearTimeout(timeout);stopLoading();if(options.mustMatch){$input.search(function(result){if(!result){if(options.multiple){var words=trimWords($input.val()).slice(0,-1);$input.val(words.join(options.multipleSeparator)+(words.length?options.multipleSeparator:""));}else
+$input.val("");}});}if(wasVisible)$.Autocompleter.Selection(input,input.value.length,input.value.length);};function receiveData(q,data){if(data&&data.length&&hasFocus){stopLoading();select.display(data,q);autoFill(q,data[0].value);select.show();}else{hideResultsNow();}};function request(term,success,failure){if(!options.matchCase)term=term.toLowerCase();var data=cache.load(term);if(data&&data.length){success(term,data);}else if((typeof options.url=="string")&&(options.url.length>0)){var extraParams={timestamp:+new Date()};$.each(options.extraParams,function(key,param){extraParams[key]=typeof param=="function"?param():param;});$.ajax({mode:"abort",port:"autocomplete"+input.name,dataType:options.dataType,url:options.url,data:$.extend({q:lastWord(term),limit:options.max},extraParams),success:function(data){var parsed=options.parse&&options.parse(data)||parse(data);cache.add(term,parsed);success(term,parsed);}});}else{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i<rows.length;i++){var row=$.trim(rows[i]);if(row){row=row.split("|");parsed[parsed.length]={data:row,value:row[0],result:options.formatResult&&options.formatResult(row,row[0])||row[0]};}}return parsed;};function stopLoading(){$input.removeClass(options.loadingClass);};};$.Autocompleter.defaults={inputClass:"ac_input",resultsClass:"ac_results",loadingClass:"ac_loading",minChars:1,delay:400,matchCase:false,matchSubset:true,matchContains:false,cacheLength:10,max:100,mustMatch:false,extraParams:{},selectFirst:true,formatItem:function(row){return row[0];},formatMatch:null,autoFill:false,width:0,multiple:false,multipleSeparator:", ",highlight:function(value,term){return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"<strong>$1</strong>");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase)s=s.toLowerCase();var i=s.indexOf(sub);if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();}if(!data[q]){length++;}data[q]=value;}function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i<ol;i++){var rawValue=options.data[i];rawValue=(typeof rawValue=="string")?[rawValue]:rawValue;var value=options.formatMatch(rawValue,i+1,options.data.length);if(value===false)continue;var firstChar=value.charAt(0).toLowerCase();if(!stMatchSets[firstChar])stMatchSets[firstChar]=[];var row={value:value,data:rawValue,result:options.formatResult&&options.formatResult(rawValue)||value};stMatchSets[firstChar].push(row);if(nullData++<options.max){stMatchSets[""].push(row);}};$.each(stMatchSets,function(i,value){options.cacheLength++;add(i,value);});}setTimeout(populate,25);function flush(){data={};length=0;}return{flush:flush,add:add,populate:populate,load:function(q){if(!options.cacheLength||!length)return null;if(!options.url&&options.matchContains){var csub=[];for(var k in data){if(k.length>0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}}return csub;}else
+if(data[q]){return data[q];}else
+if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}}return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit)return;element=$("<div/>").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("<ul/>").appendTo(element).mouseover(function(event){if(target(event).nodeName&&target(event).nodeName.toUpperCase()=='LI'){active=$("li",list).removeClass(CLASSES.ACTIVE).index(target(event));$(target(event)).addClass(CLASSES.ACTIVE);}}).click(function(event){$(target(event)).addClass(CLASSES.ACTIVE);select();input.focus();return false;}).mousedown(function(){config.mouseDownOnSelect=true;}).mouseup(function(){config.mouseDownOnSelect=false;});if(options.width>0)element.css("width",options.width);needsInit=false;}function target(event){var element=event.target;while(element&&element.tagName!="LI")element=element.parentNode;if(!element)return[];return element;}function moveSelect(step){listItems.slice(active,active+1).removeClass(CLASSES.ACTIVE);movePosition(step);var activeItem=listItems.slice(active,active+1).addClass(CLASSES.ACTIVE);if(options.scroll){var offset=0;listItems.slice(0,active).each(function(){offset+=this.offsetHeight;});if((offset+activeItem[0].offsetHeight-list.scrollTop())>list[0].clientHeight){list.scrollTop(offset+activeItem[0].offsetHeight-list.innerHeight());}else if(offset<list.scrollTop()){list.scrollTop(offset);}}};function movePosition(step){active+=step;if(active<0){active=listItems.size()-1;}else if(active>=listItems.size()){active=0;}}function limitNumberOfItems(available){return options.max&&options.max<available?options.max:available;}function fillList(){list.empty();var max=limitNumberOfItems(data.length);for(var i=0;i<max;i++){if(!data[i])continue;var formatted=options.formatItem(data[i].data,i+1,max,data[i].value,term);if(formatted===false)continue;var li=$("<li/>").html(options.highlight(formatted,term)).addClass(i%2==0?"ac_even":"ac_odd").appendTo(list)[0];$.data(li,"ac_data",data[i]);}listItems=list.find("li");if(options.selectFirst){listItems.slice(0,1).addClass(CLASSES.ACTIVE);active=0;}if($.fn.bgiframe)list.bgiframe();}return{display:function(d,q){init();data=d;term=q;fillList();},next:function(){moveSelect(1);},prev:function(){moveSelect(-1);},pageUp:function(){if(active!=0&&active-8<0){moveSelect(-active);}else{moveSelect(-8);}},pageDown:function(){if(active!=listItems.size()-1&&active+8>listItems.size()){moveSelect(listItems.size()-1-active);}else{moveSelect(8);}},hide:function(){element&&element.hide();listItems&&listItems.removeClass(CLASSES.ACTIVE);active=-1;},visible:function(){return element&&element.is(":visible");},current:function(){return this.visible()&&(listItems.filter("."+CLASSES.ACTIVE)[0]||options.selectFirst&&listItems[0]);},show:function(){var offset=$(input).offset();element.css({width:typeof options.width=="string"||options.width>0?options.width:$(input).width(),top:offset.top+input.offsetHeight,left:offset.left}).show();if(options.scroll){list.scrollTop(0);list.css({maxHeight:options.scrollHeight,overflow:'auto'});if($.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var listHeight=0;listItems.each(function(){listHeight+=this.offsetHeight;});var scrollbarsVisible=listHeight>options.scrollHeight;list.css('height',scrollbarsVisible?options.scrollHeight:listHeight);if(!scrollbarsVisible){listItems.width(list.width()-parseInt(listItems.css("padding-left"))-parseInt(listItems.css("padding-right")));}}}},selected:function(){var selected=listItems&&listItems.filter("."+CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);return selected&&selected.length&&$.data(selected[0],"ac_data");},emptyList:function(){list&&list.empty();},unbind:function(){element&&element.remove();}};};$.Autocompleter.Selection=function(field,start,end){if(field.createTextRange){var selRange=field.createTextRange();selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}else if(field.setSelectionRange){field.setSelectionRange(start,end);}else{if(field.selectionStart){field.selectionStart=start;field.selectionEnd=end;}}field.focus();};})(jQuery);
+/*
+ * TypeWatch 2.0 - Original by Denny Ferrassoli / Refactored by Charles Christolini
+ * Copyright(c) 2007 Denny Ferrassoli - DennyDotNet.com
+ * Coprright(c) 2008 Charles Christolini - BinaryPie.com
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+*/(function(jQuery){jQuery.fn.typeWatch=function(o){var options=jQuery.extend({wait:750,callback:function(){},highlight:true,captureLength:2},o);function checkElement(timer,override){var elTxt=jQuery(timer.el).val();if((elTxt.length>options.captureLength&&elTxt.toUpperCase()!=timer.text)||(override&&elTxt.length>options.captureLength)){timer.text=elTxt.toUpperCase();timer.cb(elTxt)}};function watchElement(elem){if(elem.type.toUpperCase()=="TEXT"||elem.nodeName.toUpperCase()=="TEXTAREA"){var timer={timer:null,text:jQuery(elem).val().toUpperCase(),cb:options.callback,el:elem,wait:options.wait};if(options.highlight){jQuery(elem).focus(function(){this.select()})}var startWatch=function(evt){var timerWait=timer.wait;var overrideBool=false;if(evt.keyCode==13&&this.type.toUpperCase()=="TEXT"){timerWait=1;overrideBool=true}var timerCallbackFx=function(){checkElement(timer,overrideBool)};clearTimeout(timer.timer);timer.timer=setTimeout(timerCallbackFx,timerWait)};jQuery(elem).keydown(startWatch)}};return this.each(function(index){watchElement(this)})}})(jQuery);
+/*
+Ajax upload
+*/jQuery.extend({createUploadIframe:function(d,b){var a="jUploadFrame"+d;if(window.ActiveXObject){var c=document.createElement('<iframe id="'+a+'" name="'+a+'" />');if(typeof b=="boolean"){c.src="javascript:false"}else{if(typeof b=="string"){c.src=b}}}else{var c=document.createElement("iframe");c.id=a;c.name=a}c.style.position="absolute";c.style.top="-1000px";c.style.left="-1000px";document.body.appendChild(c);return c},createUploadForm:function(g,b){var e="jUploadForm"+g;var a="jUploadFile"+g;var d=$('<form action="" method="POST" name="'+e+'" id="'+e+'" enctype="multipart/form-data"></form>');var c=$("#"+b);var f=$(c).clone();$(c).attr("id",a);$(c).before(f);$(c).appendTo(d);$(d).css("position","absolute");$(d).css("top","-1200px");$(d).css("left","-1200px");$(d).appendTo("body");return d},ajaxFileUpload:function(k){k=jQuery.extend({},jQuery.ajaxSettings,k);var a=new Date().getTime();var b=jQuery.createUploadForm(a,k.fileElementId);var i=jQuery.createUploadIframe(a,k.secureuri);var h="jUploadFrame"+a;var j="jUploadForm"+a;if(k.global&&!jQuery.active++){jQuery.event.trigger("ajaxStart")}var c=false;var f={};if(k.global){jQuery.event.trigger("ajaxSend",[f,k])}var d=function(l){var p=document.getElementById(h);try{if(p.contentWindow){f.responseText=p.contentWindow.document.body?p.contentWindow.document.body.innerText:null;f.responseXML=p.contentWindow.document.XMLDocument?p.contentWindow.document.XMLDocument:p.contentWindow.document}else{if(p.contentDocument){f.responseText=p.contentDocument.document.body?p.contentDocument.document.body.textContent||document.body.innerText:null;f.responseXML=p.contentDocument.document.XMLDocument?p.contentDocument.document.XMLDocument:p.contentDocument.document}}}catch(o){jQuery.handleError(k,f,null,o)}if(f||l=="timeout"){c=true;var m;try{m=l!="timeout"?"success":"error";if(m!="error"){var n=jQuery.uploadHttpData(f,k.dataType);if(k.success){k.success(n,m)}if(k.global){jQuery.event.trigger("ajaxSuccess",[f,k])}}else{jQuery.handleError(k,f,m)}}catch(o){m="error";jQuery.handleError(k,f,m,o)}if(k.global){jQuery.event.trigger("ajaxComplete",[f,k])}if(k.global&&!--jQuery.active){jQuery.event.trigger("ajaxStop")}if(k.complete){k.complete(f,m)}jQuery(p).unbind();setTimeout(function(){try{$(p).remove();$(b).remove()}catch(q){jQuery.handleError(k,f,null,q)}},100);f=null}};if(k.timeout>0){setTimeout(function(){if(!c){d("timeout")}},k.timeout)}try{var b=$("#"+j);$(b).attr("action",k.url);$(b).attr("method","POST");$(b).attr("target",h);if(b.encoding){b.encoding="multipart/form-data"}else{b.enctype="multipart/form-data"}$(b).submit()}catch(g){jQuery.handleError(k,f,null,g)}if(window.attachEvent){document.getElementById(h).attachEvent("onload",d)}else{document.getElementById(h).addEventListener("load",d,false)}return{abort:function(){}}},uploadHttpData:function(r,type){var data=!type;data=type=="xml"||data?r.responseXML:r.responseText;if(type=="script"){jQuery.globalEval(data)}if(type=="json"){eval("data = "+data)}if(type=="html"){jQuery("<div>").html(data).evalScripts()}return data}});
+/*Upload call*/
+function ajaxFileUpload(imageUrl)
+{
+ $("#loading").ajaxStart(function(){
+ $(this).show();
+ }).ajaxComplete(function(){
+ $(this).hide();
+ });
+
+ $("#upload").ajaxStart(function(){
+ $(this).hide();
+ }).ajaxComplete(function(){
+ $(this).show();
+ });
+
+ $.ajaxFileUpload
+ (
+ {
+ url:'/upload/',
+ secureuri:false,
+ fileElementId:'file-upload',
+ dataType: 'xml',
+ success: function (data, status)
+ {
+ var fileURL = $(data).find('file_url').text();
+ var error = $(data).find('error').text();
+ if(error != ''){
+ alert(error);
+ }else{
+ imageUrl.attr('value', fileURL);
+ }
+
+ },
+ error: function (data, status, e)
+ {
+ alert(e);
+ }
+ }
+ )
+
+ 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 2d8c90a6..90166eec 100644
--- a/templates/content/js/com.cnprog.i18n.js
+++ b/templates/content/js/com.cnprog.i18n.js
@@ -1,5 +1,3 @@
-// Evgeny Fadeev evgeny.fadeev@gmail.com localized for english and chinese
-// i18nLang variable must be set in html header to extract correct language
var i18nZh = {
'insufficient privilege':'用户权限不在操作范围',
'cannot pick own answer as best':'不能设置自己的回答为最佳答案',
@@ -59,6 +57,10 @@ var i18nZh = {
};
var i18nEn = {
+ '>15 points requried to upvote':'>15 points requried to upvote ',
+ 'tags cannot be empty':'please enter at least one tag',
+ 'anonymous users cannot vote':'anonymous users cannot vote ',
+ 'anonymous users cannot select favorite questions':'anonymous users cannot select favorite questions ',
'to comment, need': 'to comment, need reputation ',
'please see':'please see ',
'community reputation points':' ',
@@ -84,7 +86,7 @@ var i18nEn = {
var i18n = {
'en':i18nEn,
- 'zh':i18nZh
+ 'zh_CN':i18nZh
};
var i18n_dict = i18n[i18nLang];
diff --git a/templates/content/js/com.cnprog.post.js b/templates/content/js/com.cnprog.post.js
index e3101ea7..546cf101 100644
--- a/templates/content/js/com.cnprog.post.js
+++ b/templates/content/js/com.cnprog.post.js
@@ -1,616 +1,631 @@
-/*
-Scripts for cnprog.com
-Project Name: Lanai
-All Rights Resevred 2008. CNPROG.COM
-*/
-var lanai =
-{
- /**
- * Finds any <pre><code></code></pre> tags which aren't registered for
- * pretty printing, adds the appropriate class name and invokes prettify.
- */
- highlightSyntax: function(){
- var styled = false;
- $("pre code").parent().each(function(){
- if (!$(this).hasClass('prettyprint')){
- $(this).addClass('prettyprint');
- styled = true;
- }
- });
-
- if (styled){
- prettyPrint();
- }
- }
-};
-
-var Vote = function(){
- // All actions are related to a question
- var questionId;
- // The object we operate on actually. It can be a question or an answer.
- var postId;
- var questionAuthorId;
- var currentUserId;
- var answerContainerIdPrefix = 'answer-container-';
- var voteContainerId = 'vote-buttons';
- var imgIdPrefixAccept = 'answer-img-accept-';
- var imgClassPrefixFavorite = 'question-img-favorite';
- var imgIdPrefixQuestionVoteup = 'question-img-upvote-';
- var imgIdPrefixQuestionVotedown = 'question-img-downvote-';
- var imgIdPrefixAnswerVoteup = 'answer-img-upvote-';
- var imgIdPrefixAnswerVotedown = 'answer-img-downvote-';
- var divIdFavorite = 'favorite-number';
- var commentLinkIdPrefix = 'comment-';
- var voteNumberClass = "vote-number";
- var offensiveIdPrefixQuestionFlag = 'question-offensive-flag-';
- var offensiveIdPrefixAnswerFlag = 'answer-offensive-flag-';
- var offensiveClassFlag = 'offensive-flag';
- var questionControlsId = 'question-controls';
- var removeQuestionLinkIdPrefix = 'question-delete-link-';
- var removeAnswerLinkIdPrefix = 'answer-delete-link-';
-
- var acceptAnonymousMessage = $.i18n._('insufficient privilege');
- var acceptOwnAnswerMessage = $.i18n._('cannot pick own answer as best');
- var favoriteAnonymousMessage = $.i18n._('anonymous user cannot select favorite questions')
- + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
- + $.i18n._('please login') + "</a>";
- var voteAnonymousMessage = $.i18n._('anonymous users cannot vote')
- + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
- + $.i18n._('please login') + "</a>";
- var upVoteRequiredScoreMessage = $.i18n._('>15 points requried to upvote')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var downVoteRequiredScoreMessage = $.i18n._('>100 points requried to downvote')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var voteOwnDeniedMessage = $.i18n._('cannot vote for own posts');
- var voteRequiredMoreVotes = $.i18n._('daily vote cap exhausted')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var voteDenyCancelMessage = $.i18n._('cannot revoke old vote')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var offensiveConfirmation = $.i18n._('please confirm offensive');
- var offensiveAnonymousMessage = $.i18n._('anonymous users cannot flag offensive posts')
- + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
- + $.i18n._('please login') + "</a>";
- var offensiveTwiceMessage = $.i18n._('cannot flag message as offensive twice')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var offensiveNoFlagsLeftMessage = $.i18n._('flag offensive cap exhausted')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var offensiveNoPermissionMessage = $.i18n._('need >15 points to report spam')
- + $.i18n._('please see') + "<a href='/faq'>faq</a>";
- var removeConfirmation = $.i18n._('confirm delete');
- var removeAnonymousMessage = $.i18n._('anonymous users cannot delete/undelete');
- var recoveredMessage = $.i18n._('post recovered');
- var deletedMessage = $.i18n._('post deleted');
-
- var VoteType = {
- acceptAnswer : 0,
- questionUpVote : 1,
- questionDownVote : 2,
- favorite : 4,
- answerUpVote: 5,
- answerDownVote:6,
- offensiveQuestion : 7,
- offensiveAnswer:8,
- removeQuestion: 9,
- removeAnswer:10
- };
-
- var getFavoriteButton = function(){
- var favoriteButton = 'div.'+ voteContainerId +' img[class='+ imgClassPrefixFavorite +']';
- return $(favoriteButton);
- };
- var getFavoriteNumber = function(){
- var favoriteNumber = '#'+ divIdFavorite ;
- return $(favoriteNumber);
- };
- var getQuestionVoteUpButton = function(){
- var questionVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVoteup +']';
- return $(questionVoteUpButton);
- };
- var getQuestionVoteDownButton = function(){
- var questionVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVotedown +']';
- return $(questionVoteDownButton);
- };
- var getAnswerVoteUpButtons = function(){
- var answerVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVoteup +']';
- return $(answerVoteUpButton);
- };
- var getAnswerVoteDownButtons = function(){
- var answerVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVotedown +']';
- return $(answerVoteDownButton);
- };
- var getAnswerVoteUpButton = function(id){
- var answerVoteUpButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVoteup + id + ']';
- return $(answerVoteUpButton);
- };
- var getAnswerVoteDownButton = function(id){
- var answerVoteDownButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVotedown + id + ']';
- return $(answerVoteDownButton);
- };
-
- var getOffensiveQuestionFlag = function(){
- var offensiveQuestionFlag = 'table[id=question-table] span[class='+ offensiveClassFlag +']';
- return $(offensiveQuestionFlag);
- };
-
- var getOffensiveAnswerFlags = function(){
- var offensiveQuestionFlag = 'div.answer span[class='+ offensiveClassFlag +']';
- return $(offensiveQuestionFlag);
- };
-
- var getremoveQuestionLink = function(){
- var removeQuestionLink = 'div#question-controls a[id^='+ removeQuestionLinkIdPrefix +']';
- return $(removeQuestionLink);
- };
-
- var getremoveAnswersLinks = function(){
- var removeAnswerLinks = 'div.answer-controls a[id^='+ removeAnswerLinkIdPrefix +']';
- return $(removeAnswerLinks);
- };
-
- var setVoteImage = function(voteType, undo, object){
- var flag = undo ? "" : "-on";
- var arrow = (voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote) ? "up" : "down";
- object.attr("src", "/content/images/vote-arrow-"+ arrow + flag +".png");
-
- // if undo voting, then undo the pair of arrows.
- if(undo){
- if(voteType == VoteType.questionUpVote || voteType == VoteType.questionDownVote){
- $(getQuestionVoteUpButton()).attr("src", "/content/images/vote-arrow-up.png");
- $(getQuestionVoteDownButton()).attr("src", "/content/images/vote-arrow-down.png");
- }
- else{
- $(getAnswerVoteUpButton(postId)).attr("src", "/content/images/vote-arrow-up.png");
- $(getAnswerVoteDownButton(postId)).attr("src", "/content/images/vote-arrow-down.png");
- }
- }
- };
-
- var setVoteNumber = function(object, number){
- var voteNumber = object.parent('div.'+ voteContainerId).find('div.'+ voteNumberClass);
- $(voteNumber).text(number);
- };
-
- var bindEvents = function(){
- // accept answers
- if(questionAuthorId == currentUserId){
- var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
- $(acceptedButtons).unbind('click').click(function(event){
- Vote.accept($(event.target))
- });
- }
- // set favorite question
- var favoriteButton = getFavoriteButton();
- favoriteButton.unbind('click').click(function(event){
- Vote.favorite($(event.target))
- });
-
- // question vote up
- var questionVoteUpButton = getQuestionVoteUpButton();
- questionVoteUpButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.questionUpVote)
- });
-
- var questionVoteDownButton = getQuestionVoteDownButton();
- questionVoteDownButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.questionDownVote)
- });
-
- var answerVoteUpButton = getAnswerVoteUpButtons();
- answerVoteUpButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.answerUpVote)
- });
-
- var answerVoteDownButton = getAnswerVoteDownButtons();
- answerVoteDownButton.unbind('click').click(function(event){
- Vote.vote($(event.target), VoteType.answerDownVote)
- });
-
- getOffensiveQuestionFlag().unbind('click').click(function(event){
- Vote.offensive(this, VoteType.offensiveQuestion)
- });
-
- getOffensiveAnswerFlags().unbind('click').click(function(event){
- Vote.offensive(this, VoteType.offensiveAnswer)
- });
-
- getremoveQuestionLink().unbind('click').click(function(event){
- Vote.remove(this, VoteType.removeQuestion);
- });
-
- getremoveAnswersLinks().unbind('click').click(function(event){
- Vote.remove(this, VoteType.removeAnswer)
- });
- };
-
- var submit = function(object, voteType, callback) {
- $.ajax({
- type: "POST",
- cache: false,
- dataType: "json",
- url: "/questions/" + questionId + "/vote/",
- data: { "type": voteType, "postId": postId },
- error: handleFail,
- success: function(data){callback(object, voteType, data)}});
- };
-
- var handleFail = function(xhr, msg){
- alert("Callback invoke error: " + msg)
- };
-
- // callback function for Accept Answer action
- var callback_accept = function(object, voteType, data){
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, acceptAnonymousMessage);
- }
- else if(data.allowed == "-1"){
- showMessage(object, acceptOwnAnswerMessage);
- }
- else if(data.status == "1"){
- object.attr("src", "/content/images/vote-accepted.png");
- $("#"+answerContainerIdPrefix+postId).removeClass("accepted-answer");
- $("#"+commentLinkIdPrefix+postId).removeClass("comment-link-accepted");
- }
- else if(data.success == "1"){
- var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
- $(acceptedButtons).attr("src", "/content/images/vote-accepted.png");
- var answers = ("div[id^="+answerContainerIdPrefix +"]");
- $(answers).removeClass("accepted-answer");
- var commentLinks = ("div[id^="+answerContainerIdPrefix +"] div[id^="+ commentLinkIdPrefix +"]");
- $(commentLinks).removeClass("comment-link-accepted");
-
- object.attr("src", "/content/images/vote-accepted-on.png");
- $("#"+answerContainerIdPrefix+postId).addClass("accepted-answer");
- $("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted");
- }
- else{
- showMessage(object, data.message);
- }
- };
-
- var callback_favorite = function(object, voteType, data){
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
- }
- else if(data.status == "1"){
- object.attr("src", "/content/images/vote-favorite-off.png");
- var fav = getFavoriteNumber();
- fav.removeClass("my-favorite-number");
- if(data.count == 0)
- data.count = '';
- fav.text(data.count);
- }
- else if(data.success == "1"){
- object.attr("src", "/content/images/vote-favorite-on.png");
- var fav = getFavoriteNumber();
- fav.text(data.count);
- fav.addClass("my-favorite-number");
- }
- else{
- showMessage(object, data.message);
- }
- };
-
- var callback_vote = function(object, voteType, data){
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
- }
- else if(data.allowed == "-3"){
- showMessage(object, voteRequiredMoreVotes);
- }
- else if(data.allowed == "-2"){
- if(voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote){
- showMessage(object, upVoteRequiredScoreMessage);
- }
- else if(voteType == VoteType.questionDownVote || voteType == VoteType.answerDownVote){
- showMessage(object, downVoteRequiredScoreMessage);
- }
- }
- else if(data.allowed == "-1"){
- showMessage(object, voteOwnDeniedMessage);
- }
- else if(data.status == "2"){
- showMessage(object, voteDenyCancelMessage);
- }
- else if(data.status == "1"){
- setVoteImage(voteType, true, object);
- setVoteNumber(object, data.count);
- }
- else if(data.success == "1"){
- setVoteImage(voteType, false, object);
- setVoteNumber(object, data.count);
- if(data.message.length > 0)
- showMessage(object, data.message);
- }
- };
-
- var callback_offensive = function(object, voteType, data){
- object = $(object);
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
- }
- else if(data.allowed == "-3"){
- showMessage(object, offensiveNoFlagsLeftMessage);
- }
- else if(data.allowed == "-2"){
- showMessage(object, offensiveNoPermissionMessage);
- }
- else if(data.status == "1"){
- showMessage(object, offensiveTwiceMessage);
- }
- else if(data.success == "1"){
- $(object).children('span[class=darkred]').text("("+ data.count +")");
- }
- };
-
- var callback_remove = function(object, voteType, data){
- alert(data.status);
- if(data.allowed == "0" && data.success == "0"){
- showMessage(object, removeAnonymousMessage.replace("{{QuestionID}}", questionId));
- }
- else if (data.success == "1"){
- if (removeActionType == 'delete'){
- postNode.addClass('deleted');
- postRemoveLink.innerHTML = $.i18n._('undelete');
- showMessage(object, deletedMessage);
- }
- else if (removeActionType == 'undelete') {
- postNode.removeClass('deleted');
- postRemoveLink.innerHTML = $.i18n._('delete');
- showMessage(object, recoveredMessage);
- }
- }
- };
-
- return {
- init : function(qId, questionAuthor, userId){
- questionId = qId;
- questionAuthorId = questionAuthor;
- currentUserId = userId;
- bindEvents();
- },
-
- // Accept answer public function
- accept: function(object){
- postId = object.attr("id").substring(imgIdPrefixAccept.length);
- submit(object, VoteType.acceptAnswer, callback_accept);
- },
-
- favorite: function(object){
- if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
- return false;
- }
- submit(object, VoteType.favorite, callback_favorite);
- },
-
- vote: function(object, voteType){
- if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
- return false;
- }
- if(voteType == VoteType.answerUpVote){
- postId = object.attr("id").substring(imgIdPrefixAnswerVoteup.length);
- }
- else if(voteType == VoteType.answerDownVote){
- postId = object.attr("id").substring(imgIdPrefixAnswerVotedown.length);
- }
-
- submit(object, voteType, callback_vote);
- },
-
- offensive: function(object, voteType){
- if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage($(object), offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
- return false;
- }
- if(confirm(offensiveConfirmation)){
- postId = object.id.substr(object.id.lastIndexOf('-') + 1);
- submit(object, voteType, callback_offensive);
- }
- },
-
- remove: function(object, voteType){
- if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
- showMessage($(object), removeAnonymousMessage.replace("{{QuestionID}}", questionId));
- return false;
- }
- if(confirm(removeConfirmation)){
- bits = object.id.split('-');
- postId = bits.pop();/* this seems to be used within submit! */
- postType = bits.shift();
-
- if (postType == 'answer'){
- postNode = $('#answer-container-' + postId);
- postRemoveLink = object;
- if (postNode.hasClass('deleted')){
- removeActionType = 'undelete';
- }
- else {
- removeActionType = 'delete';
- }
- }
- submit($(object), voteType, callback_remove);
-
-
- }
- }
- }
-} ();
-
-
-// site comments
-function createComments(type) {
- var objectType = type;
- var jDivInit = function(id) {
- return $("#comments-" + objectType + '-' + id);
- };
-
- var appendLoaderImg = function(id) {
- appendLoader("#comments-" + objectType + '-' + id + " div.comments");
- };
-
- var canPostComments = function(id, jDiv) {
- var jHidden = jDiv.siblings("#can-post-comments-" + objectType + '-' + id);
- return jHidden.val().toLowerCase() == "true";
- };
-
- var renderForm = function(id, jDiv) {
- var formId = "form-comments-" + objectType + "-" + id;
- if (canPostComments(id, jDiv)) {
- 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 += '<span class="form-error"></span></div></form>';
-
- jDiv.append(form);
-
- setupFormValidation("#" + formId,
- { comment: { required: true, minlength: 10} }, '',
- function() { postComment(id, formId); });
- }
- }
- else {
- var divId = "comments-rep-needed-" + objectType + '-' + id;
- if (jDiv.find("#" + divId).length == 0) {
- jDiv.append('<div id="' + divId + '" style="color:red">'
- + $.i18n._('to comment, need') + ' ' +
- + repNeededForComments + ' ' + $.i18n._('community reputation points')
- + '<a href="/faq" class="comment-user">' + $.i18n._('please see') + 'faq</a></span>');
- }
- }
- };
-
- var getComments = function(id, jDiv) {
- appendLoaderImg(id);
- $.getJSON("/" + objectType + "s/" + id + "/comments/", function(json) { showComments(id, json); });
- };
-
- var showComments = function(id, json) {
- var jDiv = jDivInit(id);
-
- jDiv = jDiv.find("div.comments"); // this div should contain any fetched comments..
- jDiv.find("div[id^='comment-" + objectType + "-'" + "]").remove(); // clean previous calls..
-
- removeLoader();
-
- if (json && json.length > 0) {
- for (var i = 0; i < json.length; i++)
- renderComment(jDiv, json[i]);
-
- jDiv.children().show();
- }
- };
-
- // {"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) {
- var html = '<div id="comment-' + objectType + "-" + json.id + '" style="display:none">' + json.text;
- html += json.user_url ? '&nbsp;&ndash;&nbsp;<a href="' + json.user_url + '"' : '<span';
- html += ' class="comment-user">' + json.user_display_name + (json.user_url ? '</a>' : '</span>');
- html += ' <span class="comment-date">(' + json.add_date + ')</span>';
-
- if (json.delete_url) {
- var img = "/content/images/close-small.png";
- var imgHover = "/content/images/close-small-hover.png";
- html += '<img onclick="' + objectType + 'Comments.deleteComment($(this), ' + json.object_id + ', \'' + json.delete_url + '\')" src="' + img;
- html += '" onmouseover="$(this).attr(\'src\', \'' + imgHover + '\')" onmouseout="$(this).attr(\'src\', \'' + img
- html += '\')" title="' + $.i18n._('delete this comment') + '" />';
- }
-
- html += '</div>';
-
- jDiv.append(html);
- };
-
- var postComment = function(id, formId) {
- appendLoaderImg(id);
-
- var formSelector = "#" + formId;
- var textarea = $(formSelector + " textarea");
-
- $.ajax({
- type: "POST",
- url: "/" + objectType + "s/" + id + "/comments/",
- dataType: "json",
- data: { comment: textarea.val() },
- success: function(json) {
- showComments(id, json);
- textarea.val("");
- commentsFactory[objectType].updateTextCounter(textarea);
- enableSubmitButton(formSelector);
- },
- error: function(res, textStatus, errorThrown) {
- removeLoader();
- showMessage(formSelector, res.responseText);
- enableSubmitButton(formSelector);
- }
- });
- };
-
- // public methods..
- return {
-
- init: function() {
- // Setup "show comments" clicks..
- $("a[id^='comments-link-" + objectType + "-" + "']").unbind("click").click(function() { commentsFactory[objectType].show($(this).attr("id").substr(("comments-link-" + objectType + "-").length)); });
- },
-
- show: function(id) {
- var jDiv = jDivInit(id);
- getComments(id, jDiv);
- renderForm(id, jDiv);
- jDiv.show();
- if (canPostComments(id, jDiv)) jDiv.find("textarea").get(0).focus();
- jDiv.siblings("a").unbind("click").click(function(){
- commentsFactory[objectType].hide(id);
- }).text($.i18n._('hide comments'));
- },
-
- 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>)";
-
- jDiv.hide();
- jDiv.siblings("a").unbind("click").click(function() { commentsFactory[objectType].show(id); }).html(anchorText);
- jDiv.children("div.comments").children().hide();
- },
-
- deleteComment: function(jImg, id, deleteUrl) {
- if (confirm($.i18n._('confirm delete comment'))) {
- jImg.hide();
- appendLoaderImg(id);
- $.post(deleteUrl, { dataNeeded: "forIIS7" }, function(json) {
- showComments(id, json);
- }, "json");
- }
- },
-
- updateTextCounter: function(textarea) {
- 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);
- }
- };
-}
-
-var questionComments = createComments('question');
-var answerComments = createComments('answer');
-
-$().ready(function() {
- questionComments.init();
- answerComments.init();
-});
-
-var commentsFactory = {'question' : questionComments, 'answer' : answerComments};
-
-/*
-Prettify
-http://www.apache.org/licenses/LICENSE-2.0
-*/
-var PR_SHOULD_USE_CONTINUATION = true; var PR_TAB_WIDTH = 8; var PR_normalizedHtml; var PR; var prettyPrintOne; var prettyPrint; function _pr_isIE6() { var isIE6 = navigator && navigator.userAgent && /\bMSIE 6\./.test(navigator.userAgent); _pr_isIE6 = function() { return isIE6; }; return isIE6; } (function() { function wordSet(words) { words = words.split(/ /g); var set = {}; for (var i = words.length; --i >= 0; ) { var w = words[i]; if (w) { set[w] = null; } } return set; } var FLOW_CONTROL_KEYWORDS = "break continue do else for if return while "; var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " + "double enum extern float goto int long register short signed sizeof " + "static struct switch typedef union unsigned void volatile "; var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " + "new operator private protected public this throw true try "; var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " + "concept concept_map const_cast constexpr decltype " + "dynamic_cast explicit export friend inline late_check " + "mutable namespace nullptr reinterpret_cast static_assert static_cast " + "template typeid typename typeof using virtual wchar_t where "; var JAVA_KEYWORDS = COMMON_KEYWORDS + "boolean byte extends final finally implements import instanceof null " + "native package strictfp super synchronized throws transient "; var CSHARP_KEYWORDS = JAVA_KEYWORDS + "as base by checked decimal delegate descending event " + "fixed foreach from group implicit in interface internal into is lock " + "object out override orderby params readonly ref sbyte sealed " + "stackalloc string select uint ulong unchecked unsafe ushort var "; var JSCRIPT_KEYWORDS = COMMON_KEYWORDS + "debugger eval export function get null set undefined var with " + "Infinity NaN "; var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " + "goto if import last local my next no our print package redo require " + "sub undef unless until use wantarray while BEGIN END "; var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " + "elif except exec finally from global import in is lambda " + "nonlocal not or pass print raise try with yield " + "False True None "; var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" + " defined elsif end ensure false in module next nil not or redo rescue " + "retry self super then true undef unless until when yield BEGIN END "; var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " + "function in local set then until "; var ALL_KEYWORDS = (CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS + PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS); var PR_STRING = 'str'; var PR_KEYWORD = 'kwd'; var PR_COMMENT = 'com'; var PR_TYPE = 'typ'; var PR_LITERAL = 'lit'; var PR_PUNCTUATION = 'pun'; var PR_PLAIN = 'pln'; var PR_TAG = 'tag'; var PR_DECLARATION = 'dec'; var PR_SOURCE = 'src'; var PR_ATTRIB_NAME = 'atn'; var PR_ATTRIB_VALUE = 'atv'; var PR_NOCODE = 'nocode'; function isWordChar(ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); } function spliceArrayInto(inserted, container, containerPosition, countReplaced) { inserted.unshift(containerPosition, countReplaced || 0); try { container.splice.apply(container, inserted); } finally { inserted.splice(0, 2); } } var REGEXP_PRECEDER_PATTERN = function() { var preceders = ["!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=", "&=", "(", "*", "*=", "+=", ",", "-=", "->", "/", "/=", ":", "::", ";", "<", "<<", "<<=", "<=", "=", "==", "===", ">", ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[", "^", "^=", "^^", "^^=", "{", "|", "|=", "||", "||=", "~", "break", "case", "continue", "delete", "do", "else", "finally", "instanceof", "return", "throw", "try", "typeof"]; var pattern = '(?:' + '(?:(?:^|[^0-9.])\\.{1,3})|' + '(?:(?:^|[^\\+])\\+)|' + '(?:(?:^|[^\\-])-)'; for (var i = 0; i < preceders.length; ++i) { var preceder = preceders[i]; if (isWordChar(preceder.charAt(0))) { pattern += '|\\b' + preceder; } else { pattern += '|' + preceder.replace(/([^=<>:&])/g, '\\$1'); } } pattern += '|^)\\s*$'; return new RegExp(pattern); } (); var pr_amp = /&/g; var pr_lt = /</g; var pr_gt = />/g; var pr_quot = /\"/g; function attribToHtml(str) { return str.replace(pr_amp, '&amp;').replace(pr_lt, '&lt;').replace(pr_gt, '&gt;').replace(pr_quot, '&quot;'); } function textToHtml(str) { return str.replace(pr_amp, '&amp;').replace(pr_lt, '&lt;').replace(pr_gt, '&gt;'); } var pr_ltEnt = /&lt;/g; var pr_gtEnt = /&gt;/g; var pr_aposEnt = /&apos;/g; var pr_quotEnt = /&quot;/g; var pr_ampEnt = /&amp;/g; var pr_nbspEnt = /&nbsp;/g; function htmlToText(html) { var pos = html.indexOf('&'); if (pos < 0) { return html; } for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0; ) { var end = html.indexOf(';', pos); if (end >= 0) { var num = html.substring(pos + 3, end); var radix = 10; if (num && num.charAt(0) === 'x') { num = num.substring(1); radix = 16; } var codePoint = parseInt(num, radix); if (!isNaN(codePoint)) { html = (html.substring(0, pos) + String.fromCharCode(codePoint) + html.substring(end + 1)); } } } return html.replace(pr_ltEnt, '<').replace(pr_gtEnt, '>').replace(pr_aposEnt, "'").replace(pr_quotEnt, '"').replace(pr_ampEnt, '&').replace(pr_nbspEnt, ' '); } function isRawContent(node) { return 'XMP' === node.tagName; } function normalizedHtml(node, out) { switch (node.nodeType) { case 1: var name = node.tagName.toLowerCase(); out.push('<', name); for (var i = 0; i < node.attributes.length; ++i) { var attr = node.attributes[i]; if (!attr.specified) { continue; } out.push(' '); normalizedHtml(attr, out); } out.push('>'); for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } if (node.firstChild || !/^(?:br|link|img)$/.test(name)) { out.push('<\/', name, '>'); } break; case 2: out.push(node.name.toLowerCase(), '="', attribToHtml(node.value), '"'); break; case 3: case 4: out.push(textToHtml(node.nodeValue)); break; } } var PR_innerHtmlWorks = null; function getInnerHtml(node) { if (null === PR_innerHtmlWorks) { var testNode = document.createElement('PRE'); testNode.appendChild(document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />')); PR_innerHtmlWorks = !/</.test(testNode.innerHTML); } if (PR_innerHtmlWorks) { var content = node.innerHTML; if (isRawContent(node)) { content = textToHtml(content); } return content; } var out = []; for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } return out.join(''); } function makeTabExpander(tabWidth) { var SPACES = ' '; var charInLine = 0; return function(plainText) { var out = null; var pos = 0; for (var i = 0, n = plainText.length; i < n; ++i) { var ch = plainText.charAt(i); switch (ch) { case '\t': if (!out) { out = []; } out.push(plainText.substring(pos, i)); var nSpaces = tabWidth - (charInLine % tabWidth); charInLine += nSpaces; for (; nSpaces >= 0; nSpaces -= SPACES.length) { out.push(SPACES.substring(0, nSpaces)); } pos = i + 1; break; case '\n': charInLine = 0; break; default: ++charInLine; } } if (!out) { return plainText; } out.push(plainText.substring(pos)); return out.join(''); }; } var pr_chunkPattern = /(?:[^<]+|<!--[\s\S]*?-->|<!\[CDATA\[([\s\S]*?)\]\]>|<\/?[a-zA-Z][^>]*>|<)/g; var pr_commentPrefix = /^<!--/; var pr_cdataPrefix = /^<\[CDATA\[/; var pr_brPrefix = /^<br\b/i; var pr_tagNameRe = /^<(\/?)([a-zA-Z]+)/; function extractTags(s) { var matches = s.match(pr_chunkPattern); var sourceBuf = []; var sourceBufLen = 0; var extractedTags = []; if (matches) { for (var i = 0, n = matches.length; i < n; ++i) { var match = matches[i]; if (match.length > 1 && match.charAt(0) === '<') { if (pr_commentPrefix.test(match)) { continue; } if (pr_cdataPrefix.test(match)) { sourceBuf.push(match.substring(9, match.length - 3)); sourceBufLen += match.length - 12; } else if (pr_brPrefix.test(match)) { sourceBuf.push('\n'); ++sourceBufLen; } else { if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) { var name = match.match(pr_tagNameRe)[2]; var depth = 1; end_tag_loop: for (var j = i + 1; j < n; ++j) { var name2 = matches[j].match(pr_tagNameRe); if (name2 && name2[2] === name) { if (name2[1] === '/') { if (--depth === 0) { break end_tag_loop; } } else { ++depth; } } } if (j < n) { extractedTags.push(sourceBufLen, matches.slice(i, j + 1).join('')); i = j; } else { extractedTags.push(sourceBufLen, match); } } else { extractedTags.push(sourceBufLen, match); } } } else { var literalText = htmlToText(match); sourceBuf.push(literalText); sourceBufLen += literalText.length; } } } return { source: sourceBuf.join(''), tags: extractedTags }; } function isNoCodeTag(tag) { return !!tag.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g, ' $1="$2$3$4"').match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/); } function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) { var shortcuts = {}; (function() { var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns); for (var i = allPatterns.length; --i >= 0; ) { var patternParts = allPatterns[i]; var shortcutChars = patternParts[3]; if (shortcutChars) { for (var c = shortcutChars.length; --c >= 0; ) { shortcuts[shortcutChars.charAt(c)] = patternParts; } } } })(); var nPatterns = fallthroughStylePatterns.length; var notWs = /\S/; return function(sourceCode, opt_basePos) { opt_basePos = opt_basePos || 0; var decorations = [opt_basePos, PR_PLAIN]; var lastToken = ''; var pos = 0; var tail = sourceCode; while (tail.length) { var style; var token = null; var match; var patternParts = shortcuts[tail.charAt(0)]; if (patternParts) { match = tail.match(patternParts[1]); token = match[0]; style = patternParts[0]; } else { for (var i = 0; i < nPatterns; ++i) { patternParts = fallthroughStylePatterns[i]; var contextPattern = patternParts[2]; if (contextPattern && !contextPattern.test(lastToken)) { continue; } match = tail.match(patternParts[1]); if (match) { token = match[0]; style = patternParts[0]; break; } } if (!token) { style = PR_PLAIN; token = tail.substring(0, 1); } } decorations.push(opt_basePos + pos, style); pos += token.length; tail = tail.substring(token.length); if (style !== PR_COMMENT && notWs.test(token)) { lastToken = token; } } return decorations; }; } var PR_MARKUP_LEXER = createSimpleLexer([], [[PR_PLAIN, /^[^<]+/, null], [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/, null], [PR_COMMENT, /^<!--[\s\S]*?(?:-->|$)/, null], [PR_SOURCE, /^<\?[\s\S]*?(?:\?>|$)/, null], [PR_SOURCE, /^<%[\s\S]*?(?:%>|$)/, null], [PR_SOURCE, /^<(script|style|xmp)\b[^>]*>[\s\S]*?<\/\1\b[^>]*>/i, null], [PR_TAG, /^<\/?\w[^<>]*>/, null]]); var PR_SOURCE_CHUNK_PARTS = /^(<[^>]*>)([\s\S]*)(<\/[^>]*>)$/; function tokenizeMarkup(source) { var decorations = PR_MARKUP_LEXER(source); for (var i = 0; i < decorations.length; i += 2) { if (decorations[i + 1] === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var sourceChunk = source.substring(start, end); var match = sourceChunk.match(PR_SOURCE_CHUNK_PARTS); if (match) { decorations.splice(i, 2, start, PR_TAG, start + match[1].length, PR_SOURCE, start + match[1].length + (match[2] || '').length, PR_TAG); } } } return decorations; } var PR_TAG_LEXER = createSimpleLexer([[PR_ATTRIB_VALUE, /^\'[^\']*(?:\'|$)/, null, "'"], [PR_ATTRIB_VALUE, /^\"[^\"]*(?:\"|$)/, null, '"'], [PR_PUNCTUATION, /^[<>\/=]+/, null, '<>/=']], [[PR_TAG, /^[\w:\-]+/, /^</], [PR_ATTRIB_VALUE, /^[\w\-]+/, /^=/], [PR_ATTRIB_NAME, /^[\w:\-]+/, null], [PR_PLAIN, /^\s+/, null, ' \t\r\n']]); function splitTagAttributes(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_TAG) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var chunk = source.substring(start, end); var subDecorations = PR_TAG_LEXER(chunk, start); spliceArrayInto(subDecorations, decorations, i, 2); i += subDecorations.length - 2; } } return decorations; } function sourceDecorator(options) { var shortcutStylePatterns = [], fallthroughStylePatterns = []; if (options.tripleQuotedStrings) { shortcutStylePatterns.push([PR_STRING, /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/, null, '\'"']); } else if (options.multiLineStrings) { shortcutStylePatterns.push([PR_STRING, /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/, null, '\'"`']); } else { shortcutStylePatterns.push([PR_STRING, /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/, null, '"\'']); } fallthroughStylePatterns.push([PR_PLAIN, /^(?:[^\'\"\`\/\#]+)/, null, ' \r\n']); if (options.hashComments) { shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']); } if (options.cStyleComments) { fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]); fallthroughStylePatterns.push([PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]); } if (options.regexLiterals) { var REGEX_LITERAL = ('^/(?=[^/*])' + '(?:[^/\\x5B\\x5C]' + '|\\x5C[\\s\\S]' + '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+' + '(?:/|$)'); fallthroughStylePatterns.push([PR_STRING, new RegExp(REGEX_LITERAL), REGEXP_PRECEDER_PATTERN]); } var keywords = wordSet(options.keywords); options = null; var splitStringAndCommentTokens = createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns); var styleLiteralIdentifierPuncRecognizer = createSimpleLexer([], [[PR_PLAIN, /^\s+/, null, ' \r\n'], [PR_PLAIN, /^[a-z_$@][a-z_$@0-9]*/i, null], [PR_LITERAL, /^0x[a-f0-9]+[a-z]/i, null], [PR_LITERAL, /^(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d+)(?:e[+\-]?\d+)?[a-z]*/i, null, '123456789'], [PR_PUNCTUATION, /^[^\s\w\.$@]+/, null]]); function splitNonStringNonCommentTokens(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_PLAIN) { var start, end, chunk, subDecs; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; chunk = source.substring(start, end); subDecs = styleLiteralIdentifierPuncRecognizer(chunk, start); for (var j = 0, m = subDecs.length; j < m; j += 2) { var subStyle = subDecs[j + 1]; if (subStyle === PR_PLAIN) { var subStart = subDecs[j]; var subEnd = j + 2 < m ? subDecs[j + 2] : chunk.length; var token = source.substring(subStart, subEnd); if (token === '.') { subDecs[j + 1] = PR_PUNCTUATION; } else if (token in keywords) { subDecs[j + 1] = PR_KEYWORD; } else if (/^@?[A-Z][A-Z$]*[a-z][A-Za-z$]*$/.test(token)) { subDecs[j + 1] = token.charAt(0) === '@' ? PR_LITERAL : PR_TYPE; } } } spliceArrayInto(subDecs, decorations, i, 2); i += subDecs.length - 2; } } return decorations; } return function(sourceCode) { var decorations = splitStringAndCommentTokens(sourceCode); decorations = splitNonStringNonCommentTokens(sourceCode, decorations); return decorations; }; } var decorateSource = sourceDecorator({ keywords: ALL_KEYWORDS, hashComments: true, cStyleComments: true, multiLineStrings: true, regexLiterals: true }); function splitSourceNodes(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var subDecorations = decorateSource(source.substring(start, end)); for (var j = 0, m = subDecorations.length; j < m; j += 2) { subDecorations[j] += start; } spliceArrayInto(subDecorations, decorations, i, 2); i += subDecorations.length - 2; } } return decorations; } function splitSourceAttributes(source, decorations) { var nextValueIsSource = false; for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; var start, end; if (style === PR_ATTRIB_NAME) { start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; nextValueIsSource = /^on|^style$/i.test(source.substring(start, end)); } else if (style === PR_ATTRIB_VALUE) { if (nextValueIsSource) { start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var attribValue = source.substring(start, end); var attribLen = attribValue.length; var quoted = (attribLen >= 2 && /^[\"\']/.test(attribValue) && attribValue.charAt(0) === attribValue.charAt(attribLen - 1)); var attribSource; var attribSourceStart; var attribSourceEnd; if (quoted) { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue; } else { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue.substring(1, attribValue.length - 1); } var attribSourceDecorations = decorateSource(attribSource); for (var j = 0, m = attribSourceDecorations.length; j < m; j += 2) { attribSourceDecorations[j] += attribSourceStart; } if (quoted) { attribSourceDecorations.push(attribSourceEnd, PR_ATTRIB_VALUE); spliceArrayInto(attribSourceDecorations, decorations, i + 2, 0); } else { spliceArrayInto(attribSourceDecorations, decorations, i, 2); } } nextValueIsSource = false; } } return decorations; } function decorateMarkup(sourceCode) { var decorations = tokenizeMarkup(sourceCode); decorations = splitTagAttributes(sourceCode, decorations); decorations = splitSourceNodes(sourceCode, decorations); decorations = splitSourceAttributes(sourceCode, decorations); return decorations; } function recombineTagsAndDecorations(sourceText, extractedTags, decorations) { var html = []; var outputIdx = 0; var openDecoration = null; var currentDecoration = null; var tagPos = 0; var decPos = 0; var tabExpander = makeTabExpander(PR_TAB_WIDTH); var adjacentSpaceRe = /([\r\n ]) /g; var startOrSpaceRe = /(^| ) /gm; var newlineRe = /\r\n?|\n/g; var trailingSpaceRe = /[ \r\n]$/; var lastWasSpace = true; function emitTextUpTo(sourceIdx) { if (sourceIdx > outputIdx) { if (openDecoration && openDecoration !== currentDecoration) { html.push('</span>'); openDecoration = null; } if (!openDecoration && currentDecoration) { openDecoration = currentDecoration; html.push('<span class="', openDecoration, '">'); } var htmlChunk = textToHtml(tabExpander(sourceText.substring(outputIdx, sourceIdx))).replace(lastWasSpace ? startOrSpaceRe : adjacentSpaceRe, '$1&nbsp;'); lastWasSpace = trailingSpaceRe.test(htmlChunk); html.push(htmlChunk.replace(newlineRe, '<br />')); outputIdx = sourceIdx; } } while (true) { var outputTag; if (tagPos < extractedTags.length) { if (decPos < decorations.length) { outputTag = extractedTags[tagPos] <= decorations[decPos]; } else { outputTag = true; } } else { outputTag = false; } if (outputTag) { emitTextUpTo(extractedTags[tagPos]); if (openDecoration) { html.push('</span>'); openDecoration = null; } html.push(extractedTags[tagPos + 1]); tagPos += 2; } else if (decPos < decorations.length) { emitTextUpTo(decorations[decPos]); currentDecoration = decorations[decPos + 1]; decPos += 2; } else { break; } } emitTextUpTo(sourceText.length); if (openDecoration) { html.push('</span>'); } return html.join(''); } var langHandlerRegistry = {}; function registerLangHandler(handler, fileExtensions) { for (var i = fileExtensions.length; --i >= 0; ) { var ext = fileExtensions[i]; if (!langHandlerRegistry.hasOwnProperty(ext)) { langHandlerRegistry[ext] = handler; } else if ('console' in window) { console.log('cannot override language handler %s', ext); } } } registerLangHandler(decorateSource, ['default-code']); registerLangHandler(decorateMarkup, ['default-markup', 'html', 'htm', 'xhtml', 'xml', 'xsl']); registerLangHandler(sourceDecorator({ keywords: CPP_KEYWORDS, hashComments: true, cStyleComments: true }), ['c', 'cc', 'cpp', 'cs', 'cxx', 'cyc']); registerLangHandler(sourceDecorator({ keywords: JAVA_KEYWORDS, cStyleComments: true }), ['java']); registerLangHandler(sourceDecorator({ keywords: SH_KEYWORDS, hashComments: true, multiLineStrings: true }), ['bsh', 'csh', 'sh']); registerLangHandler(sourceDecorator({ keywords: PYTHON_KEYWORDS, hashComments: true, multiLineStrings: true, tripleQuotedStrings: true }), ['cv', 'py']); registerLangHandler(sourceDecorator({ keywords: PERL_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['perl', 'pl', 'pm']); registerLangHandler(sourceDecorator({ keywords: RUBY_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['rb']); registerLangHandler(sourceDecorator({ keywords: JSCRIPT_KEYWORDS, cStyleComments: true, regexLiterals: true }), ['js']); function prettyPrintOne(sourceCodeHtml, opt_langExtension) { try { var sourceAndExtractedTags = extractTags(sourceCodeHtml); var source = sourceAndExtractedTags.source; var extractedTags = sourceAndExtractedTags.tags; if (!langHandlerRegistry.hasOwnProperty(opt_langExtension)) { opt_langExtension = /^\s*</.test(source) ? 'default-markup' : 'default-code'; } var decorations = langHandlerRegistry[opt_langExtension].call({}, source); return recombineTagsAndDecorations(source, extractedTags, decorations); } catch (e) { if ('console' in window) { console.log(e); console.trace(); } return sourceCodeHtml; } } function prettyPrint(opt_whenDone) { var isIE6 = _pr_isIE6(); var codeSegments = [document.getElementsByTagName('pre'), document.getElementsByTagName('code'), document.getElementsByTagName('xmp')]; var elements = []; for (var i = 0; i < codeSegments.length; ++i) { for (var j = 0; j < codeSegments[i].length; ++j) { elements.push(codeSegments[i][j]); } } codeSegments = null; var k = 0; function doWork() { var endTime = (PR_SHOULD_USE_CONTINUATION ? new Date().getTime() + 250 : Infinity); for (; k < elements.length && new Date().getTime() < endTime; k++) { var cs = elements[k]; if (cs.className && cs.className.indexOf('prettyprint') >= 0) { var langExtension = cs.className.match(/\blang-(\w+)\b/); if (langExtension) { langExtension = langExtension[1]; } var nested = false; for (var p = cs.parentNode; p; p = p.parentNode) { if ((p.tagName === 'pre' || p.tagName === 'code' || p.tagName === 'xmp') && p.className && p.className.indexOf('prettyprint') >= 0) { nested = true; break; } } if (!nested) { var content = getInnerHtml(cs); content = content.replace(/(?:\r\n?|\n)$/, ''); var newContent = prettyPrintOne(content, langExtension); if (!isRawContent(cs)) { cs.innerHTML = newContent; } else { var pre = document.createElement('PRE'); for (var i = 0; i < cs.attributes.length; ++i) { var a = cs.attributes[i]; if (a.specified) { var aname = a.name.toLowerCase(); if (aname === 'class') { pre.className = a.value; } else { pre.setAttribute(a.name, a.value); } } } pre.innerHTML = newContent; cs.parentNode.replaceChild(pre, cs); cs = pre; } if (isIE6 && cs.tagName === 'PRE') { var lineBreaks = cs.getElementsByTagName('br'); for (var j = lineBreaks.length; --j >= 0; ) { var lineBreak = lineBreaks[j]; lineBreak.parentNode.replaceChild(document.createTextNode('\r\n'), lineBreak); } } } } } if (k < elements.length) { setTimeout(doWork, 250); } else if (opt_whenDone) { opt_whenDone(); } } doWork(); } window['PR_normalizedHtml'] = normalizedHtml; window['prettyPrintOne'] = prettyPrintOne; window['prettyPrint'] = prettyPrint; window['PR'] = { 'createSimpleLexer': createSimpleLexer, 'registerLangHandler': registerLangHandler, 'sourceDecorator': sourceDecorator, 'PR_ATTRIB_NAME': PR_ATTRIB_NAME, 'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE, 'PR_COMMENT': PR_COMMENT, 'PR_DECLARATION': PR_DECLARATION, 'PR_KEYWORD': PR_KEYWORD, 'PR_LITERAL': PR_LITERAL, 'PR_NOCODE': PR_NOCODE, 'PR_PLAIN': PR_PLAIN, 'PR_PUNCTUATION': PR_PUNCTUATION, 'PR_SOURCE': PR_SOURCE, 'PR_STRING': PR_STRING, 'PR_TAG': PR_TAG, 'PR_TYPE': PR_TYPE }; })(); \ No newline at end of file
+/*
+Scripts for cnprog.com
+Project Name: Lanai
+All Rights Resevred 2008. CNPROG.COM
+*/
+var lanai =
+{
+ /**
+ * Finds any <pre><code></code></pre> tags which aren't registered for
+ * pretty printing, adds the appropriate class name and invokes prettify.
+ */
+ highlightSyntax: function(){
+ var styled = false;
+ $("pre code").parent().each(function(){
+ if (!$(this).hasClass('prettyprint')){
+ $(this).addClass('prettyprint');
+ styled = true;
+ }
+ });
+
+ if (styled){
+ prettyPrint();
+ }
+ }
+};
+
+var Vote = function(){
+ // All actions are related to a question
+ var questionId;
+ // The object we operate on actually. It can be a question or an answer.
+ var postId;
+ var questionAuthorId;
+ var currentUserId;
+ var answerContainerIdPrefix = 'answer-container-';
+ var voteContainerId = 'vote-buttons';
+ var imgIdPrefixAccept = 'answer-img-accept-';
+ var imgClassPrefixFavorite = 'question-img-favorite';
+ var imgIdPrefixQuestionVoteup = 'question-img-upvote-';
+ var imgIdPrefixQuestionVotedown = 'question-img-downvote-';
+ var imgIdPrefixAnswerVoteup = 'answer-img-upvote-';
+ var imgIdPrefixAnswerVotedown = 'answer-img-downvote-';
+ var divIdFavorite = 'favorite-number';
+ var commentLinkIdPrefix = 'comment-';
+ var voteNumberClass = "vote-number";
+ var offensiveIdPrefixQuestionFlag = 'question-offensive-flag-';
+ var offensiveIdPrefixAnswerFlag = 'answer-offensive-flag-';
+ var offensiveClassFlag = 'offensive-flag';
+ var questionControlsId = 'question-controls';
+ var removeQuestionLinkIdPrefix = 'question-delete-link-';
+ var removeAnswerLinkIdPrefix = 'answer-delete-link-';
+ var questionSubscribeUpdates = 'question-subscribe-updates';
+
+ var acceptAnonymousMessage = $.i18n._('insufficient privilege');
+ var acceptOwnAnswerMessage = $.i18n._('cannot pick own answer as best');
+ var favoriteAnonymousMessage = $.i18n._('anonymous users cannot select favorite questions')
+ + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
+ + $.i18n._('please login') + "</a>";
+ var voteAnonymousMessage = $.i18n._('anonymous users cannot vote')
+ + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
+ + $.i18n._('please login') + "</a>";
+ var upVoteRequiredScoreMessage = $.i18n._('>15 points requried to upvote')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var downVoteRequiredScoreMessage = $.i18n._('>100 points requried to downvote')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var voteOwnDeniedMessage = $.i18n._('cannot vote for own posts');
+ var voteRequiredMoreVotes = $.i18n._('daily vote cap exhausted')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var voteDenyCancelMessage = $.i18n._('cannot revoke old vote')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var offensiveConfirmation = $.i18n._('please confirm offensive');
+ var offensiveAnonymousMessage = $.i18n._('anonymous users cannot flag offensive posts')
+ + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
+ + $.i18n._('please login') + "</a>";
+ var offensiveTwiceMessage = $.i18n._('cannot flag message as offensive twice')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var offensiveNoFlagsLeftMessage = $.i18n._('flag offensive cap exhausted')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var offensiveNoPermissionMessage = $.i18n._('need >15 points to report spam')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var removeConfirmation = $.i18n._('confirm delete');
+ var removeAnonymousMessage = $.i18n._('anonymous users cannot delete/undelete');
+ var recoveredMessage = $.i18n._('post recovered');
+ var deletedMessage = $.i18n._('post deleted');
+
+ var VoteType = {
+ acceptAnswer : 0,
+ questionUpVote : 1,
+ questionDownVote : 2,
+ favorite : 4,
+ answerUpVote: 5,
+ answerDownVote:6,
+ offensiveQuestion : 7,
+ offensiveAnswer:8,
+ removeQuestion: 9,
+ removeAnswer:10,
+ questionSubscribeUpdates:11,
+ questionUnsubscribeUpdates:12,
+ };
+
+ var getFavoriteButton = function(){
+ var favoriteButton = 'div.'+ voteContainerId +' img[class='+ imgClassPrefixFavorite +']';
+ return $(favoriteButton);
+ };
+ var getFavoriteNumber = function(){
+ var favoriteNumber = '#'+ divIdFavorite ;
+ return $(favoriteNumber);
+ };
+ var getQuestionVoteUpButton = function(){
+ var questionVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVoteup +']';
+ return $(questionVoteUpButton);
+ };
+ var getQuestionVoteDownButton = function(){
+ var questionVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVotedown +']';
+ return $(questionVoteDownButton);
+ };
+ var getAnswerVoteUpButtons = function(){
+ var answerVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVoteup +']';
+ return $(answerVoteUpButton);
+ };
+ var getAnswerVoteDownButtons = function(){
+ var answerVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVotedown +']';
+ return $(answerVoteDownButton);
+ };
+ var getAnswerVoteUpButton = function(id){
+ var answerVoteUpButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVoteup + id + ']';
+ return $(answerVoteUpButton);
+ };
+ var getAnswerVoteDownButton = function(id){
+ var answerVoteDownButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVotedown + id + ']';
+ return $(answerVoteDownButton);
+ };
+
+ var getOffensiveQuestionFlag = function(){
+ var offensiveQuestionFlag = 'table[id=question-table] span[class='+ offensiveClassFlag +']';
+ return $(offensiveQuestionFlag);
+ };
+
+ var getOffensiveAnswerFlags = function(){
+ var offensiveQuestionFlag = 'div.answer span[class='+ offensiveClassFlag +']';
+ return $(offensiveQuestionFlag);
+ };
+
+ var getremoveQuestionLink = function(){
+ var removeQuestionLink = 'div#question-controls a[id^='+ removeQuestionLinkIdPrefix +']';
+ return $(removeQuestionLink);
+ };
+
+ var getquestionSubscribeUpdatesCheckbox = function(){
+ return $('#' + questionSubscribeUpdates);
+ };
+
+ var getremoveAnswersLinks = function(){
+ var removeAnswerLinks = 'div.answer-controls a[id^='+ removeAnswerLinkIdPrefix +']';
+ return $(removeAnswerLinks);
+ };
+
+ var setVoteImage = function(voteType, undo, object){
+ var flag = undo ? "" : "-on";
+ var arrow = (voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote) ? "up" : "down";
+ object.attr("src", "/content/images/vote-arrow-"+ arrow + flag +".png");
+
+ // if undo voting, then undo the pair of arrows.
+ if(undo){
+ if(voteType == VoteType.questionUpVote || voteType == VoteType.questionDownVote){
+ $(getQuestionVoteUpButton()).attr("src", "/content/images/vote-arrow-up.png");
+ $(getQuestionVoteDownButton()).attr("src", "/content/images/vote-arrow-down.png");
+ }
+ else{
+ $(getAnswerVoteUpButton(postId)).attr("src", "/content/images/vote-arrow-up.png");
+ $(getAnswerVoteDownButton(postId)).attr("src", "/content/images/vote-arrow-down.png");
+ }
+ }
+ };
+
+ var setVoteNumber = function(object, number){
+ var voteNumber = object.parent('div.'+ voteContainerId).find('div.'+ voteNumberClass);
+ $(voteNumber).text(number);
+ };
+
+ var bindEvents = function(){
+ // accept answers
+ if(questionAuthorId == currentUserId){
+ var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
+ $(acceptedButtons).unbind('click').click(function(event){
+ Vote.accept($(event.target))
+ });
+ }
+ // set favorite question
+ var favoriteButton = getFavoriteButton();
+ favoriteButton.unbind('click').click(function(event){
+ Vote.favorite($(event.target))
+ });
+
+ // question vote up
+ var questionVoteUpButton = getQuestionVoteUpButton();
+ questionVoteUpButton.unbind('click').click(function(event){
+ Vote.vote($(event.target), VoteType.questionUpVote)
+ });
+
+ var questionVoteDownButton = getQuestionVoteDownButton();
+ questionVoteDownButton.unbind('click').click(function(event){
+ Vote.vote($(event.target), VoteType.questionDownVote)
+ });
+
+ var answerVoteUpButton = getAnswerVoteUpButtons();
+ answerVoteUpButton.unbind('click').click(function(event){
+ Vote.vote($(event.target), VoteType.answerUpVote)
+ });
+
+ var answerVoteDownButton = getAnswerVoteDownButtons();
+ answerVoteDownButton.unbind('click').click(function(event){
+ Vote.vote($(event.target), VoteType.answerDownVote)
+ });
+
+ getOffensiveQuestionFlag().unbind('click').click(function(event){
+ Vote.offensive(this, VoteType.offensiveQuestion)
+ });
+
+ getOffensiveAnswerFlags().unbind('click').click(function(event){
+ Vote.offensive(this, VoteType.offensiveAnswer)
+ });
+
+ getremoveQuestionLink().unbind('click').click(function(event){
+ Vote.remove(this, VoteType.removeQuestion);
+ });
+
+ getquestionSubscribeUpdatesCheckbox().unbind('click').click(function(event){
+ if (this.checked){
+ Vote.vote($(event.target), VoteType.questionSubscribeUpdates);
+ }
+ else {
+ Vote.vote($(event.target), VoteType.questionUnsubscribeUpdates);
+ }
+ });
+
+ getremoveAnswersLinks().unbind('click').click(function(event){
+ Vote.remove(this, VoteType.removeAnswer)
+ });
+ };
+
+ var submit = function(object, voteType, callback) {
+ $.ajax({
+ type: "POST",
+ cache: false,
+ dataType: "json",
+ url: "/questions/" + questionId + "/vote/",
+ data: { "type": voteType, "postId": postId },
+ error: handleFail,
+ success: function(data){callback(object, voteType, data)}});
+ };
+
+ var handleFail = function(xhr, msg){
+ alert("Callback invoke error: " + msg)
+ };
+
+ // callback function for Accept Answer action
+ var callback_accept = function(object, voteType, data){
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, acceptAnonymousMessage);
+ }
+ else if(data.allowed == "-1"){
+ showMessage(object, acceptOwnAnswerMessage);
+ }
+ else if(data.status == "1"){
+ object.attr("src", "/content/images/vote-accepted.png");
+ $("#"+answerContainerIdPrefix+postId).removeClass("accepted-answer");
+ $("#"+commentLinkIdPrefix+postId).removeClass("comment-link-accepted");
+ }
+ else if(data.success == "1"){
+ var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
+ $(acceptedButtons).attr("src", "/content/images/vote-accepted.png");
+ var answers = ("div[id^="+answerContainerIdPrefix +"]");
+ $(answers).removeClass("accepted-answer");
+ var commentLinks = ("div[id^="+answerContainerIdPrefix +"] div[id^="+ commentLinkIdPrefix +"]");
+ $(commentLinks).removeClass("comment-link-accepted");
+
+ object.attr("src", "/content/images/vote-accepted-on.png");
+ $("#"+answerContainerIdPrefix+postId).addClass("accepted-answer");
+ $("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted");
+ }
+ else{
+ showMessage(object, data.message);
+ }
+ };
+
+ var callback_favorite = function(object, voteType, data){
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
+ }
+ else if(data.status == "1"){
+ object.attr("src", "/content/images/vote-favorite-off.png");
+ var fav = getFavoriteNumber();
+ fav.removeClass("my-favorite-number");
+ if(data.count == 0)
+ data.count = '';
+ fav.text(data.count);
+ }
+ else if(data.success == "1"){
+ object.attr("src", "/content/images/vote-favorite-on.png");
+ var fav = getFavoriteNumber();
+ fav.text(data.count);
+ fav.addClass("my-favorite-number");
+ }
+ else{
+ showMessage(object, data.message);
+ }
+ };
+
+ var callback_vote = function(object, voteType, data){
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
+ }
+ else if(data.allowed == "-3"){
+ showMessage(object, voteRequiredMoreVotes);
+ }
+ else if(data.allowed == "-2"){
+ if(voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote){
+ showMessage(object, upVoteRequiredScoreMessage);
+ }
+ else if(voteType == VoteType.questionDownVote || voteType == VoteType.answerDownVote){
+ showMessage(object, downVoteRequiredScoreMessage);
+ }
+ }
+ else if(data.allowed == "-1"){
+ showMessage(object, voteOwnDeniedMessage);
+ }
+ else if(data.status == "2"){
+ showMessage(object, voteDenyCancelMessage);
+ }
+ else if(data.status == "1"){
+ setVoteImage(voteType, true, object);
+ setVoteNumber(object, data.count);
+ }
+ else if(data.success == "1"){
+ setVoteImage(voteType, false, object);
+ setVoteNumber(object, data.count);
+ if(data.message.length > 0)
+ showMessage(object, data.message);
+ }
+ };
+
+ var callback_offensive = function(object, voteType, data){
+ object = $(object);
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
+ }
+ else if(data.allowed == "-3"){
+ showMessage(object, offensiveNoFlagsLeftMessage);
+ }
+ else if(data.allowed == "-2"){
+ showMessage(object, offensiveNoPermissionMessage);
+ }
+ else if(data.status == "1"){
+ showMessage(object, offensiveTwiceMessage);
+ }
+ else if(data.success == "1"){
+ $(object).children('span[class=darkred]').text("("+ data.count +")");
+ }
+ };
+
+ var callback_remove = function(object, voteType, data){
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, removeAnonymousMessage.replace("{{QuestionID}}", questionId));
+ }
+ else if (data.success == "1"){
+ if (removeActionType == 'delete'){
+ postNode.addClass('deleted');
+ postRemoveLink.innerHTML = $.i18n._('undelete');
+ showMessage(object, deletedMessage);
+ }
+ else if (removeActionType == 'undelete') {
+ postNode.removeClass('deleted');
+ postRemoveLink.innerHTML = $.i18n._('delete');
+ showMessage(object, recoveredMessage);
+ }
+ }
+ };
+
+ return {
+ init : function(qId, questionAuthor, userId){
+ questionId = qId;
+ questionAuthorId = questionAuthor;
+ currentUserId = userId;
+ bindEvents();
+ },
+
+ // Accept answer public function
+ accept: function(object){
+ postId = object.attr("id").substring(imgIdPrefixAccept.length);
+ submit(object, VoteType.acceptAnswer, callback_accept);
+ },
+
+ favorite: function(object){
+ if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
+ return false;
+ }
+ submit(object, VoteType.favorite, callback_favorite);
+ },
+
+ vote: function(object, voteType){
+ if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
+ return false;
+ }
+ if(voteType == VoteType.answerUpVote){
+ postId = object.attr("id").substring(imgIdPrefixAnswerVoteup.length);
+ }
+ else if(voteType == VoteType.answerDownVote){
+ postId = object.attr("id").substring(imgIdPrefixAnswerVotedown.length);
+ }
+
+ submit(object, voteType, callback_vote);
+ },
+
+ offensive: function(object, voteType){
+ if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage($(object), offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
+ return false;
+ }
+ if(confirm(offensiveConfirmation)){
+ postId = object.id.substr(object.id.lastIndexOf('-') + 1);
+ submit(object, voteType, callback_offensive);
+ }
+ },
+
+ remove: function(object, voteType){
+ if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage($(object), removeAnonymousMessage.replace("{{QuestionID}}", questionId));
+ return false;
+ }
+ if(confirm(removeConfirmation)){
+ bits = object.id.split('-');
+ postId = bits.pop();/* this seems to be used within submit! */
+ postType = bits.shift();
+
+ if (postType == 'answer'){
+ postNode = $('#answer-container-' + postId);
+ postRemoveLink = object;
+ if (postNode.hasClass('deleted')){
+ removeActionType = 'undelete';
+ }
+ else {
+ removeActionType = 'delete';
+ }
+ }
+ submit($(object), voteType, callback_remove);
+
+
+ }
+ }
+ }
+} ();
+
+
+// site comments
+function createComments(type) {
+ var objectType = type;
+ var jDivInit = function(id) {
+ return $("#comments-" + objectType + '-' + id);
+ };
+
+ var appendLoaderImg = function(id) {
+ appendLoader("#comments-" + objectType + '-' + id + " div.comments");
+ };
+
+ var canPostComments = function(id, jDiv) {
+ var jHidden = jDiv.siblings("#can-post-comments-" + objectType + '-' + id);
+ return jHidden.val().toLowerCase() == "true";
+ };
+
+ var renderForm = function(id, jDiv) {
+ var formId = "form-comments-" + objectType + "-" + id;
+ if (canPostComments(id, jDiv)) {
+ 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 += '<span class="form-error"></span></div></form>';
+
+ jDiv.append(form);
+
+ setupFormValidation("#" + formId,
+ { comment: { required: true, minlength: 10} }, '',
+ function() { postComment(id, formId); });
+ }
+ }
+ else {
+ var divId = "comments-rep-needed-" + objectType + '-' + id;
+ if (jDiv.find("#" + divId).length == 0) {
+ jDiv.append('<div id="' + divId + '" style="color:red">'
+ + $.i18n._('to comment, need') + ' ' +
+ + repNeededForComments + ' ' + $.i18n._('community reputation points')
+ + '<a href="/faq" class="comment-user">' + $.i18n._('please see') + 'faq</a></span>');
+ }
+ }
+ };
+
+ var getComments = function(id, jDiv) {
+ appendLoaderImg(id);
+ $.getJSON("/" + objectType + "s/" + id + "/comments/", function(json) { showComments(id, json); });
+ };
+
+ var showComments = function(id, json) {
+ var jDiv = jDivInit(id);
+
+ jDiv = jDiv.find("div.comments"); // this div should contain any fetched comments..
+ jDiv.find("div[id^='comment-" + objectType + "-'" + "]").remove(); // clean previous calls..
+
+ removeLoader();
+
+ if (json && json.length > 0) {
+ for (var i = 0; i < json.length; i++)
+ renderComment(jDiv, json[i]);
+
+ jDiv.children().show();
+ }
+ };
+
+ // {"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) {
+ var html = '<div id="comment-' + objectType + "-" + json.id + '" style="display:none">' + json.text;
+ html += json.user_url ? '&nbsp;&ndash;&nbsp;<a href="' + json.user_url + '"' : '<span';
+ html += ' class="comment-user">' + json.user_display_name + (json.user_url ? '</a>' : '</span>');
+ html += ' <span class="comment-date">(' + json.add_date + ')</span>';
+
+ if (json.delete_url) {
+ var img = "/content/images/close-small.png";
+ var imgHover = "/content/images/close-small-hover.png";
+ html += '<img onclick="' + objectType + 'Comments.deleteComment($(this), ' + json.object_id + ', \'' + json.delete_url + '\')" src="' + img;
+ html += '" onmouseover="$(this).attr(\'src\', \'' + imgHover + '\')" onmouseout="$(this).attr(\'src\', \'' + img
+ html += '\')" title="' + $.i18n._('delete this comment') + '" />';
+ }
+
+ html += '</div>';
+
+ jDiv.append(html);
+ };
+
+ var postComment = function(id, formId) {
+ appendLoaderImg(id);
+
+ var formSelector = "#" + formId;
+ var textarea = $(formSelector + " textarea");
+
+ $.ajax({
+ type: "POST",
+ url: "/" + objectType + "s/" + id + "/comments/",
+ dataType: "json",
+ data: { comment: textarea.val() },
+ success: function(json) {
+ showComments(id, json);
+ textarea.val("");
+ commentsFactory[objectType].updateTextCounter(textarea);
+ enableSubmitButton(formSelector);
+ },
+ error: function(res, textStatus, errorThrown) {
+ removeLoader();
+ showMessage(formSelector, res.responseText);
+ enableSubmitButton(formSelector);
+ }
+ });
+ };
+
+ // public methods..
+ return {
+
+ init: function() {
+ // Setup "show comments" clicks..
+ $("a[id^='comments-link-" + objectType + "-" + "']").unbind("click").click(function() { commentsFactory[objectType].show($(this).attr("id").substr(("comments-link-" + objectType + "-").length)); });
+ },
+
+ show: function(id) {
+ var jDiv = jDivInit(id);
+ getComments(id, jDiv);
+ renderForm(id, jDiv);
+ jDiv.show();
+ if (canPostComments(id, jDiv)) jDiv.find("textarea").get(0).focus();
+ jDiv.siblings("a").unbind("click").click(function(){
+ commentsFactory[objectType].hide(id);
+ }).text($.i18n._('hide comments'));
+ },
+
+ 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>)";
+
+ jDiv.hide();
+ jDiv.siblings("a").unbind("click").click(function() { commentsFactory[objectType].show(id); }).html(anchorText);
+ jDiv.children("div.comments").children().hide();
+ },
+
+ deleteComment: function(jImg, id, deleteUrl) {
+ if (confirm($.i18n._('confirm delete comment'))) {
+ jImg.hide();
+ appendLoaderImg(id);
+ $.post(deleteUrl, { dataNeeded: "forIIS7" }, function(json) {
+ showComments(id, json);
+ }, "json");
+ }
+ },
+
+ updateTextCounter: function(textarea) {
+ 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);
+ }
+ };
+}
+
+var questionComments = createComments('question');
+var answerComments = createComments('answer');
+
+$().ready(function() {
+ questionComments.init();
+ answerComments.init();
+});
+
+var commentsFactory = {'question' : questionComments, 'answer' : answerComments};
+
+/*
+Prettify
+http://www.apache.org/licenses/LICENSE-2.0
+*/
+var PR_SHOULD_USE_CONTINUATION = true; var PR_TAB_WIDTH = 8; var PR_normalizedHtml; var PR; var prettyPrintOne; var prettyPrint; function _pr_isIE6() { var isIE6 = navigator && navigator.userAgent && /\bMSIE 6\./.test(navigator.userAgent); _pr_isIE6 = function() { return isIE6; }; return isIE6; } (function() { function wordSet(words) { words = words.split(/ /g); var set = {}; for (var i = words.length; --i >= 0; ) { var w = words[i]; if (w) { set[w] = null; } } return set; } var FLOW_CONTROL_KEYWORDS = "break continue do else for if return while "; var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " + "double enum extern float goto int long register short signed sizeof " + "static struct switch typedef union unsigned void volatile "; var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " + "new operator private protected public this throw true try "; var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " + "concept concept_map const_cast constexpr decltype " + "dynamic_cast explicit export friend inline late_check " + "mutable namespace nullptr reinterpret_cast static_assert static_cast " + "template typeid typename typeof using virtual wchar_t where "; var JAVA_KEYWORDS = COMMON_KEYWORDS + "boolean byte extends final finally implements import instanceof null " + "native package strictfp super synchronized throws transient "; var CSHARP_KEYWORDS = JAVA_KEYWORDS + "as base by checked decimal delegate descending event " + "fixed foreach from group implicit in interface internal into is lock " + "object out override orderby params readonly ref sbyte sealed " + "stackalloc string select uint ulong unchecked unsafe ushort var "; var JSCRIPT_KEYWORDS = COMMON_KEYWORDS + "debugger eval export function get null set undefined var with " + "Infinity NaN "; var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " + "goto if import last local my next no our print package redo require " + "sub undef unless until use wantarray while BEGIN END "; var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " + "elif except exec finally from global import in is lambda " + "nonlocal not or pass print raise try with yield " + "False True None "; var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" + " defined elsif end ensure false in module next nil not or redo rescue " + "retry self super then true undef unless until when yield BEGIN END "; var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " + "function in local set then until "; var ALL_KEYWORDS = (CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS + PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS); var PR_STRING = 'str'; var PR_KEYWORD = 'kwd'; var PR_COMMENT = 'com'; var PR_TYPE = 'typ'; var PR_LITERAL = 'lit'; var PR_PUNCTUATION = 'pun'; var PR_PLAIN = 'pln'; var PR_TAG = 'tag'; var PR_DECLARATION = 'dec'; var PR_SOURCE = 'src'; var PR_ATTRIB_NAME = 'atn'; var PR_ATTRIB_VALUE = 'atv'; var PR_NOCODE = 'nocode'; function isWordChar(ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); } function spliceArrayInto(inserted, container, containerPosition, countReplaced) { inserted.unshift(containerPosition, countReplaced || 0); try { container.splice.apply(container, inserted); } finally { inserted.splice(0, 2); } } var REGEXP_PRECEDER_PATTERN = function() { var preceders = ["!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=", "&=", "(", "*", "*=", "+=", ",", "-=", "->", "/", "/=", ":", "::", ";", "<", "<<", "<<=", "<=", "=", "==", "===", ">", ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[", "^", "^=", "^^", "^^=", "{", "|", "|=", "||", "||=", "~", "break", "case", "continue", "delete", "do", "else", "finally", "instanceof", "return", "throw", "try", "typeof"]; var pattern = '(?:' + '(?:(?:^|[^0-9.])\\.{1,3})|' + '(?:(?:^|[^\\+])\\+)|' + '(?:(?:^|[^\\-])-)'; for (var i = 0; i < preceders.length; ++i) { var preceder = preceders[i]; if (isWordChar(preceder.charAt(0))) { pattern += '|\\b' + preceder; } else { pattern += '|' + preceder.replace(/([^=<>:&])/g, '\\$1'); } } pattern += '|^)\\s*$'; return new RegExp(pattern); } (); var pr_amp = /&/g; var pr_lt = /</g; var pr_gt = />/g; var pr_quot = /\"/g; function attribToHtml(str) { return str.replace(pr_amp, '&amp;').replace(pr_lt, '&lt;').replace(pr_gt, '&gt;').replace(pr_quot, '&quot;'); } function textToHtml(str) { return str.replace(pr_amp, '&amp;').replace(pr_lt, '&lt;').replace(pr_gt, '&gt;'); } var pr_ltEnt = /&lt;/g; var pr_gtEnt = /&gt;/g; var pr_aposEnt = /&apos;/g; var pr_quotEnt = /&quot;/g; var pr_ampEnt = /&amp;/g; var pr_nbspEnt = /&nbsp;/g; function htmlToText(html) { var pos = html.indexOf('&'); if (pos < 0) { return html; } for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0; ) { var end = html.indexOf(';', pos); if (end >= 0) { var num = html.substring(pos + 3, end); var radix = 10; if (num && num.charAt(0) === 'x') { num = num.substring(1); radix = 16; } var codePoint = parseInt(num, radix); if (!isNaN(codePoint)) { html = (html.substring(0, pos) + String.fromCharCode(codePoint) + html.substring(end + 1)); } } } return html.replace(pr_ltEnt, '<').replace(pr_gtEnt, '>').replace(pr_aposEnt, "'").replace(pr_quotEnt, '"').replace(pr_ampEnt, '&').replace(pr_nbspEnt, ' '); } function isRawContent(node) { return 'XMP' === node.tagName; } function normalizedHtml(node, out) { switch (node.nodeType) { case 1: var name = node.tagName.toLowerCase(); out.push('<', name); for (var i = 0; i < node.attributes.length; ++i) { var attr = node.attributes[i]; if (!attr.specified) { continue; } out.push(' '); normalizedHtml(attr, out); } out.push('>'); for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } if (node.firstChild || !/^(?:br|link|img)$/.test(name)) { out.push('<\/', name, '>'); } break; case 2: out.push(node.name.toLowerCase(), '="', attribToHtml(node.value), '"'); break; case 3: case 4: out.push(textToHtml(node.nodeValue)); break; } } var PR_innerHtmlWorks = null; function getInnerHtml(node) { if (null === PR_innerHtmlWorks) { var testNode = document.createElement('PRE'); testNode.appendChild(document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />')); PR_innerHtmlWorks = !/</.test(testNode.innerHTML); } if (PR_innerHtmlWorks) { var content = node.innerHTML; if (isRawContent(node)) { content = textToHtml(content); } return content; } var out = []; for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } return out.join(''); } function makeTabExpander(tabWidth) { var SPACES = ' '; var charInLine = 0; return function(plainText) { var out = null; var pos = 0; for (var i = 0, n = plainText.length; i < n; ++i) { var ch = plainText.charAt(i); switch (ch) { case '\t': if (!out) { out = []; } out.push(plainText.substring(pos, i)); var nSpaces = tabWidth - (charInLine % tabWidth); charInLine += nSpaces; for (; nSpaces >= 0; nSpaces -= SPACES.length) { out.push(SPACES.substring(0, nSpaces)); } pos = i + 1; break; case '\n': charInLine = 0; break; default: ++charInLine; } } if (!out) { return plainText; } out.push(plainText.substring(pos)); return out.join(''); }; } var pr_chunkPattern = /(?:[^<]+|<!--[\s\S]*?-->|<!\[CDATA\[([\s\S]*?)\]\]>|<\/?[a-zA-Z][^>]*>|<)/g; var pr_commentPrefix = /^<!--/; var pr_cdataPrefix = /^<\[CDATA\[/; var pr_brPrefix = /^<br\b/i; var pr_tagNameRe = /^<(\/?)([a-zA-Z]+)/; function extractTags(s) { var matches = s.match(pr_chunkPattern); var sourceBuf = []; var sourceBufLen = 0; var extractedTags = []; if (matches) { for (var i = 0, n = matches.length; i < n; ++i) { var match = matches[i]; if (match.length > 1 && match.charAt(0) === '<') { if (pr_commentPrefix.test(match)) { continue; } if (pr_cdataPrefix.test(match)) { sourceBuf.push(match.substring(9, match.length - 3)); sourceBufLen += match.length - 12; } else if (pr_brPrefix.test(match)) { sourceBuf.push('\n'); ++sourceBufLen; } else { if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) { var name = match.match(pr_tagNameRe)[2]; var depth = 1; end_tag_loop: for (var j = i + 1; j < n; ++j) { var name2 = matches[j].match(pr_tagNameRe); if (name2 && name2[2] === name) { if (name2[1] === '/') { if (--depth === 0) { break end_tag_loop; } } else { ++depth; } } } if (j < n) { extractedTags.push(sourceBufLen, matches.slice(i, j + 1).join('')); i = j; } else { extractedTags.push(sourceBufLen, match); } } else { extractedTags.push(sourceBufLen, match); } } } else { var literalText = htmlToText(match); sourceBuf.push(literalText); sourceBufLen += literalText.length; } } } return { source: sourceBuf.join(''), tags: extractedTags }; } function isNoCodeTag(tag) { return !!tag.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g, ' $1="$2$3$4"').match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/); } function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) { var shortcuts = {}; (function() { var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns); for (var i = allPatterns.length; --i >= 0; ) { var patternParts = allPatterns[i]; var shortcutChars = patternParts[3]; if (shortcutChars) { for (var c = shortcutChars.length; --c >= 0; ) { shortcuts[shortcutChars.charAt(c)] = patternParts; } } } })(); var nPatterns = fallthroughStylePatterns.length; var notWs = /\S/; return function(sourceCode, opt_basePos) { opt_basePos = opt_basePos || 0; var decorations = [opt_basePos, PR_PLAIN]; var lastToken = ''; var pos = 0; var tail = sourceCode; while (tail.length) { var style; var token = null; var match; var patternParts = shortcuts[tail.charAt(0)]; if (patternParts) { match = tail.match(patternParts[1]); token = match[0]; style = patternParts[0]; } else { for (var i = 0; i < nPatterns; ++i) { patternParts = fallthroughStylePatterns[i]; var contextPattern = patternParts[2]; if (contextPattern && !contextPattern.test(lastToken)) { continue; } match = tail.match(patternParts[1]); if (match) { token = match[0]; style = patternParts[0]; break; } } if (!token) { style = PR_PLAIN; token = tail.substring(0, 1); } } decorations.push(opt_basePos + pos, style); pos += token.length; tail = tail.substring(token.length); if (style !== PR_COMMENT && notWs.test(token)) { lastToken = token; } } return decorations; }; } var PR_MARKUP_LEXER = createSimpleLexer([], [[PR_PLAIN, /^[^<]+/, null], [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/, null], [PR_COMMENT, /^<!--[\s\S]*?(?:-->|$)/, null], [PR_SOURCE, /^<\?[\s\S]*?(?:\?>|$)/, null], [PR_SOURCE, /^<%[\s\S]*?(?:%>|$)/, null], [PR_SOURCE, /^<(script|style|xmp)\b[^>]*>[\s\S]*?<\/\1\b[^>]*>/i, null], [PR_TAG, /^<\/?\w[^<>]*>/, null]]); var PR_SOURCE_CHUNK_PARTS = /^(<[^>]*>)([\s\S]*)(<\/[^>]*>)$/; function tokenizeMarkup(source) { var decorations = PR_MARKUP_LEXER(source); for (var i = 0; i < decorations.length; i += 2) { if (decorations[i + 1] === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var sourceChunk = source.substring(start, end); var match = sourceChunk.match(PR_SOURCE_CHUNK_PARTS); if (match) { decorations.splice(i, 2, start, PR_TAG, start + match[1].length, PR_SOURCE, start + match[1].length + (match[2] || '').length, PR_TAG); } } } return decorations; } var PR_TAG_LEXER = createSimpleLexer([[PR_ATTRIB_VALUE, /^\'[^\']*(?:\'|$)/, null, "'"], [PR_ATTRIB_VALUE, /^\"[^\"]*(?:\"|$)/, null, '"'], [PR_PUNCTUATION, /^[<>\/=]+/, null, '<>/=']], [[PR_TAG, /^[\w:\-]+/, /^</], [PR_ATTRIB_VALUE, /^[\w\-]+/, /^=/], [PR_ATTRIB_NAME, /^[\w:\-]+/, null], [PR_PLAIN, /^\s+/, null, ' \t\r\n']]); function splitTagAttributes(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_TAG) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var chunk = source.substring(start, end); var subDecorations = PR_TAG_LEXER(chunk, start); spliceArrayInto(subDecorations, decorations, i, 2); i += subDecorations.length - 2; } } return decorations; } function sourceDecorator(options) { var shortcutStylePatterns = [], fallthroughStylePatterns = []; if (options.tripleQuotedStrings) { shortcutStylePatterns.push([PR_STRING, /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/, null, '\'"']); } else if (options.multiLineStrings) { shortcutStylePatterns.push([PR_STRING, /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/, null, '\'"`']); } else { shortcutStylePatterns.push([PR_STRING, /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/, null, '"\'']); } fallthroughStylePatterns.push([PR_PLAIN, /^(?:[^\'\"\`\/\#]+)/, null, ' \r\n']); if (options.hashComments) { shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']); } if (options.cStyleComments) { fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]); fallthroughStylePatterns.push([PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]); } if (options.regexLiterals) { var REGEX_LITERAL = ('^/(?=[^/*])' + '(?:[^/\\x5B\\x5C]' + '|\\x5C[\\s\\S]' + '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+' + '(?:/|$)'); fallthroughStylePatterns.push([PR_STRING, new RegExp(REGEX_LITERAL), REGEXP_PRECEDER_PATTERN]); } var keywords = wordSet(options.keywords); options = null; var splitStringAndCommentTokens = createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns); var styleLiteralIdentifierPuncRecognizer = createSimpleLexer([], [[PR_PLAIN, /^\s+/, null, ' \r\n'], [PR_PLAIN, /^[a-z_$@][a-z_$@0-9]*/i, null], [PR_LITERAL, /^0x[a-f0-9]+[a-z]/i, null], [PR_LITERAL, /^(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d+)(?:e[+\-]?\d+)?[a-z]*/i, null, '123456789'], [PR_PUNCTUATION, /^[^\s\w\.$@]+/, null]]); function splitNonStringNonCommentTokens(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_PLAIN) { var start, end, chunk, subDecs; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; chunk = source.substring(start, end); subDecs = styleLiteralIdentifierPuncRecognizer(chunk, start); for (var j = 0, m = subDecs.length; j < m; j += 2) { var subStyle = subDecs[j + 1]; if (subStyle === PR_PLAIN) { var subStart = subDecs[j]; var subEnd = j + 2 < m ? subDecs[j + 2] : chunk.length; var token = source.substring(subStart, subEnd); if (token === '.') { subDecs[j + 1] = PR_PUNCTUATION; } else if (token in keywords) { subDecs[j + 1] = PR_KEYWORD; } else if (/^@?[A-Z][A-Z$]*[a-z][A-Za-z$]*$/.test(token)) { subDecs[j + 1] = token.charAt(0) === '@' ? PR_LITERAL : PR_TYPE; } } } spliceArrayInto(subDecs, decorations, i, 2); i += subDecs.length - 2; } } return decorations; } return function(sourceCode) { var decorations = splitStringAndCommentTokens(sourceCode); decorations = splitNonStringNonCommentTokens(sourceCode, decorations); return decorations; }; } var decorateSource = sourceDecorator({ keywords: ALL_KEYWORDS, hashComments: true, cStyleComments: true, multiLineStrings: true, regexLiterals: true }); function splitSourceNodes(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var subDecorations = decorateSource(source.substring(start, end)); for (var j = 0, m = subDecorations.length; j < m; j += 2) { subDecorations[j] += start; } spliceArrayInto(subDecorations, decorations, i, 2); i += subDecorations.length - 2; } } return decorations; } function splitSourceAttributes(source, decorations) { var nextValueIsSource = false; for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; var start, end; if (style === PR_ATTRIB_NAME) { start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; nextValueIsSource = /^on|^style$/i.test(source.substring(start, end)); } else if (style === PR_ATTRIB_VALUE) { if (nextValueIsSource) { start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var attribValue = source.substring(start, end); var attribLen = attribValue.length; var quoted = (attribLen >= 2 && /^[\"\']/.test(attribValue) && attribValue.charAt(0) === attribValue.charAt(attribLen - 1)); var attribSource; var attribSourceStart; var attribSourceEnd; if (quoted) { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue; } else { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue.substring(1, attribValue.length - 1); } var attribSourceDecorations = decorateSource(attribSource); for (var j = 0, m = attribSourceDecorations.length; j < m; j += 2) { attribSourceDecorations[j] += attribSourceStart; } if (quoted) { attribSourceDecorations.push(attribSourceEnd, PR_ATTRIB_VALUE); spliceArrayInto(attribSourceDecorations, decorations, i + 2, 0); } else { spliceArrayInto(attribSourceDecorations, decorations, i, 2); } } nextValueIsSource = false; } } return decorations; } function decorateMarkup(sourceCode) { var decorations = tokenizeMarkup(sourceCode); decorations = splitTagAttributes(sourceCode, decorations); decorations = splitSourceNodes(sourceCode, decorations); decorations = splitSourceAttributes(sourceCode, decorations); return decorations; } function recombineTagsAndDecorations(sourceText, extractedTags, decorations) { var html = []; var outputIdx = 0; var openDecoration = null; var currentDecoration = null; var tagPos = 0; var decPos = 0; var tabExpander = makeTabExpander(PR_TAB_WIDTH); var adjacentSpaceRe = /([\r\n ]) /g; var startOrSpaceRe = /(^| ) /gm; var newlineRe = /\r\n?|\n/g; var trailingSpaceRe = /[ \r\n]$/; var lastWasSpace = true; function emitTextUpTo(sourceIdx) { if (sourceIdx > outputIdx) { if (openDecoration && openDecoration !== currentDecoration) { html.push('</span>'); openDecoration = null; } if (!openDecoration && currentDecoration) { openDecoration = currentDecoration; html.push('<span class="', openDecoration, '">'); } var htmlChunk = textToHtml(tabExpander(sourceText.substring(outputIdx, sourceIdx))).replace(lastWasSpace ? startOrSpaceRe : adjacentSpaceRe, '$1&nbsp;'); lastWasSpace = trailingSpaceRe.test(htmlChunk); html.push(htmlChunk.replace(newlineRe, '<br />')); outputIdx = sourceIdx; } } while (true) { var outputTag; if (tagPos < extractedTags.length) { if (decPos < decorations.length) { outputTag = extractedTags[tagPos] <= decorations[decPos]; } else { outputTag = true; } } else { outputTag = false; } if (outputTag) { emitTextUpTo(extractedTags[tagPos]); if (openDecoration) { html.push('</span>'); openDecoration = null; } html.push(extractedTags[tagPos + 1]); tagPos += 2; } else if (decPos < decorations.length) { emitTextUpTo(decorations[decPos]); currentDecoration = decorations[decPos + 1]; decPos += 2; } else { break; } } emitTextUpTo(sourceText.length); if (openDecoration) { html.push('</span>'); } return html.join(''); } var langHandlerRegistry = {}; function registerLangHandler(handler, fileExtensions) { for (var i = fileExtensions.length; --i >= 0; ) { var ext = fileExtensions[i]; if (!langHandlerRegistry.hasOwnProperty(ext)) { langHandlerRegistry[ext] = handler; } else if ('console' in window) { console.log('cannot override language handler %s', ext); } } } registerLangHandler(decorateSource, ['default-code']); registerLangHandler(decorateMarkup, ['default-markup', 'html', 'htm', 'xhtml', 'xml', 'xsl']); registerLangHandler(sourceDecorator({ keywords: CPP_KEYWORDS, hashComments: true, cStyleComments: true }), ['c', 'cc', 'cpp', 'cs', 'cxx', 'cyc']); registerLangHandler(sourceDecorator({ keywords: JAVA_KEYWORDS, cStyleComments: true }), ['java']); registerLangHandler(sourceDecorator({ keywords: SH_KEYWORDS, hashComments: true, multiLineStrings: true }), ['bsh', 'csh', 'sh']); registerLangHandler(sourceDecorator({ keywords: PYTHON_KEYWORDS, hashComments: true, multiLineStrings: true, tripleQuotedStrings: true }), ['cv', 'py']); registerLangHandler(sourceDecorator({ keywords: PERL_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['perl', 'pl', 'pm']); registerLangHandler(sourceDecorator({ keywords: RUBY_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['rb']); registerLangHandler(sourceDecorator({ keywords: JSCRIPT_KEYWORDS, cStyleComments: true, regexLiterals: true }), ['js']); function prettyPrintOne(sourceCodeHtml, opt_langExtension) { try { var sourceAndExtractedTags = extractTags(sourceCodeHtml); var source = sourceAndExtractedTags.source; var extractedTags = sourceAndExtractedTags.tags; if (!langHandlerRegistry.hasOwnProperty(opt_langExtension)) { opt_langExtension = /^\s*</.test(source) ? 'default-markup' : 'default-code'; } var decorations = langHandlerRegistry[opt_langExtension].call({}, source); return recombineTagsAndDecorations(source, extractedTags, decorations); } catch (e) { if ('console' in window) { console.log(e); console.trace(); } return sourceCodeHtml; } } function prettyPrint(opt_whenDone) { var isIE6 = _pr_isIE6(); var codeSegments = [document.getElementsByTagName('pre'), document.getElementsByTagName('code'), document.getElementsByTagName('xmp')]; var elements = []; for (var i = 0; i < codeSegments.length; ++i) { for (var j = 0; j < codeSegments[i].length; ++j) { elements.push(codeSegments[i][j]); } } codeSegments = null; var k = 0; function doWork() { var endTime = (PR_SHOULD_USE_CONTINUATION ? new Date().getTime() + 250 : Infinity); for (; k < elements.length && new Date().getTime() < endTime; k++) { var cs = elements[k]; if (cs.className && cs.className.indexOf('prettyprint') >= 0) { var langExtension = cs.className.match(/\blang-(\w+)\b/); if (langExtension) { langExtension = langExtension[1]; } var nested = false; for (var p = cs.parentNode; p; p = p.parentNode) { if ((p.tagName === 'pre' || p.tagName === 'code' || p.tagName === 'xmp') && p.className && p.className.indexOf('prettyprint') >= 0) { nested = true; break; } } if (!nested) { var content = getInnerHtml(cs); content = content.replace(/(?:\r\n?|\n)$/, ''); var newContent = prettyPrintOne(content, langExtension); if (!isRawContent(cs)) { cs.innerHTML = newContent; } else { var pre = document.createElement('PRE'); for (var i = 0; i < cs.attributes.length; ++i) { var a = cs.attributes[i]; if (a.specified) { var aname = a.name.toLowerCase(); if (aname === 'class') { pre.className = a.value; } else { pre.setAttribute(a.name, a.value); } } } pre.innerHTML = newContent; cs.parentNode.replaceChild(pre, cs); cs = pre; } if (isIE6 && cs.tagName === 'PRE') { var lineBreaks = cs.getElementsByTagName('br'); for (var j = lineBreaks.length; --j >= 0; ) { var lineBreak = lineBreaks[j]; lineBreak.parentNode.replaceChild(document.createTextNode('\r\n'), lineBreak); } } } } } if (k < elements.length) { setTimeout(doWork, 250); } else if (opt_whenDone) { opt_whenDone(); } } doWork(); } window['PR_normalizedHtml'] = normalizedHtml; window['prettyPrintOne'] = prettyPrintOne; window['prettyPrint'] = prettyPrint; window['PR'] = { 'createSimpleLexer': createSimpleLexer, 'registerLangHandler': registerLangHandler, 'sourceDecorator': sourceDecorator, 'PR_ATTRIB_NAME': PR_ATTRIB_NAME, 'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE, 'PR_COMMENT': PR_COMMENT, 'PR_DECLARATION': PR_DECLARATION, 'PR_KEYWORD': PR_KEYWORD, 'PR_LITERAL': PR_LITERAL, 'PR_NOCODE': PR_NOCODE, 'PR_PLAIN': PR_PLAIN, 'PR_PUNCTUATION': PR_PUNCTUATION, 'PR_SOURCE': PR_SOURCE, 'PR_STRING': PR_STRING, 'PR_TAG': PR_TAG, 'PR_TYPE': PR_TYPE }; })();
diff --git a/templates/content/js/com.cnprog.utils.js b/templates/content/js/com.cnprog.utils.js
index 683b7a3f..e271ed78 100644
--- a/templates/content/js/com.cnprog.utils.js
+++ b/templates/content/js/com.cnprog.utils.js
@@ -119,4 +119,4 @@ var CPValidator = function(){
}();
//Search Engine Keyword Highlight with Javascript
//http://scott.yang.id.au/code/se-hilite/
-Hilite={elementid:"content",exact:true,max_nodes:1000,onload:true,style_name:"hilite",style_name_suffix:true,debug_referrer:""};Hilite.search_engines=[["local","q"],["cnprog\\.","q"],["google\\.","q"],["search\\.yahoo\\.","p"],["search\\.msn\\.","q"],["search\\.live\\.","query"],["search\\.aol\\.","userQuery"],["ask\\.com","q"],["altavista\\.","q"],["feedster\\.","q"],["search\\.lycos\\.","q"],["alltheweb\\.","q"],["technorati\\.com/search/([^\\?/]+)",1],["dogpile\\.com/info\\.dogpl/search/web/([^\\?/]+)",1,true]];Hilite.decodeReferrer=function(d){var g=null;var e=new RegExp("");for(var c=0;c<Hilite.search_engines.length;c++){var f=Hilite.search_engines[c];e.compile("^http://(www\\.)?"+f[0],"i");var b=d.match(e);if(b){var a;if(isNaN(f[1])){a=Hilite.decodeReferrerQS(d,f[1])}else{a=b[f[1]+1]}if(a){a=decodeURIComponent(a);if(f.length>2&&f[2]){a=decodeURIComponent(a)}a=a.replace(/\'|"/g,"");a=a.split(/[\s,\+\.]+/);return a}break}}return null};Hilite.decodeReferrerQS=function(f,d){var b=f.indexOf("?");var c;if(b>=0){var a=new String(f.substring(b+1));b=0;c=0;while((b>=0)&&((c=a.indexOf("=",b))>=0)){var e,g;e=a.substring(b,c);b=a.indexOf("&",c)+1;if(e==d){if(b<=0){return a.substring(c+1)}else{return a.substring(c+1,b-1)}}else{if(b<=0){return null}}}}return null};Hilite.hiliteElement=function(f,e){if(!e||f.childNodes.length==0){return}var c=new Array();for(var b=0;b<e.length;b++){e[b]=e[b].toLowerCase();if(Hilite.exact){c.push("\\b"+e[b]+"\\b")}else{c.push(e[b])}}c=new RegExp(c.join("|"),"i");var a={};for(var b=0;b<e.length;b++){if(Hilite.style_name_suffix){a[e[b]]=Hilite.style_name+(b+1)}else{a[e[b]]=Hilite.style_name}}var d=function(m){var j=c.exec(m.data);if(j){var n=j[0];var i="";var h=m.splitText(j.index);var g=h.splitText(n.length);var l=m.ownerDocument.createElement("SPAN");m.parentNode.replaceChild(l,h);l.className=a[n.toLowerCase()];l.appendChild(h);return l}else{return m}};Hilite.walkElements(f.childNodes[0],1,d)};Hilite.hilite=function(){var a=Hilite.debug_referrer?Hilite.debug_referrer:document.referrer;var b=null;a=Hilite.decodeReferrer(a);if(a&&((Hilite.elementid&&(b=document.getElementById(Hilite.elementid)))||(b=document.body))){Hilite.hiliteElement(b,a)}};Hilite.walkElements=function(d,f,e){var a=/^(script|style|textarea)/i;var c=0;while(d&&f>0){c++;if(c>=Hilite.max_nodes){var b=function(){Hilite.walkElements(d,f,e)};setTimeout(b,50);return}if(d.nodeType==1){if(!a.test(d.tagName)&&d.childNodes.length>0){d=d.childNodes[0];f++;continue}}else{if(d.nodeType==3){d=e(d)}}if(d.nextSibling){d=d.nextSibling}else{while(f>0){d=d.parentNode;f--;if(d.nextSibling){d=d.nextSibling;break}}}}};if(Hilite.onload){if(window.attachEvent){window.attachEvent("onload",Hilite.hilite)}else{if(window.addEventListener){window.addEventListener("load",Hilite.hilite,false)}else{var __onload=window.onload;window.onload=function(){Hilite.hilite();__onload()}}}}; \ No newline at end of file
+Hilite={elementid:"content",exact:true,max_nodes:1000,onload:true,style_name:"hilite",style_name_suffix:true,debug_referrer:""};Hilite.search_engines=[["local","q"],["cnprog\\.","q"],["google\\.","q"],["search\\.yahoo\\.","p"],["search\\.msn\\.","q"],["search\\.live\\.","query"],["search\\.aol\\.","userQuery"],["ask\\.com","q"],["altavista\\.","q"],["feedster\\.","q"],["search\\.lycos\\.","q"],["alltheweb\\.","q"],["technorati\\.com/search/([^\\?/]+)",1],["dogpile\\.com/info\\.dogpl/search/web/([^\\?/]+)",1,true]];Hilite.decodeReferrer=function(d){var g=null;var e=new RegExp("");for(var c=0;c<Hilite.search_engines.length;c++){var f=Hilite.search_engines[c];e.compile("^http://(www\\.)?"+f[0],"i");var b=d.match(e);if(b){var a;if(isNaN(f[1])){a=Hilite.decodeReferrerQS(d,f[1])}else{a=b[f[1]+1]}if(a){a=decodeURIComponent(a);if(f.length>2&&f[2]){a=decodeURIComponent(a)}a=a.replace(/\'|"/g,"");a=a.split(/[\s,\+\.]+/);return a}break}}return null};Hilite.decodeReferrerQS=function(f,d){var b=f.indexOf("?");var c;if(b>=0){var a=new String(f.substring(b+1));b=0;c=0;while((b>=0)&&((c=a.indexOf("=",b))>=0)){var e,g;e=a.substring(b,c);b=a.indexOf("&",c)+1;if(e==d){if(b<=0){return a.substring(c+1)}else{return a.substring(c+1,b-1)}}else{if(b<=0){return null}}}}return null};Hilite.hiliteElement=function(f,e){if(!e||f.childNodes.length==0){return}var c=new Array();for(var b=0;b<e.length;b++){e[b]=e[b].toLowerCase();if(Hilite.exact){c.push("\\b"+e[b]+"\\b")}else{c.push(e[b])}}c=new RegExp(c.join("|"),"i");var a={};for(var b=0;b<e.length;b++){if(Hilite.style_name_suffix){a[e[b]]=Hilite.style_name+(b+1)}else{a[e[b]]=Hilite.style_name}}var d=function(m){var j=c.exec(m.data);if(j){var n=j[0];var i="";var h=m.splitText(j.index);var g=h.splitText(n.length);var l=m.ownerDocument.createElement("SPAN");m.parentNode.replaceChild(l,h);l.className=a[n.toLowerCase()];l.appendChild(h);return l}else{return m}};Hilite.walkElements(f.childNodes[0],1,d)};Hilite.hilite=function(){var a=Hilite.debug_referrer?Hilite.debug_referrer:document.referrer;var b=null;a=Hilite.decodeReferrer(a);if(a&&((Hilite.elementid&&(b=document.getElementById(Hilite.elementid)))||(b=document.body))){Hilite.hiliteElement(b,a)}};Hilite.walkElements=function(d,f,e){var a=/^(script|style|textarea)/i;var c=0;while(d&&f>0){c++;if(c>=Hilite.max_nodes){var b=function(){Hilite.walkElements(d,f,e)};setTimeout(b,50);return}if(d.nodeType==1){if(!a.test(d.tagName)&&d.childNodes.length>0){d=d.childNodes[0];f++;continue}}else{if(d.nodeType==3){d=e(d)}}if(d.nextSibling){d=d.nextSibling}else{while(f>0){d=d.parentNode;f--;if(d.nextSibling){d=d.nextSibling;break}}}}};if(Hilite.onload){if(window.attachEvent){window.attachEvent("onload",Hilite.hilite)}else{if(window.addEventListener){window.addEventListener("load",Hilite.hilite,false)}else{var __onload=window.onload;window.onload=function(){Hilite.hilite();__onload()}}}};
diff --git a/templates/content/js/jquery.ajaxfileupload.js b/templates/content/js/jquery.ajaxfileupload.js
index 106de906..75292776 100644
--- a/templates/content/js/jquery.ajaxfileupload.js
+++ b/templates/content/js/jquery.ajaxfileupload.js
@@ -1,195 +1,195 @@
-jQuery.extend({
- createUploadIframe: function(id, uri){
- //create frame
- var frameId = 'jUploadFrame' + id;
- if(window.ActiveXObject) {
- var io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '" />');
- if(typeof uri== 'boolean'){
- io.src = 'javascript:false';
- }
- else if(typeof uri== 'string'){
- io.src = uri;
- }
- }
- else {
- var io = document.createElement('iframe');
- io.id = frameId;
- io.name = frameId;
- }
- io.style.position = 'absolute';
- io.style.top = '-1000px';
- io.style.left = '-1000px';
-
- document.body.appendChild(io);
- return io;
- },
- createUploadForm: function(id, fileElementId)
- {
- //create form
- var formId = 'jUploadForm' + id;
- var fileId = 'jUploadFile' + id;
- var form = $('<form action="" method="POST" name="' + formId + '" id="' + formId
- + '" enctype="multipart/form-data"></form>');
- var oldElement = $('#' + fileElementId);
- var newElement = $(oldElement).clone();
- $(oldElement).attr('id', fileId);
- $(oldElement).before(newElement);
- $(oldElement).appendTo(form);
- //set attributes
- $(form).css('position', 'absolute');
- $(form).css('top', '-1200px');
- $(form).css('left', '-1200px');
- $(form).appendTo('body');
- return form;
- },
-
- ajaxFileUpload: function(s) {
- // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout
- s = jQuery.extend({}, jQuery.ajaxSettings, s);
- var id = new Date().getTime()
- var form = jQuery.createUploadForm(id, s.fileElementId);
- var io = jQuery.createUploadIframe(id, s.secureuri);
- var frameId = 'jUploadFrame' + id;
- var formId = 'jUploadForm' + id;
- // Watch for a new set of requests
- if ( s.global && ! jQuery.active++ )
- {
- jQuery.event.trigger( "ajaxStart" );
- }
- var requestDone = false;
- // Create the request object
- var xml = {}
- if ( s.global )
- jQuery.event.trigger("ajaxSend", [xml, s]);
- // Wait for a response to come back
- var uploadCallback = function(isTimeout)
- {
- var io = document.getElementById(frameId);
- try {
- if(io.contentWindow){
- xml.responseText = io.contentWindow.document.body ?
- io.contentWindow.document.body.innerText : null;
- xml.responseXML = io.contentWindow.document.XMLDocument ?
- io.contentWindow.document.XMLDocument : io.contentWindow.document;
-
- }
- else if(io.contentDocument)
- {
- xml.responseText = io.contentDocument.document.body ?
- io.contentDocument.document.body.textContent || document.body.innerText : null;
- xml.responseXML = io.contentDocument.document.XMLDocument ?
- io.contentDocument.document.XMLDocument : io.contentDocument.document;
- }
- }
- catch(e)
- {
- jQuery.handleError(s, xml, null, e);
- }
- if ( xml || isTimeout == "timeout")
- {
- requestDone = true;
- var status;
- try {
- status = isTimeout != "timeout" ? "success" : "error";
- // Make sure that the request was successful or notmodified
- if ( status != "error" )
- {
- // process the data (runs the xml through httpData regardless of callback)
- var data = jQuery.uploadHttpData( xml, s.dataType );
- // If a local callback was specified, fire it and pass it the data
- if ( s.success )
- s.success( data, status );
-
- // Fire the global callback
- if( s.global )
- jQuery.event.trigger( "ajaxSuccess", [xml, s] );
- } else
- jQuery.handleError(s, xml, status);
- } catch(e)
- {
- status = "error";
- jQuery.handleError(s, xml, status, e);
- }
-
- // The request was completed
- if( s.global )
- jQuery.event.trigger( "ajaxComplete", [xml, s] );
-
- // Handle the global AJAX counter
- if ( s.global && ! --jQuery.active )
- jQuery.event.trigger( "ajaxStop" );
-
- // Process result
- if ( s.complete )
- s.complete(xml, status);
-
- jQuery(io).unbind();
-
- setTimeout(function()
- { try
- {
- $(io).remove();
- $(form).remove();
-
- } catch(e) {
- jQuery.handleError(s, xml, null, e);
- }
- }, 100)
- xml = null;
- }
- }
- // Timeout checker
- if ( s.timeout > 0 ) {
- setTimeout(function(){
- // Check to see if the request is still happening
- if( !requestDone ) uploadCallback( "timeout" );
- }, s.timeout);
- }
- try
- {
- // var io = $('#' + frameId);
- var form = $('#' + formId);
- $(form).attr('action', s.url);
- $(form).attr('method', 'POST');
- $(form).attr('target', frameId);
- if(form.encoding)
- {
- form.encoding = 'multipart/form-data';
- }
- else
- {
- form.enctype = 'multipart/form-data';
- }
- $(form).submit();
-
- } catch(e)
- {
- jQuery.handleError(s, xml, null, e);
- }
- if(window.attachEvent){
- document.getElementById(frameId).attachEvent('onload', uploadCallback);
- }
- else{
- document.getElementById(frameId).addEventListener('load', uploadCallback, false);
- }
- return {abort: function () {}};
-
- },
-
- uploadHttpData: function( r, type ) {
- var data = !type;
- data = type == "xml" || data ? r.responseXML : r.responseText;
- // If the type is "script", eval it in global context
- if ( type == "script" )
- jQuery.globalEval( data );
- // Get the JavaScript object, if JSON is used.
- if ( type == "json" )
- eval( "data = " + data );
- // evaluate scripts within html
- if ( type == "html" )
- jQuery("<div>").html(data).evalScripts();
- //alert($('param', data).each(function(){alert($(this).attr('value'));}));
- return data;
- }
-})
-
+jQuery.extend({
+ createUploadIframe: function(id, uri){
+ //create frame
+ var frameId = 'jUploadFrame' + id;
+ if(window.ActiveXObject) {
+ var io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '" />');
+ if(typeof uri== 'boolean'){
+ io.src = 'javascript:false';
+ }
+ else if(typeof uri== 'string'){
+ io.src = uri;
+ }
+ }
+ else {
+ var io = document.createElement('iframe');
+ io.id = frameId;
+ io.name = frameId;
+ }
+ io.style.position = 'absolute';
+ io.style.top = '-1000px';
+ io.style.left = '-1000px';
+
+ document.body.appendChild(io);
+ return io;
+ },
+ createUploadForm: function(id, fileElementId)
+ {
+ //create form
+ var formId = 'jUploadForm' + id;
+ var fileId = 'jUploadFile' + id;
+ var form = $('<form action="" method="POST" name="' + formId + '" id="' + formId
+ + '" enctype="multipart/form-data"></form>');
+ var oldElement = $('#' + fileElementId);
+ var newElement = $(oldElement).clone();
+ $(oldElement).attr('id', fileId);
+ $(oldElement).before(newElement);
+ $(oldElement).appendTo(form);
+ //set attributes
+ $(form).css('position', 'absolute');
+ $(form).css('top', '-1200px');
+ $(form).css('left', '-1200px');
+ $(form).appendTo('body');
+ return form;
+ },
+
+ ajaxFileUpload: function(s) {
+ // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout
+ s = jQuery.extend({}, jQuery.ajaxSettings, s);
+ var id = new Date().getTime()
+ var form = jQuery.createUploadForm(id, s.fileElementId);
+ var io = jQuery.createUploadIframe(id, s.secureuri);
+ var frameId = 'jUploadFrame' + id;
+ var formId = 'jUploadForm' + id;
+ // Watch for a new set of requests
+ if ( s.global && ! jQuery.active++ )
+ {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+ var requestDone = false;
+ // Create the request object
+ var xml = {}
+ if ( s.global )
+ jQuery.event.trigger("ajaxSend", [xml, s]);
+ // Wait for a response to come back
+ var uploadCallback = function(isTimeout)
+ {
+ var io = document.getElementById(frameId);
+ try {
+ if(io.contentWindow){
+ xml.responseText = io.contentWindow.document.body ?
+ io.contentWindow.document.body.innerText : null;
+ xml.responseXML = io.contentWindow.document.XMLDocument ?
+ io.contentWindow.document.XMLDocument : io.contentWindow.document;
+
+ }
+ else if(io.contentDocument)
+ {
+ xml.responseText = io.contentDocument.document.body ?
+ io.contentDocument.document.body.textContent || document.body.innerText : null;
+ xml.responseXML = io.contentDocument.document.XMLDocument ?
+ io.contentDocument.document.XMLDocument : io.contentDocument.document;
+ }
+ }
+ catch(e)
+ {
+ jQuery.handleError(s, xml, null, e);
+ }
+ if ( xml || isTimeout == "timeout")
+ {
+ requestDone = true;
+ var status;
+ try {
+ status = isTimeout != "timeout" ? "success" : "error";
+ // Make sure that the request was successful or notmodified
+ if ( status != "error" )
+ {
+ // process the data (runs the xml through httpData regardless of callback)
+ var data = jQuery.uploadHttpData( xml, s.dataType );
+ // If a local callback was specified, fire it and pass it the data
+ if ( s.success )
+ s.success( data, status );
+
+ // Fire the global callback
+ if( s.global )
+ jQuery.event.trigger( "ajaxSuccess", [xml, s] );
+ } else
+ jQuery.handleError(s, xml, status);
+ } catch(e)
+ {
+ status = "error";
+ jQuery.handleError(s, xml, status, e);
+ }
+
+ // The request was completed
+ if( s.global )
+ jQuery.event.trigger( "ajaxComplete", [xml, s] );
+
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active )
+ jQuery.event.trigger( "ajaxStop" );
+
+ // Process result
+ if ( s.complete )
+ s.complete(xml, status);
+
+ jQuery(io).unbind();
+
+ setTimeout(function()
+ { try
+ {
+ $(io).remove();
+ $(form).remove();
+
+ } catch(e) {
+ jQuery.handleError(s, xml, null, e);
+ }
+ }, 100)
+ xml = null;
+ }
+ }
+ // Timeout checker
+ if ( s.timeout > 0 ) {
+ setTimeout(function(){
+ // Check to see if the request is still happening
+ if( !requestDone ) uploadCallback( "timeout" );
+ }, s.timeout);
+ }
+ try
+ {
+ // var io = $('#' + frameId);
+ var form = $('#' + formId);
+ $(form).attr('action', s.url);
+ $(form).attr('method', 'POST');
+ $(form).attr('target', frameId);
+ if(form.encoding)
+ {
+ form.encoding = 'multipart/form-data';
+ }
+ else
+ {
+ form.enctype = 'multipart/form-data';
+ }
+ $(form).submit();
+
+ } catch(e)
+ {
+ jQuery.handleError(s, xml, null, e);
+ }
+ if(window.attachEvent){
+ document.getElementById(frameId).attachEvent('onload', uploadCallback);
+ }
+ else{
+ document.getElementById(frameId).addEventListener('load', uploadCallback, false);
+ }
+ return {abort: function () {}};
+
+ },
+
+ uploadHttpData: function( r, type ) {
+ var data = !type;
+ data = type == "xml" || data ? r.responseXML : r.responseText;
+ // If the type is "script", eval it in global context
+ if ( type == "script" )
+ jQuery.globalEval( data );
+ // Get the JavaScript object, if JSON is used.
+ if ( type == "json" )
+ eval( "data = " + data );
+ // evaluate scripts within html
+ if ( type == "html" )
+ jQuery("<div>").html(data).evalScripts();
+ //alert($('param', data).each(function(){alert($(this).attr('value'));}));
+ return data;
+ }
+})
+
diff --git a/templates/content/style/default.css b/templates/content/style/default.css
index 9b561803..0221cc03 100644
--- a/templates/content/style/default.css
+++ b/templates/content/style/default.css
@@ -164,7 +164,7 @@ h4 {display:block;font-size:90%; font-family:Verdana;color:#ccc;}
color:darkred;
font-weight:400;
font-size:100%;
- letter-spacing:1px;
+ /*letter-spacing:1px;*/
}
@@ -198,7 +198,7 @@ h4 {display:block;font-size:90%; font-family:Verdana;color:#ccc;}
float: left;
font-size: 140%;
font-weight:700;
- letter-spacing:3px;
+ /*letter-spacing:3px;*/
margin-top:8px;
padding:5px 0 0 3px ;
height:20px;
@@ -329,7 +329,7 @@ h4 {display:block;font-size:90%; font-family:Verdana;color:#ccc;}
font-size:120%;
font-weight:bold;
width:120px;
- letter-spacing:1px;
+ /*letter-spacing:1px;*/
background-color:#D4D0C8;
}
.notify
@@ -461,7 +461,7 @@ h4 {display:block;font-size:90%; font-family:Verdana;color:#ccc;}
}
.highlight-box{
- letter-spacing:1px;
+ /*letter-spacing:1px;*/
color:#735005;
}
@@ -890,7 +890,7 @@ h4 {display:block;font-size:90%; font-family:Verdana;color:#ccc;}
margin-bottom:-10px;
width:600px;
color:#aaa;
- letter-spacing:1px;
+ /*letter-spacing:1px;*/
}
@@ -1750,4 +1750,4 @@ a.comment-user:hover {
.error{color:red;}
.error-list li{padding:5px;}
.login{margin-bottom:10px;}
-.fieldset{border:solid 1px #777;margin-top:10px;padding:10px;} \ No newline at end of file
+.fieldset{border:solid 1px #777;margin-top:10px;padding:10px;}
diff --git a/templates/content/style/openid.css b/templates/content/style/openid.css
index 7a892840..0d201df2 100644
--- a/templates/content/style/openid.css
+++ b/templates/content/style/openid.css
@@ -42,4 +42,4 @@
}
.openid_selected {
border: 4px solid #DDD;
- } \ No newline at end of file
+ }
diff --git a/templates/content/style/style.css b/templates/content/style/style.css
index bfbd603a..b3fa6a6c 100644
--- a/templates/content/style/style.css
+++ b/templates/content/style/style.css
@@ -1,999 +1,1119 @@
-@import url(/content/style/jquery.autocomplete.css);
-@import url(/content/style/openid.css);
-@import url(/content/style/prettify.css);
-/* 公用 */
-body{background:#FFF; font-size:12px; line-height:150%; margin:0; padding:0; color:#000; font-family: "segoe ui",Helvetica,微软雅黑,宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;
-}
-div{margin:0 auto; padding:0;}
-h1,h2,h3,h4,h5,h6,ul,li,dl,dt,dd,form,img,p{margin:0; padding:0; border:none; }
-input, select {font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;}
-p{margin-bottom:4px; font-size:13px; line-height:160%;}
-a {color:#663333; text-decoration:none;}
-a:hover {text-decoration:underline;}
-.block{width:960px; height:auto;}
-.fleft{float:left;}
-.fright{float:right;}
-.tleft{text-align:left;}
-.tcenter{text-align:center;}
-.tright{text-align:right;}
-.dis{display:block;}
-.inline{display:inline;}
-.none{display:none;}
-.red{color:#CC0000;}
-.b{font-weight:bold;}
-.f10{font-size:10px;}
-.f11{font-size:11px;}
-.f12{font-size:12px;}
-.f13{font-size:13px;}
-.f14{font-size:14px;}
-.white{color:#FFFFFF;}
-.u{text-decoration:underline;}
-.spacer1{height:6px; line-height:6px; clear:both; visibility:hidden;}
-.spacer3{height:30px; line-height:30px; clear:both; visibility:hidden;}
-h1{font-size:160%;padding:5px 0 5px 0;}
-h2{font-size:140%;padding:3px 0 3px 0;}
-h3{font-size:120%;padding:3px 0 3px 0;}
-ul
-{
- list-style: disc;
- margin-left: 20px;
- padding-left:0px;
- margin-bottom: 1em;
-}
-ol
-{
- list-style: decimal;
- margin-left: 30px;
- margin-bottom: 1em;
- padding-left:0px;
-}
-pre
-{
- font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
- font-size:100%;
- margin-bottom: 10px;
- overflow: auto;
- width: 580px;
- background-color: #F5F5F5;
- padding-left:5px;
- padding-top:5px;
- padding-bottom: 20px !ie7;
-}
-
-code{
- font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
- font-size:100%;
-
-}
-
-blockquote
-{
- margin-bottom: 10px;
- margin-right: 15px;
- padding: 10px 0px 1px 10px;
- background-color: #F5F5F5;
-}
-
-/*页面布局*/
-#wrapper {width:960px;margin:auto;padding:0;}
-#roof {margin-top:0px;background:#FFF;}
-#room {padding-top:10px;background-color:#FFF;border-bottom:1px solid #777;}
-#CALeft{width:700px; float:left; position:relative;padding-left:5px}
-#CARight{width:240px; float:right; padding-right:5px}
-#CAFull{float:left;padding:0 5px 0 5px;width:950px;}
-#ground {clear:both;border-top:6px solid #000; padding-top:6px; padding-bottom:50px; text-align:center;background:#777;}
-
-/*顶部及导航栏*/
-#top {height:20px; text-align:right; padding:3px;background-color:#eee;}
-#header {width:960px;}
-#top a {height:35px; text-align:right;letter-spacing:1px; margin-left:20px;text-decoration:underline; font-size:12px; color:#666;}
-#logo {padding:5px;}
-#navBar {float:clear;position:relative;display:block;width:960px;}
-#navBar .nav {margin:20px 0px 0px 16px;letter-spacing:1px; }
-#navBar .nav a {color:#333; background-color:#F9F7ED; padding:0px 12px 3px 12px; height:25px; line-height:30px;margin-left:10px; font-size:15px; font-weight:400; text-decoration:none;display: block;float: left;}
-#navBar .nav a:hover {text-decoration:underline}
-#navBar .nav a.on {height:24px;line-height:28px;border:1px solid green; background:green; color:#FFF; font-weight:600; text-decoration:none}
-#navBar .nav a.special {font-size:15px; color:white;background:grey; font-weight:bold; text-decoration:none;}
-#navBar .nav a.special:hover {text-decoration:underline;}
-#navBar .nav div.focus {position:absolute; top:4px; padding-right:0px;}
-/*搜索栏*/
-#searchBar {background-color:green;padding:5px;}
-#searchBar .content { }
-#searchBar .searchInput {font-size:13px; height:18px; width:400px;}
-#searchBar .searchBtn {font-size:14px; height:26px; width:80px;}
-#searchBar .options {padding-top:5px; font-size:100%;color:#EEE;letter-spacing:1px;}
-#searchBar .options INPUT {margin-left:15px;}
-#searchBar .options INPUT:hover {cursor:pointer}
-
-/*问题列表*/
-#listA {float:left; background-color:#FFF;padding:0 0px 0 0px; width:100%;}
-#listA .qstA {padding:3px 5px 0 5px; margin:0 0px 10px 0px; background:url(/content/images/quest-bg.gif) repeat-x top;}
-#listA .qstA thumb {float:left; }
-#listA .qstA H2 {font-size:15px; font-weight:800; margin:8px auto;padding:0px;}
-#listA .qstA H2 a {color:#663333; }
-#listA .qstA .stat {font-size:13px;letter-spacing:1px;float:right;}
-#listA .qstA .stat span {margin-right:5px;}
-#listA .qstA .stat td {min-width:40px;text-align:center;}
-#listA .qstA .stat .num {font-family:arial;color:#905213; font-size:20px; font-weight:800;}
-#listA .qstA .stat .unit {color:#777;}
-#listA .qstA .from {margin-top:5px; font-size:13px;}
-#listA .qstA .from .score {font-family:arial;}
-#listA .qstA .date {margin-left:20px; color:#777;}
-#listA .qstA .wiki {color:#663333;font-size:12px;}
-#listA .qstA .from a {}
-#listA .qstA .from IMG {vertical-align:middle;}
-#listA .qstA .author {font-weight:400; }
-#listA .qstA .author a{ }
-#listA .qstA .summary{margin-right:5px;}
-.evenMore {font-size:14px; font-weight:800;}
-.questions-count{font-size:32px;font-family:arial;font-weight:600;padding:5px;color:#777;}
-
-/*内容块*/
-.boxA {background:#777; padding:6px; margin-bottom:8px;}
-.boxA H3 {font-size:13px; font-weight:800; color:#FFF; margin:0; padding:0; margin-bottom:4px;}
-.boxA .body {border:1px solid #999; padding:8px; background:#FFF; font-size:13px;}
-.boxA .more {padding:2px; text-align:right; font-weight:800;}
-.boxB {background:#F9F7CD; padding:6px; margin-bottom:8px;}
-.boxB H3 {font-size:13px; font-weight:800; color:#000; margin:0; padding:0 0 0 15px; margin-bottom:4px; background:url(/content/images/dot-g.gif) no-repeat left center;}
-.boxB .body {border:1px solid #FFFF88; padding:8px; background:#FFF; font-size:13px; line-height:160%;}
-.boxB .more {padding:1px; text-align:right; font-weight:800;}
-.boxC {background:#F5F5F5; padding:6px; margin-bottom:8px;}
-/*分页*/
-.pager {margin-top:10px; margin-bottom:16px; float:left;}
-.pagesize {margin-top:10px; margin-bottom:16px; float:right;}
-
-/** PAGINATOR **/
-.paginator {
- padding:5px 0 10px 0;
- font:normal 12px arial;
-}
-
-.paginator .prev-na,
-.paginator .next-na {
- padding:.3em;
- font:bold .875em arial;
-}
-
-.paginator .prev-na,
-.paginator .next-na {
- border:1px solid #ccc;
- background-color:#f9f9f9;
- color:#aaa;
- font-weight:normal;
-}
-
-.paginator .prev a, .paginator .prev a:visited,
-.paginator .next a, .paginator .next a:visited {
- border:1px solid #fff;
- background-color:#fff;
- color:#777;
- padding:.3em;
- font:bold 100% arial;
-}
-
-.paginator .prev, .paginator .prev-na { margin-right:.5em; }
-.paginator .next, .paginator .next-na { margin-left:.5em; }
-
-.paginator .page a, .paginator .page a:visited, .paginator .curr {
- padding:.25em;
- font:normal .875em verdana;
- border:1px solid #ccc;
- background-color:#fff;
- margin:0em .25em;
- color:#777;
-}
-
-.paginator .curr {
- background-color:#777;
- color:#fff;
- border:1px solid #777;
- font-weight:bold;
-}
-
-.paginator .page a:hover,
-.paginator .curr a:hover,
-.paginator .prev a:hover,
-.paginator .next a:hover {
- color:#fff;
- background-color:#777;
- border:1px solid #777;
- text-decoration:none;
-}
-
-.paginator .text{
- color:#777;
- padding:.3em;
- font:bold 100% arial;
-}
-
-.paginator-container{
- float:right;
- padding:10px 0 10px 0;
-}
-
-.paginator-container-left{
- padding:5px 0 10px 0;
-}
-
-/*标签*/
-.tag {font-size:100%; 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:arial; line-height:200%; display:block; margin-top:5px;}
-.tags a {font-size:100%; 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:hover {background-color:#fFF;color:#333;}
-.tag-number {font-weight:700;font-family:arial;}
-
-/*奖牌*/
-a.medal { font-size:14px; line-height:250%; font-weight:800; color:#333; text-decoration:none; background:url(/content/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;}
-a:hover.medal {color:#333; text-decoration:none; background:url(/content/images/medala_on.gif) no-repeat; border-left:1px solid #E7E296; border-top:1px solid #E7E296; border-bottom:1px solid #D1CA3D; border-right:1px solid #D1CA3D;}
-
-/*Tab栏*/
-.tabBar{background-color:#FFF;border-bottom: 1px solid #666;height: 30px; width: 100%;clear:both; margin-bottom:3px;}
-.tabsA {background-color:#FFF;float:right;position:relative;display:block;font-weight:bold;height:20px;}
-.tabsB {background-color:#FFF;float:left;position:relative;display:block;font-weight:bold;height:20px;}
-.tabsA a.on, .tabsA a.on:hover,.tabsB a.on, .tabsB a.on:hover {background: #fff;
- color:#333;
- border: 1px solid #777;
- border-bottom:2px solid #FFF;
- height: 25px;
- line-height: 26px;
- margin-top: 3px;
- padding: 0px 11px 0px 11px;}
-
-.tabsA a {background: #eee;
- border: 1px solid #eee;
- color: #777;
- display: block;
- float: left;
- height: 22px;
- line-height: 28px;
- margin: 5px 4px 0 0;
- padding: 0 11px 0 11px;
- text-decoration: none;
-}
-.tabsB a {background: #eee;
- border: 1px solid #eee;
- color: #777;
- display: block;
- float: left;
- height: 22px;
- line-height: 28px;
- margin: 5px 0px 0 4px;
- padding: 0 11px 0 11px;
- text-decoration: none;
-}
-.tabsA a:hover, .tabsB a:hover {background: #fff;border: 1px solid #777;border-bottom:3px solid #FFF;}
-.headlineA {font-size:13px; border-bottom:1px solid #777; padding-bottom:2px; font-weight:800; margin-bottom:12px; text-align:right; height:30px;}
-.headQuestions {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;}
-.headAnswers {float:left; padding:3px; font-size:18px; font-weight:800; background:url(/content/images/ico_answers.gif) left 2px no-repeat; padding-left:24px;}
-.headTags {float:left; padding:3px; font-size:18px; font-weight:800; background:url(/content/images/ico_tags.gif) no-repeat; padding-left:24px;}
-.headUsers {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;}
-.headMedals {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;}
-.headLogin {float:left; padding:3px; font-size:15px; font-weight:800; background:url(/content/images/ico_login.gif) no-repeat; padding-left:24px;}
-.headNormal {text-align:left;padding:3px; font-size:15px; margin-bottom:12px; font-weight:800;border-bottom:1px solid #777;}
-.headUser {text-align:left;padding:5px; font-size:20px; letter-spacing:1px;margin-bottom:12px; font-weight:800;border-bottom:1px solid #777;}
-/*RSS订阅*/
-#feeds {margin:10px 0; }
-#feeds a {background:url(/content/images/feed-icon-small.png) no-repeat 0; padding-left:18px; font-weight:700; font-size:13px; }
-
-/*问题*/
-#question {margin-bottom:30px;}
-#question h1{font-size:15px;background:#CCC; padding:6px 8px;;}
-#question .body{background:#F7F7F7; padding:20px 10px;}
-.starter {padding:10px; background:#E0EAF1;}
-.vote {font-size:20px; color:#666; font-weight:800;}
-.questions-related{font-weight:700;word-wrap:break-word;}
-.questions-related p{line-height:20px; margin-bottom:10px;font-size:100%;}
-.question-status{
- margin-top:10px;
- padding: 20px;
- background-color:#F5F5F5;
- text-align:center;
-}
-.question-status h3{font-size:125%;}
-.question-body{
- min-height:100px;
- font-size:13px;
- line-height:20px;
-}
-.question-body IMG{
- max-width:600px;
-}
-.question-mark{
- background-color:#E9E9FF;
- text-align:left;
- padding:5px;
- overflow:hidden;
-}
-.question-edit{
- text-align:left;
- overflow:hidden;
-}
-.vote-buttons {float:left;text-align:center;}
-.vote-buttons IMG{cursor:pointer;}
-.vote-number{
- font-family:Arial;
- padding:0px 0 3px 0;
- font-size:140%;
- font-weight:bold;
- color:#777;
-}
-.question-img-upvote:hover{background:url(/content/images/vote-arrow-up-on.png)}
-.question-img-downvote:hover{background:url(/content/images/vote-arrow-down-on.png)}
-.question-img-favorite:hover{background:url(/content/images/vote-favorite-on.png)}
-.favorite-number{padding:0px;font-size:100%; font-family:Arial;font-weight:bold;color:#777;}
-.vote-notification
-{
- z-index: 1;
- cursor: pointer;
- display: none;
- position: absolute;
- padding: 15px;
- color: White;
- background-color: darkred;
-}
-.vote-notification a
-{
- color: White;
- text-decoration:underline;
-}
-.offensive-flag a{
- color:#777;
- padding:3px;
- cursor:pointer;
-}
-
-.offensive-flag a:hover{
- background-color:#777;
- text-decoration:none;
- color:#fff;
-}
-
-.linksopt a{
- color:#777;
- padding:3px;
- cursor:pointer;
-}
-
-.linksopt a:hover{
- background-color:#777;
- text-decoration:none;
- color:#fff;
-}
-
-.action-link a{
- color:#777;
- padding:3px;
- cursor:pointer;
-}
-
-.action-link a:hover{
- background-color:#777;
- text-decoration:none;
- color:#fff;
-}
-.action-link-separator{
- color:#ccc;
-}
-.wiki-category{
- margin-left:5px;
- color:#999;
- font-size:90%;
-}
-
-div.comments {
- line-height:150%;
- padding:10px 0;
-}
-
-div.post-comments{
- width:100%;
-}
-
-form.post-comments textarea {
- height:6em;
- margin-bottom:4px;
-}
-
-form.post-comments input {
- margin-left:10px;
- margin-top:1px;
- vertical-align:top;
- width:100px;
-}
-span.text-counter {
- margin-right:20px;
-}
-
-span.form-error {
- color:#990000;
- font-weight:normal;
- margin-left:5px;
-}
-
-div.comments-container, div.comments-container-accepted, div.comments-container-owner, div.comments-container-deleted {
- display:none;
- margin-top:-1px;
- padding:0 5px 5px;
-}
-
-div.comments-container, a.comments-link {
- background-color:#EEEEEE;
-}
-
-.post-comments a {
- color:#888888;
- padding:0 3px 2px;
-}
-
-a.comments-link, a.comments-link-accepted, a.comments-link-owner, a.comments-link-deleted {
- color:black;
- padding:2px;
- cursor:pointer;
-}
-
-.post-comments a:hover {
- background-color:#777777;
- color:white;
- text-decoration:none;
-}
-
-a.comment-user, a.comment-user:hover {
- background-color:inherit;
- color:#0077CC;
- padding:0;
-}
-
-a.comment-user:hover {
- text-decoration:underline;
-}
-.deleted{
- background:#F4E7E7 none repeat scroll 0 0;
-}
-/*回答*/
-#answers {}
-.answer{
- border-bottom:1px dotted #666666;
- padding-bottom:20px;
- padding-top:20px;
- width: 100%;
- margin-bottom:10px;
-}
-.answer-body{
- min-height:80px;
- font-size:13px;
- line-height:20px;
-}
-
-.answer-body IMG{
- max-width:600px;
-}
-
-.accepted-answer{
- background-color:#EBFFE6;
- border-bottom-color:#9BD59B;
-}
-
-.accepted-answer .comments-link{
- background-color:#CCFFBF;
-}
-
-.accepted-answer .comments-container{
- background-color:#CCFFBF;
-}
-
-.answered
-{
- background: #CCC;
- color: #999;
-}
-
-.answered-accepted
-{
- background: #CCC;
- color: #663333;
-}
-
-.unanswered
-{
- background: #777;
- color: white;
-}
-
-.answered-by-owner
-{
- background: #E9E9FF;
-}
-
-.answered-by-owner .comments-link
-{
- background-color:#E6ECFF;
-}
-
-.answered-by-owner .comments-container
-{
- background-color:#E6ECFF;
-}
-
-.answered-accepted strong
-{
- color: #E1E818;
-}
-
-.answer-img-accept:hover{background:url(/content/images/vote-accepted-on.png)}
-
-/*标签列表*/
-.tagsbox {}
-.tagsbox a {color:#000;line-height:30px;margin-right:10px;font-size:100%;background-color:#F9F7ED;padding:3px;}
-.tagsbox a:hover {text-decoration:none;background-color:#F9F7CD;color:green;}
-.tagsList {margin:0; list-style-type:none;padding:0px;min-height:360px;}
-.tagsList li {width:235px; float:left;}
-.badge-list{margin:0; list-style-type:none;}
-/*登录*/
-.list-item{margin-left:15px;}
-.list-item LI{list-style-type:disc; font-size:13px; line-height:20px; margin-bottom:10px;}
-/* openid styles */
-.form-row{line-height:25px;}
-.submit-row{line-height:30px;padding-top:10px;}
-.errors{line-height:20px;color:red;}
-.error{color:red;}
-.error-list li{padding:5px;}
-.login{margin-bottom:10px;}
-.fieldset{border:solid 1px #777;margin-top:10px;padding:10px;}
-.openid-input{background:url(/content/images/openid.gif) no-repeat;padding-left:15px;cursor:pointer;}
-.openid-login-input{
- background-position:center left;
- background:url(/content/images/openid.gif) no-repeat 0% 50%;
- padding:5px 5px 5px 15px;
- cursor:pointer;
- font-family:Trebuchet MS;
- font-weight:300;
- font-size:150%;
- width:500px;
-}
-
-.openid-login-submit{
- height:40px;
- width:80px;
- line-height:40px;
- cursor:pointer;
- border:1px solid #777;
- font-weight:bold;
- font-size:120%;
-}
-
-.openid-samples{
-
-}
-
-.openid-samples .list, .list li{
- font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;
- list-style:none !important;
- margin-left:-30px !important;
- line-height:20px !important;
-}
-
-/*表单相关*/
-span.form-error {
- color:#990000;
- font-size:90%;
- font-weight:normal;
- margin-left:5px;
-}
-.title-desc{
- color:#999;
- font-size:90%;
-}
-
-/*adjustment for editor preview*/
-#editor{
- font-size:100%;
- min-height:200px;
- line-height: 18px;
- width:100%;
-}
-
-.wmd-preview{
- margin-top:10px;
- padding:6px;
- width:100%;
- background-color:#F5F5F5;
- min-height:20px;
-}
-.wmd-preview pre{
- background-color:#E7F1F8;
-
-}
-
-.wmd-preview blockquote
-{
- background-color: #eee;
-}
-
-.wmd-preview IMG{
- max-width:600px;
-}
-.preview-toggle{
- font-weight:600;
- width:100%;
- color:#aaa;
- letter-spacing:1px;
- text-align:left;
-}
-
-.preview-toggle span:hover{
- cursor:pointer;
-}
-
-.edit-content-html{
- border-top:1px dotted #D8D2A9;
- border-bottom:1px dotted #D8D2A9;
- margin:5px 0 5px 0;
-}
-
-/*修订记录*/
-
-#revisions{
- width:950px;
-}
-
-.revision{
- margin:10px 0 10px 0;
- width:100%;
- font-size:13px;
-}
-
-.revision .header{
- background-color:#eee;
- padding:5px;
- cursor:pointer;
-}
-
-.revision .author{
- background-color:#E9E9FF;
-}
-
-.revision .summary{
- padding: 5px 0 10px 0;
-}
-
-.revision .summary span{
- background-color:yellow;
- display:inline;
-}
-.revision h1{
- font-size:130%;
- font-weight:600;
- padding:15px 0 15px 0;
-}
-
-.revision-mark{
- width:200px;
- text-align:left;
- font-size:90%;
- overflow:hidden;
-}
-
-.revision-number{
- font-size:300%;
- font-weight:bold;
- font-family:arial;
-}
-
-.revision .body{
- padding-left:10px;
- margin-bottom:50px;
-}
-.revision .answerbody{
- padding:10px 0 5px 10px;
-}
-
-/* Revision pages */
-del { color: #FF5F5F; }
-del .post-tag{
-color: #FF5F5F;
-}
-ins { background-color: #97ff97;}
-ins .post-tag{
-background-color: #97ff97;
-}
-
-/*用户资料页面*/
-.count {font-family:Arial;font-size:200%;font-weight:700;color:#777}
-.scoreNumber{font-family:Arial;font-size:35px;font-weight:800;color:#777;line-height:40px;letter-spacing:0px}
-.user-details{font-size:13px;}
-.user-about{background-color:#EEEEEE;height:200px;line-height:20px; overflow:auto;padding:10px;width:90%;}
-.user-edit-link {background:url(/content/images/edit.png) no-repeat; padding-left:20px; font-weight:600;}
-.favorites-count-off {
- color:#919191;
- float:left;
- padding:3px;
- margin:10px 0 0 0 ;
- text-align:center;
-}
-
-.favorites-count {
- color:#D4A849;
- float:left;
- padding:3px;
- margin:10px 0 0 0 ;
- text-align:center;
-}
-.favorites-empty{
- width: 32px; height: 45px; float: left;
-}
-.question-summary {
- border-bottom:1px dotted #999999;
- float:left;
- overflow:hidden;
- padding:11px 0;
- width:670px;
-}
-
-.user-info-table{
-width:950;margin-bottom:10px;
-}
-
-.user-stats-table .question-summary {
- width:800px;
-}
-
-.narrow .stats {
- background:transparent none repeat scroll 0 0;
- float:left;
- height:48px;
- margin:0 0 0 7px;
- padding:0;
- width:auto;
- font-family:Arial;
-}
-
-.narrow .votes {
- background:#EEEEEE none repeat scroll 0 0;
- float:left;
- height:38px;
- margin:0 3px 0 0;
- padding:5px;
- width:40px;
- text-align:center;
- -moz-border-radius: 5px;
- -khtml-border-radius: 5px;
- -webkit-border-radius: 5px;
-}
-
-.narrow .summary {
- width:620px;
-}
-
-.narrow .summary h3 {
- padding:0px;
- margin:0px;
-}
-
-.narrow .views {
- float:left;
- height:38px;
- margin:0 7px 0 0;
- padding:5px 0 5px 4px;
- width:40px;
- text-align:center;
- -moz-border-radius: 5px;
- -khtml-border-radius: 5px;
- -webkit-border-radius: 5px;
- color:#777;
-}
-
-.narrow .status {
- float:left;
- height:38px;
- margin:0 3px 0 0;
- padding:5px;
- width:40px;
- text-align:center;
- -moz-border-radius: 5px;
- -khtml-border-radius: 5px;
- -webkit-border-radius: 5px;
-}
-
-.narrow .vote-count-post {
- font-weight:800;
- margin:0;
- font-size: 190%; color:#555; line-height:20px;
-}
-.narrow .answer-count-post{font-weight:800;margin:0; font-size: 190%; }
-.narrow .views-count-post{font-weight:800;margin:0; font-size: 190%;}
-div.started {
- color:#999999;
- float:right;
- line-height:18px;
-
-}
-
-.narrow div.started {
- line-height:inherit;
- padding-top:4px;
- white-space:nowrap;
- width:auto;
-}
-
-.relativetime {
- font-weight:bold;
- text-decoration:none;
-}
-
-div.started a {
- font-weight:bold;
-}
-
-div.started .reputation-score {
- margin-left:1px;
-}
-
-.narrow .tags{float:left;}
-
-.answer-summary {
- clear:both;
- padding:3px;
-}
-
-.answer-votes {
- background-color:#EEEEEE;
- color:#555555;
- float:left;
- font-family:Arial;
- font-size:110%;
- font-weight:bold;
- height:15px;
- padding:4px 4px 5px;
- text-align:center;
- text-decoration:none;
- width:20px;
- margin-right:10px;
-}
-.vote-count{font-family:Arial; font-size:160%; font-weight:700; color:#777;}
-.user-action{
-
-}
-.user-action-1{
- font-weight:bold;
- color:#333;
-}
-.user-action-2{
- font-weight:bold;
- color:#CCC;
-}
-.user-action-3{
- color:#333;
-}
-.user-action-4{
- color:#333;
-}
-.user-action-5{
- color:darkred;
-}
-.user-action-6{
- color:darkred;
-}
-.user-action-7{
- color:#333;
-}
-.user-action-8{
- padding:3px;
- font-weight:bold;
- background-color:#CCC;
- color:#663333;
-}
-
-.revision-summary{
- background-color:#FFFE9B;
- padding:2px;
-}
-.question-title-link a{
- font-weight:bold;
- color:#0077CC;
-}
-.answer-title-link a{
- color:#333;
-}
-
-.post-type-1 a {
- font-weight:bold;
-
-}
-.post-type-3 a {
- font-weight:bold;
-
-}
-.post-type-5 a {
- font-weight:bold;
- }
-.post-type-2 a{
- color:#333;
-}
-.post-type-4 a{
- color:#333;
-}
-.post-type-6 a{
- color:#333;
-}
-.post-type-8 a{
- color:#333;
-}
-
-
-/*读书频道*/
-.bookInfo {float:left; width:940px;padding:5px;}
-.bookCover {float:left; width:200px;}
-.bookCover img{border:1px solid #ccc;max-width:200px;}
-.bookSummary {float:left; font-size:13px;}
-.blogRss {float:right;margin:0 10px 0 0;width:460px;height:240px;background-color:#EEE; padding:5px;}
-.bookQuestions {margin-bottom:10px;}
-.bookFeed {float:right;}
-.bookAsk{letter-spacing:1px; float:right;margin:-30px 10px 0 0; padding:3px 5px 3px 5px;}
-.bookAsk a {font-size:15px; color:#FFF; font-weight:bold; text-decoration:none;background-color:#EC7000;padding:3px 6px 3px 6px; }
-.bookAsk a:hover {text-decoration:underline;}
-
-
-/*其他全局样式*/
-.hilite { background-color: #ff0; }
-.hilite1 { background-color: #ff0; }
-.hilite2 { background-color: #f0f; }
-.hilite3 { background-color: #0ff; }
-.userStatus {margin-left:12px; color:#FFF; float:right;}
-.userStatus a {color:#FFF;}
-.gold, .badge1 {color:#FFCC00;}
-.silver, .badge2 {color:#CCCCCC;}
-.bronze, .badge3 {color:#CC9933;}
-.score {font-weight:800; color:#333;}
-.footerLinks {color:#EEE; font-size:13px; letter-spacing:1px;}
-.footerLinks a {color:#FFF; font-size:13px;}
-.subSearch {margin-bottom:12px; padding:4px;}
-a.comment {background:#EEE; color:#993300; padding:4px;}
-a.permLink {padding:2px;}
-a.offensive {color:#999;}
-ul.bulleta li {background:url(/content/images/bullet_green.gif) no-repeat 0px 2px; padding-left:16px; margin-bottom:4px;}
-.user {padding:5px; line-height:140%; width:170px;}
-.user ul {margin:0; list-style-type:none;}
-.user .thumb{clear:both;float:left; margin-right:4px; display:inline;}
-.yellowbg{background:yellow;}
-.subtitle{line-height:30px;font-size:15px; font-weight:700;}
-.message{padding:5px;font-weight:bold;background-color:#eee;margin:10px 0 10px 0;}
-.warning{color:red;}
-.darkred{color:darkred;}
-.submit{cursor:pointer;letter-spacing:1px;background-color:#D4D0C8;height:40px;border:1px solid #777;width:100px;font-weight:bold;font-size:120%;}
-.submit:hover{text-decoration:underline;}
-.ask-body{padding-right:10px;}
-.thousand{color:orange;}
-.notify
-{
- position: fixed;
- top: 0px;
- left: 0px;
- width: 100%;
- z-index: 100;
- padding: 7px 0 5px 0;
- text-align: center;
- font-size: 130%;
- font-weight: Bold;
- color: #444;
- background-color: #F4A83D;
-}
-
-.notify span
-{
- float: left;
- width: 95%;
- text-align: center;
-}
-
-.close-notify
-{
- float: right;
- margin-right: 20px;
- color: #735005;
- text-decoration: none;
- background-color: #FAD163;
- border: 2px #735005 solid;
- padding-left: 3px;
- padding-right: 3px;
- cursor:pointer;
+@import url(/content/style/jquery.autocomplete.css);
+@import url(/content/style/openid.css);
+@import url(/content/style/prettify.css);
+/* 公用 */
+body{background:#FFF; font-size:12px; line-height:150%; margin:0; padding:0; color:#000; font-family: sans-serif;
+}
+div{margin:0 auto; padding:0;}
+h1,h2,h3,h4,h5,h6,ul,li,dl,dt,dd,form,img,p{margin:0; padding:0; border:none; }
+input, select {font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;}
+p{margin-bottom:13px; font-size:13px; line-height:140%;}
+a {color:#333333; text-decoration:none;}
+
+.badges a {color:#763333;text-decoration:underline;}
+a:hover {text-decoration:underline;}
+.block{width:960px; height:auto;}
+.fleft{float:left;}
+.fright{float:right;}
+.tleft{text-align:left;}
+.tcenter{text-align:center;}
+.tright{text-align:right;}
+.dis{display:block;}
+.inline{display:inline;}
+.none{display:none;}
+.red{color:#CC0000;}
+.b{font-weight:bold;}
+.f10{font-size:10px;}
+.f11{font-size:11px;}
+.f12{font-size:12px;}
+.f13{font-size:13px;}
+.f14{font-size:14px;}
+.white{color:#FFFFFF;}
+.u{text-decoration:underline;}
+.spacer1{height:6px; line-height:6px; clear:both; visibility:hidden;}
+.spacer3{height:30px; line-height:30px; clear:both; visibility:hidden;}
+h1{font-size:160%;padding:5px 0 5px 0;}
+h2{font-size:140%;padding:3px 0 3px 0;}
+h3{font-size:120%;padding:3px 0 3px 0;}
+ul
+{
+ list-style: disc;
+ margin-left: 20px;
+ padding-left:0px;
+ margin-bottom: 1em;
+}
+ol
+{
+ list-style: decimal;
+ margin-left: 30px;
+ margin-bottom: 1em;
+ padding-left:0px;
+}
+pre
+{
+ font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
+ font-size:100%;
+ margin-bottom: 10px;
+ overflow: auto;
+ width: 580px;
+ background-color: #F5F5F5;
+ padding-left:5px;
+ padding-top:5px;
+ padding-bottom: 20px !ie7;
+}
+
+code{
+ font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
+ font-size:100%;
+
+}
+
+blockquote
+{
+ margin-bottom: 10px;
+ margin-right: 15px;
+ padding: 10px 0px 1px 10px;
+ background-color: #F5F5F5;
+}
+
+/*页面布局*/
+#wrapper {width:960px;margin:auto;padding:0;}
+#roof {
+ position:relative;
+ margin-top:0px;
+ background:#FFF;
+}
+#room {padding:10px 0 10px 0;background-color:#FFF;border-bottom:1px solid #777;}
+#CALeft{width:710px; float:left; position:relative;}
+#CARight{width:240px; float:right;}
+#CAFull{float:left;padding:0 5px 0 5px;width:950px;}
+#ground {width:100%;border-top:1px solid #000; padding-top:6px; padding-bottom:0px; text-align:center;background:#777;}
+/*#licenseLogo {position:absolute;top:10px;right:10px;}*/
+
+/*顶部及导航栏*/
+#top {
+ position:absolute;
+ top:0px;
+ right:0px;
+ height:20px;
+ text-align:right;
+ color:black;
+ padding:3px;
+ background-color:#ffffff;
+ width:500px;
+}
+/*#header {width:960px;}*/
+#top a {height:35px; text-align:right;
+ /*letter-spacing:1px; */
+ margin-left:20px;text-decoration:underline; font-size:12px; color:#333333;}
+#logo {
+ padding: 5px 0px 0px 0px;
+ margin-bottom:-3px;
+}
+#navBar {float:clear;position:relative;display:block;width:960px;}
+#navBar .nav {margin:50px 0px 0px 16px;
+ /*letter-spacing:1px; */
+ }
+#navBar .nav a {color:#333333; background-color:#F9F7ED;
+ border: 1px solid #aaaaaa;
+ border-bottom: none;
+ padding:0px 12px 3px 12px; height:25px; line-height:30px;margin-left:10px; font-size:14px; font-weight:400; text-decoration:none;display: block;float: left;}
+#navBar .nav a:hover {text-decoration:underline}
+#navBar .nav a.on {height:24px;line-height:28px;border:1px solid #d64000; background:#B02B2C; color:#FFF; font-weight:600; text-decoration:none}
+#navBar .nav a.special {font-size:15px; color:#B02B2C; font-weight:bold; text-decoration:none; border:solid 1px;}
+#navBar .nav a.special:hover {text-decoration:underline;}
+#navBar .nav div.focus {position: absolute; top:10px;
+ padding-right:0px;}
+/*搜索栏*/
+#searchBar {
+ background-color:#9db2b1;/*#e9b96e;*/
+ padding:5px 0 0 0;} /* #B02B2C */
+#searchBar .content { }
+#searchBar .searchInput {font-size:13px; height:18px; width:400px;}
+#searchBar .searchBtn {font-size:14px; height:26px; width:80px;}
+#searchBar .options {padding-top:5px; font-size:100%;color:#EEE;
+ /*letter-spacing:1px;*/
+ }
+#searchBar .options INPUT {margin-left:15px;}
+#searchBar .options INPUT:hover {cursor:pointer}
+
+/*问题列表*/
+#listA {float:left; background-color:#FFF;padding:0 0px 0 0px; width:100%;}
+#listA .qstA {padding:3px 5px 0 5px; margin:0 0px 10px 0px; background:url(/content/images/quest-bg.gif) repeat-x top;}
+#listA .qstA thumb {float:left; }
+#listA .qstA H2 {font-size:15px; font-weight:800; margin:8px auto;padding:0px;}
+#listA .qstA H2 a {color:#663333; }
+#listA .qstA .stat {font-size:13px;letter-spacing:1px;float:right;}
+#listA .qstA .stat span {margin-right:5px;}
+#listA .qstA .stat td {min-width:40px;text-align:center;}
+#listA .qstA .stat .num {font-family:arial;color:#905213; font-size:20px; font-weight:800;}
+#listA .qstA .stat .unit {color:#777;}
+#listA .qstA .from {margin-top:5px; font-size:13px;}
+#listA .qstA .from .score {font-family:arial;}
+#listA .qstA .date {margin-left:20px; color:#777;}
+#listA .qstA .wiki {color:#663333;font-size:12px;}
+#listA .qstA .from a {}
+#listA .qstA .from IMG {vertical-align:middle;}
+#listA .qstA .author {font-weight:400; }
+#listA .qstA .author a{ }
+#listA .qstA .summary{margin-right:5px;}
+
+.evenMore {font-size:14px; font-weight:800;}
+.questions-count{font-size:32px;font-family:sans-serif;font-weight:600;padding:0 0 5px 7px;color:#a40000;}
+
+/*内容块*/
+.boxA {background:#777; padding:6px; margin-bottom:8px;}
+.boxA H3 {font-size:13px; font-weight:800; color:#FFF; margin:0; padding:0; margin-bottom:4px;}
+.boxA .body {border:1px solid #999; padding:8px; background:#FFF; font-size:13px;}
+.boxA .more {padding:2px; text-align:right; font-weight:800;}
+.boxB {background:#F9F7CD; padding:6px; margin-bottom:8px;}
+.boxB H3 {font-size:13px; font-weight:800; color:#000; margin:0; padding:0 0 0 15px; margin-bottom:4px; background:url(/content/images/dot-g.gif) no-repeat left center;}
+.boxB .body {border:1px solid #FFFF88; padding:8px; background:#FFF; font-size:13px; line-height:160%;}
+.boxB .more {padding:1px; text-align:right; font-weight:800;}
+.boxC {background:#F5F5F5; padding:6px; margin-bottom:8px;}
+/*分页*/
+.pager {margin-top:10px; margin-bottom:16px; float:left;}
+.pagesize {margin-top:10px; margin-bottom:16px; float:right;}
+
+/** PAGINATOR **/
+.paginator {
+ padding:5px 0 10px 0;
+ font:normal 12px sans-serif;
+}
+
+.paginator .prev-na,
+.paginator .next-na {
+ padding:.3em;
+ font:bold .875em sans-serif;
+}
+
+.paginator .prev-na,
+.paginator .next-na {
+ border:1px solid #ccc;
+ background-color:#f9f9f9;
+ color:#aaa;
+ font-weight:normal;
+}
+
+.paginator .prev a, .paginator .prev a:visited,
+.paginator .next a, .paginator .next a:visited {
+ border:1px solid #fff;
+ background-color:#fff;
+ color:#777;
+ padding:.3em;
+ font:bold 100% sans-serif;
+}
+
+.paginator .prev, .paginator .prev-na { margin-right:.5em; }
+.paginator .next, .paginator .next-na { margin-left:.5em; }
+
+.paginator .page a, .paginator .page a:visited, .paginator .curr {
+ padding:.25em;
+ font:normal .875em verdana;
+ border:1px solid #ccc;
+ background-color:#fff;
+ margin:0em .25em;
+ color:#777;
+}
+
+.paginator .curr {
+ background-color:#777;
+ color:#fff;
+ border:1px solid #777;
+ font-weight:bold;
+}
+
+.paginator .page a:hover,
+.paginator .curr a:hover,
+.paginator .prev a:hover,
+.paginator .next a:hover {
+ color:#fff;
+ background-color:#777;
+ border:1px solid #777;
+ text-decoration:none;
+}
+
+.paginator .text{
+ color:#777;
+ padding:.3em;
+ font:bold 100% sans-serif;
+}
+
+.paginator-container{
+ float:right;
+ padding:10px 0 10px 0;
+}
+
+.paginator-container-left{
+ padding:5px 0 10px 0;
+}
+
+/*标签*/
+.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;}
+.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: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;}
+
+/*奖牌*/
+a.medal { font-size:14px; line-height:250%; font-weight:800; color:#333; text-decoration:none; background:url(/content/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;}
+a:hover.medal {color:#333; text-decoration:none; background:url(/content/images/medala_on.gif) no-repeat; border-left:1px solid #E7E296; border-top:1px solid #E7E296; border-bottom:1px solid #D1CA3D; border-right:1px solid #D1CA3D;}
+
+/*Tab栏*/
+.tabBar{background-color:#FFF;border-bottom: 1px solid #666;height: 30px; width: 100%;clear:both; margin-bottom:3px;}
+.tabsA {background-color:#FFF;float:right;position:relative;display:block;font-weight:bold;height:20px;}
+.tabsB {background-color:#FFF;float:left;position:relative;display:block;font-weight:bold;height:20px;}
+.tabsA a.on, .tabsA a.on:hover,.tabsB a.on, .tabsB a.on:hover {background: #fff;
+ color:#333;
+ border: 1px solid #777;
+ border-bottom:2px solid #FFF;
+ height: 25px;
+ line-height: 26px;
+ margin-top: 3px;
+ padding: 0px 11px 0px 11px;}
+
+.tabsA a {background: #eee;
+ border: 1px solid #eee;
+ color: #777;
+ display: block;
+ float: left;
+ height: 22px;
+ line-height: 28px;
+ margin: 5px 4px 0 0;
+ padding: 0 11px 0 11px;
+ text-decoration: none;
+}
+.tabsB a {background: #eee;
+ border: 1px solid #eee;
+ color: #777;
+ display: block;
+ float: left;
+ height: 22px;
+ line-height: 28px;
+ margin: 5px 0px 0 4px;
+ padding: 0 11px 0 11px;
+ text-decoration: none;
+}
+.tabsA a:hover, .tabsB a:hover {background: #fff;border: 1px solid #777;border-bottom:3px solid #FFF;}
+.headlineA {font-size:13px; border-bottom:1px solid #777; padding-bottom:2px; font-weight:800; margin-bottom:12px; text-align:right; height:30px;}
+.headQuestions {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;}
+.headAnswers {float:left; padding:3px; font-size:18px; font-weight:800; background:url(/content/images/ico_answers.gif) left 2px no-repeat; padding-left:24px;}
+.headTags {float:left; padding:3px; font-size:18px; font-weight:800; background:url(/content/images/ico_tags.gif) no-repeat; padding-left:24px;}
+.headUsers {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;}
+.headMedals {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;}
+.headLogin {float:left; padding:3px; font-size:15px; font-weight:800; background:url(/content/images/ico_login.gif) no-repeat; padding-left:24px;}
+.headNormal {text-align:left;padding:3px; font-size:15px; margin-bottom:12px; font-weight:800;border-bottom:1px solid #777;}
+.headUser {text-align:left;padding:5px; font-size:20px; letter-spacing:1px;margin-bottom:12px; font-weight:800;border-bottom:1px solid #777;}
+/*RSS订阅*/
+#feeds {margin:10px 0; }
+#feeds a {background:url(/content/images/feed-icon-small.png) no-repeat 0; padding-left:18px; font-weight:700; font-size:13px; }
+
+/*问题*/
+#question {margin-bottom:30px;}
+#question h1{font-size:15px;background:#CCC; padding:6px 8px;;}
+#question .body{background:#F7F7F7; padding:20px 10px;}
+.starter {padding:10px; background:#E0EAF1;}
+.vote {font-size:20px; color:#666; font-weight:800;}
+.questions-related{font-weight:700;word-wrap:break-word;}
+.questions-related p{line-height:20px; margin-bottom:10px;font-size:100%;}
+.question-status{
+ margin-top:10px;
+ padding: 20px;
+ background-color:#F5F5F5;
+ text-align:center;
+}
+.question-status h3{font-size:125%;}
+.question-body{
+ min-height:100px;
+ font-size:13px;
+ line-height:20px;
+}
+.question-body IMG{
+ max-width:600px;
+}
+.question-mark{
+ /*background-color:#fff5e0;
+ border-top: 1px solid #eeeeec;
+ border-right: 1px solid #babdb6;
+ border-bottom: 1px solid #babdb6;
+ border-left: 1px solid #eeeeec;*/
+ text-align:left;
+ padding:5px;
+ overflow:hidden;
+}
+.question-edit{
+ text-align:left;
+ overflow:hidden;
+}
+.vote-buttons {float:left;text-align:center;}
+.vote-buttons IMG{cursor:pointer;}
+.vote-number{
+ font-family:Arial;
+ padding:0px 0 3px 0;
+ font-size:140%;
+ font-weight:bold;
+ color:#777;
+}
+.question-img-upvote:hover{background:url(/content/images/vote-arrow-up-on.png)}
+.question-img-downvote:hover{background:url(/content/images/vote-arrow-down-on.png)}
+.question-img-favorite:hover{background:url(/content/images/vote-favorite-on.png)}
+.favorite-number{padding:0px;font-size:100%; font-family:Arial;font-weight:bold;color:#777;}
+.vote-notification
+{
+ z-index: 1;
+ cursor: pointer;
+ display: none;
+ position: absolute;
+ padding: 15px;
+ color: White;
+ background-color: darkred;
+ text-align: center;
+}
+.vote-notification a
+{
+ color: White;
+ text-decoration:underline;
+}
+.offensive-flag a{
+ color:#777;
+ padding:3px;
+ cursor:pointer;
+}
+
+.offensive-flag a:hover{
+ background-color:#777;
+ text-decoration:none;
+ color:#fff;
+}
+
+.linksopt a{
+ color:#777;
+ padding:3px;
+ cursor:pointer;
+}
+
+.linksopt a:hover{
+ background-color:#777;
+ text-decoration:none;
+ color:#fff;
+}
+
+.action-link a{
+ color:#777;
+ padding:3px;
+ cursor:pointer;
+}
+
+.action-link a:hover{
+ background-color:#777;
+ text-decoration:none;
+ color:#fff;
+}
+.action-link-separator{
+ color:#ccc;
+}
+.wiki-category{
+ margin-left:5px;
+ color:#999;
+ font-size:90%;
+}
+
+div.comments {
+ line-height:150%;
+ padding:10px 0;
+}
+
+div.post-comments{
+ width:100%;
+ margin-bottom:10px;
+}
+
+form.post-comments textarea {
+ height:6em;
+ margin-bottom:4px;
+}
+
+form.post-comments input {
+ margin-left:10px;
+ margin-top:1px;
+ vertical-align:top;
+ width:100px;
+}
+span.text-counter {
+ margin-right:20px;
+}
+
+span.form-error {
+ color:#990000;
+ font-weight:normal;
+ margin-left:5px;
+}
+p.form-item {
+ margin:0px;
+}
+
+div.comments-container, div.comments-container-accepted, div.comments-container-owner, div.comments-container-deleted {
+ display:none;
+ margin-top:-1px;
+ padding:0 5px 5px;
+}
+
+div.comments-container, a.comments-link {
+ background-color:#EEEEEE;
+}
+
+.post-comments a {
+ color:#888888;
+ padding:0 3px 2px;
+}
+
+a.comments-link, a.comments-link-accepted, a.comments-link-owner, a.comments-link-deleted {
+ color:black;
+ padding:2px;
+ cursor:pointer;
+}
+
+.post-comments a:hover {
+ background-color:#777777;
+ color:white;
+ text-decoration:none;
+}
+
+a.comment-user, a.comment-user:hover {
+ background-color:inherit;
+ color:#0077CC;
+ padding:0;
+}
+
+a.comment-user:hover {
+ text-decoration:underline;
+}
+/*回答*/
+#answers {}
+.answer{
+ padding-top:10px;
+ width: 100%;
+}
+.answer-body{
+ min-height:80px;
+ font-size:13px;
+ line-height:20px;
+}
+
+.answer-body IMG{
+ max-width:600px;
+}
+
+.accepted-answer{
+ background-color:#EBFFE6;
+ border-bottom-color:#9BD59B;
+}
+
+.accepted-answer .comments-link{
+ background-color:#CCFFBF;
+}
+
+.accepted-answer .comments-container{
+ background-color:#CCFFBF;
+}
+
+.answered
+{
+ background: #CCC;
+ color: #999;
+}
+
+.answered-accepted
+{
+ background: #CCC;
+ color: #763333;
+}
+
+.unanswered
+{
+ background: #777;
+ color: white;
+}
+
+.answered-by-owner
+{
+ background: #E9E9FF;
+}
+
+.answered-by-owner .comments-link
+{
+ background-color:#E6ECFF;
+}
+
+.answered-by-owner .comments-container
+{
+ background-color:#E6ECFF;
+}
+
+.answered-accepted strong
+{
+ color: #E1E818;
+}
+
+.answer-img-accept:hover{background:url(/content/images/vote-accepted-on.png)}
+
+.deleted{
+ background:#F4E7E7 none repeat scroll 0 0;
+}
+
+/*标签列表*/
+/*
+.tagsbox {}
+.tagsbox a {color:#000;line-height:30px;margin-right:10px;font-size:100%;background-color:#F9F7ED;padding:3px;border:1px solid #aaaaaa;}
+.tagsbox a:hover {text-decoration:none;background-color:#F9F7ED;color:#B02B2C;} */
+.tagsList {margin:0; list-style-type:none;padding:0px;min-height:360px;}
+.tagsList li {width:235px; float:left;}
+.badge-list{margin:0; list-style-type:none;}
+/*登录*/
+.list-item{margin-left:15px;}
+.list-item LI{list-style-type:disc; font-size:13px; line-height:20px; margin-bottom:10px;}
+/* openid styles */
+.form-row{line-height:25px;}
+.submit-row{line-height:30px;padding-top:10px;}
+.errors{line-height:20px;color:red;}
+.error{color:red;}
+.error-list li{padding:5px;}
+.login{margin-bottom:10px;}
+.fieldset{
+/* border:solid 1px #777;*/
+ border: none;
+ margin-top:10px;
+ padding:10px;
+}
+.openid-input{background:url(/content/images/openid.gif) no-repeat;padding-left:15px;cursor:pointer;}
+.openid-login-input{
+ background-position:center left;
+ background:url(/content/images/openid.gif) no-repeat 0% 50%;
+ padding:5px 5px 5px 15px;
+ cursor:pointer;
+ font-family:Trebuchet MS;
+ font-weight:300;
+ font-size:150%;
+ width:500px;
+}
+
+.openid-login-submit{
+ height:40px;
+ width:80px;
+ line-height:40px;
+ cursor:pointer;
+ border:1px solid #777;
+ font-weight:bold;
+ font-size:120%;
+}
+
+.openid-samples{
+
+}
+
+.openid-samples .list, .list li{
+ font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;
+ list-style:none !important;
+ margin-left:-30px !important;
+ line-height:20px !important;
+}
+
+/*表单相关*/
+span.form-error {
+ color:#990000;
+ font-size:90%;
+ font-weight:normal;
+ margin-left:5px;
+}
+.title-desc{
+ color:#999;
+ font-size:90%;
+}
+
+/*adjustment for editor preview*/
+#editor{
+ font-size:100%;
+ min-height:200px;
+ line-height: 18px;
+ width:100%;
+}
+
+.wmd-preview{
+ margin-top:10px;
+ padding:6px;
+ width:100%;
+ background-color:#F5F5F5;
+ min-height:20px;
+}
+.wmd-preview pre{
+ background-color:#E7F1F8;
+
+}
+
+.wmd-preview blockquote
+{
+ background-color: #eee;
+}
+
+.wmd-preview IMG{
+ max-width:600px;
+}
+.preview-toggle{
+ font-weight:600;
+ width:100%;
+ color:#aaa;
+ /*letter-spacing:1px;*/
+ text-align:left;
+}
+
+.preview-toggle span:hover{
+ cursor:pointer;
+}
+
+.edit-content-html{
+ border-top:1px dotted #D8D2A9;
+ border-bottom:1px dotted #D8D2A9;
+ margin:5px 0 5px 0;
+}
+
+/*修订记录*/
+
+#revisions{
+ width:950px;
+}
+
+.revision{
+ margin:10px 0 10px 0;
+ width:100%;
+ font-size:13px;
+}
+
+.revision .header{
+ background-color:#eee;
+ padding:5px;
+ cursor:pointer;
+}
+
+.revision .author{
+ background-color:#E9E9FF;
+}
+
+.revision .summary{
+ padding: 5px 0 10px 0;
+}
+
+.revision .summary span{
+ background-color:yellow;
+ display:inline;
+}
+.revision h1{
+ font-size:130%;
+ font-weight:600;
+ padding:15px 0 15px 0;
+}
+
+.revision-mark{
+ width:200px;
+ text-align:left;
+ font-size:90%;
+ overflow:hidden;
+}
+
+.revision-number{
+ font-size:300%;
+ font-weight:bold;
+ font-family:sans-serif;
+}
+
+.revision .body{
+ padding-left:10px;
+ margin-bottom:50px;
+}
+.revision .answerbody{
+ padding:10px 0 5px 10px;
+}
+
+/* Revision pages */
+del { color: #FF5F5F; }
+del .post-tag{
+color: #FF5F5F;
+}
+ins { background-color: #97ff97;}
+ins .post-tag{
+background-color: #97ff97;
+}
+
+/*用户资料页面*/
+.count {font-family:Arial;font-size:200%;font-weight:700;color:#777}
+.scoreNumber{font-family:Arial;font-size:35px;font-weight:800;color:#777;line-height:40px;
+ /*letter-spacing:0px*/
+ }
+.user-details{font-size:13px;}
+.user-about{background-color:#EEEEEE;height:200px;line-height:20px; overflow:auto;padding:10px;width:90%;}
+.user-edit-link {background:url(/content/images/edit.png) no-repeat; padding-left:20px; font-weight:600;}
+.favorites-count-off {
+ color:#919191;
+ float:left;
+ padding:3px;
+ margin:10px 0 0 0 ;
+ text-align:center;
+}
+
+.favorites-count {
+ color:#D4A849;
+ float:left;
+ padding:3px;
+ margin:10px 0 0 0 ;
+ text-align:center;
+}
+.favorites-empty{
+ width: 32px; height: 45px; float: left;
+}
+.question-summary {
+ border-bottom:1px dotted #999999;
+ float:left;
+ overflow:hidden;
+ padding:11px 0;
+ width:670px;
+}
+
+.user-info-table{
+width:950;margin-bottom:10px;
+}
+
+.user-stats-table .question-summary {
+ width:800px;
+}
+
+.narrow .stats {
+ background:transparent none repeat scroll 0 0;
+ float:left;
+ height:48px;
+ margin:0 0 0 7px;
+ padding:0;
+ width:auto;
+ font-family:Arial;
+}
+
+.stats div {
+ font-size:11px;
+ text-align:center;
+}
+
+.narrow .votes {
+ background:#EEEEEE none repeat scroll 0 0;
+ float:left;
+ height:42px;
+ margin:0 3px 0 0;
+ padding:5px;
+ width:46px;
+ text-align:center;
+ -moz-border-radius: 5px;
+ -khtml-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+
+.narrow .summary {
+ width:620px;
+}
+
+.narrow .summary h3 {
+ padding:0px;
+ margin:0px;
+}
+
+.narrow .views {
+ float:left;
+ height:42px;
+ margin:0 7px 0 0;
+ /*padding:5px 0 5px 4px;*/
+ padding: 5px;
+ width:46px;
+ text-align:center;
+ -moz-border-radius: 5px;
+ -khtml-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ color:#777;
+}
+
+.narrow .status {
+ float:left;
+ height:42px;
+ margin:0 3px 0 0;
+ padding:5px;
+ width:46px;
+ text-align:center;
+ -moz-border-radius: 5px;
+ -khtml-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+
+.narrow .vote-count-post {
+ font-weight:800;
+ margin:0;
+ font-size: 190%; color:#555; line-height:20px;
+}
+.narrow .answer-count-post{font-weight:800;margin:0; font-size: 190%; }
+.narrow .views-count-post{font-weight:800;margin:0; font-size: 190%;}
+div.started {
+ color:#999999;
+ float:right;
+ line-height:18px;
+
+}
+
+.narrow div.started {
+ line-height:inherit;
+ padding-top:4px;
+ white-space:nowrap;
+ width:auto;
+}
+
+.relativetime {
+ font-weight:bold;
+ text-decoration:none;
+}
+
+div.started a {
+ font-weight:bold;
+}
+
+div.started .reputation-score {
+ margin-left:1px;
+}
+
+.narrow .tags{float:left;}
+
+.answer-summary {
+ clear:both;
+ padding:3px;
+}
+
+.answer-votes {
+ background-color:#EEEEEE;
+ color:#555555;
+ float:left;
+ font-family:Arial;
+ font-size:110%;
+ font-weight:bold;
+ height:15px;
+ padding:4px 4px 5px;
+ text-align:center;
+ text-decoration:none;
+ width:20px;
+ margin-right:10px;
+}
+.vote-count{font-family:Arial; font-size:160%; font-weight:700; color:#777;}
+.user-action{
+
+}
+.user-action-1{
+ font-weight:bold;
+ color:#333;
+}
+.user-action-2{
+ font-weight:bold;
+ color:#CCC;
+}
+.user-action-3{
+ color:#333;
+}
+.user-action-4{
+ color:#333;
+}
+.user-action-5{
+ color:darkred;
+}
+.user-action-6{
+ color:darkred;
+}
+.user-action-7{
+ color:#333;
+}
+.user-action-8{
+ padding:3px;
+ font-weight:bold;
+ background-color:#CCC;
+ color:#763333;
+}
+
+.revision-summary{
+ background-color:#FFFE9B;
+ padding:2px;
+}
+.question-title-link a{
+ font-weight:bold;
+ color:#0077CC;
+}
+.answer-title-link a{
+ color:#333;
+}
+
+.post-type-1 a {
+ font-weight:bold;
+
+}
+.post-type-3 a {
+ font-weight:bold;
+
+}
+.post-type-5 a {
+ font-weight:bold;
+ }
+.post-type-2 a{
+ color:#333;
+}
+.post-type-4 a{
+ color:#333;
+}
+.post-type-6 a{
+ color:#333;
+}
+.post-type-8 a{
+ color:#333;
+}
+
+
+/*读书频道*/
+.bookInfo {float:left; width:940px;padding:5px;}
+.bookCover {float:left; width:200px;}
+.bookCover img{border:1px solid #ccc;max-width:200px;}
+.bookSummary {float:left; font-size:13px;}
+.blogRss {float:right;margin:0 10px 0 0;width:460px;height:240px;background-color:#EEE; padding:5px;}
+.bookQuestions {margin-bottom:10px;}
+.bookFeed {float:right;}
+.bookAsk{
+ /*letter-spacing:1px; */
+ float:right;margin:-30px 10px 0 0; padding:3px 5px 3px 5px;}
+.bookAsk a {font-size:15px; color:#FFF; font-weight:bold; text-decoration:none;background-color:#EC7000;padding:3px 6px 3px 6px; }
+.bookAsk a:hover {text-decoration:underline;}
+
+
+/*其他全局样式*/
+.hilite { background-color: #ff0; }
+.hilite1 { background-color: #ff0; }
+.hilite2 { background-color: #f0f; }
+.hilite3 { background-color: #0ff; }
+.userStatus {margin-left:12px; color:#FFF; float:right;}
+.userStatus a {color:#FFF;}
+.gold, .badge1 {color:#FFCC00;}
+.silver, .badge2 {color:#CCCCCC;}
+.bronze, .badge3 {color:#CC9933;}
+.score {font-weight:800; color:#333;}
+.footerLinks {color:#EEE; font-size:13px;
+ /* letter-spacing:1px;*/
+ }
+.footerLinks a {color:#FFF; font-size:13px;}
+.subSearch {margin-bottom:12px; padding:4px;}
+a.comment {background:#EEE; color:#993300; padding:4px;}
+a.permLink {padding:2px;}
+a.offensive {color:#999;}
+ul.bulleta li {background:url(/content/images/bullet_green.gif) no-repeat 0px 2px; padding-left:16px; margin-bottom:4px;}
+.user {padding:5px; line-height:140%; width:170px;}
+.user ul {margin:0; list-style-type:none;}
+.user .thumb{clear:both;float:left; margin-right:4px; display:inline;}
+.yellowbg{background:yellow;}
+
+.message{
+ padding:5px;
+ margin:10px 0 10px 0;
+ background-color:#eee;
+ border: 1px solid #aaaaaa;
+}
+.message h1 {
+ padding-top:0px;
+ font-size:15px;
+}
+.message p {
+ margin-bottom:0px;
+}
+
+.warning{color:red;}
+.darkred{color:darkred;}
+.submit{
+ cursor:pointer;
+ /*letter-spacing:1px;*/
+ background-color:#D4D0C8;
+ height:40px;
+ border:1px solid #777;
+/* width:100px; */
+ font-weight:bold;
+ font-size:120%;}
+.submit:hover{text-decoration:underline;}
+.ask-body{padding-right:10px;}
+.thousand{color:orange;}
+.notify
+{
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ z-index: 100;
+ padding: 0;
+ text-align: center;
+ font-weight: Bold;
+ color: #444;
+ background-color: #F4A83D;
+}
+.notify p {
+ margin-top:5px;
+ margin-bottom:5px;
+ font-size:16px;
+}
+#close-notify
+{
+ position:absolute;
+ right:5px;
+ top:5px;
+ padding:0 3px 0 3px;
+ color: #735005;
+ text-decoration: none;
+ font-size:14px;
+ line-height:18px;
+ background-color: #FAD163;
+ border: 2px #735005 solid;
+ cursor:pointer;
+}
+#close-notify:hover {
+ text-decoration:none;
+}
+
+.big {
+ font-size:15px;
+}
+.bigger {
+ font-size:14px;
+}
+.strong {
+ font-weight:bold;
+}
+.orange
+{
+ color:#d64000;
+ font-weight:bold;
+}
+
+.about div {
+ padding:10px 5px 10px 5px;
+ border-top:1px dashed #aaaaaa;
+}
+.about div.first {
+ padding-top:0;
+ border-top:none;
+}
+.about p {
+ margin-bottom:10px;
+}
+.about a {color:#d64000;text-decoration:underline;}
+.about h3{
+ line-height:30px;
+ font-size:15px;
+ font-weight:700;
+ padding-top: 0px;
+}
+.highlight {
+ background-color:#FFF8C6;
}
diff --git a/templates/faq.html b/templates/faq.html
index e9650c3e..78c433ae 100644
--- a/templates/faq.html
+++ b/templates/faq.html
@@ -1,114 +1,148 @@
{% extends "base_content.html" %}
+<!-- template faq.html -->
{% load extra_tags %}
{% load humanize %}
+{% load i18n %}
{% block title %}{% spaceless %}FAQ{% endspaceless %}{% endblock %}
{% block forejs %}
{% endblock %}
{% block content %}
<div class="headNormal">
- 常见问题(FAQ)
+ {% trans "Frequently Asked Questions " %}(FAQ)
</div>
-<div id="main-body" style="width:100%">
-
- <h3 class="subtitle">我可以在这里提问什么样的问题?</h3>
- <p>毫无疑问,首先必须是<span class="yellowbg">技术编程问题!</span>
- 提问之前,充分利用系统的自动查找、标签和搜索,看看是否已经有一样的问题并有了答案。<br>
- 避免问相同的问题,减少后有问题的朋友的疑惑,以后能够清晰地找到唯一问题和答案。
- </p><br>
-
- <h3 class="subtitle">什么样的问题我不该在这里提问?</h3>
- <p> <span class="yellowbg">与程序员或技术无关的,引起争吵或太过于主观性等违背社区宗旨的内容。</span>本站建立是为了帮助大众程序员解决实际技术问题,我们需要实际的问题!
- </p><p>
- 同样,在社区建立之初,很多用户可能对社区有各种各样的疑问。我们希望能够通过更多的faq或者文档去解释这些疑问,如果用户没有找到答案,希望你在社区提出此类问题,并引用“faq”或者“cnprog”等相关的标签。毕竟,访问社区的用户,是关心他们要寻找的答案,不是来学习如何使用复杂的社区功能的。
- </p><p>
- 如果还没有拜读过Eric S. Raymond的<a href="http://www.catb.org/~esr/faqs/smart-questions.html">《How To Ask Questions The Smart Way》</a>的朋友,推荐先读一下大师的箴言。另外,这里有一<a href="http://www.dianbo.org/9238/stone/tiwendezhihui.htm">中文版本</a>,可以参考浏览。
- </p><p><span class="yellowbg">管理运行社区的是你们,而不是我们。</span></p>
- </p><br>
+<div id="main-body" class="about" style="width:100%">
+
+ <div class="first">
+ <h3 class="subtitle">{% trans "What kinds of questions can I ask here?" %}</h3>
+ <p>{% trans "Most importanly - questions should be <strong>relevant</strong> to this community." %}
+ {% trans "Before asking the question - please make sure to use search to see whether your question has alredy been answered."%}
+ </p>
+
+ <h3 class="subtitle">{% trans "What questions should I avoid asking?" %}</h3>
+ <p>{% trans "Please avoid asking questions that are not relevant to this community, too subjective and argumentative." %}</p>
+ </p>
+ </div>
- <h3 class="subtitle">什么样的回答是不受欢迎的?</h3>
- <p> CNProg 希望用户提供针对提问的技术回答,可以是进一步了解问题实质,给予参考方案,或完全解决问题的回答。我们希望通过问答的形式解决用户的实际问题。因此,<span class="yellowbg">我们不欢迎在回答中出现不是回答问题的内容,包括针对他人回答的讨论,和其他无意义的浪费网络资源行为</span>。CNProg建议您使用<span class="yellowbg">评论</span>功能来讨论你的意见和想法。
-
- </p><br>
+ <div>
+ <h3 class="subtitle">{% trans "What should I avoid in my answers?" %}</h3>
+ <p>{% trans "site title" %} {% trans "is a Q&A site, not a discussion group. Therefore - please avoid having discussions in your answers, comment facility allows some space for brief discussions." %}</p>
+ </div>
- <h3 class="subtitle">谁是社区的管理员?</h3>
- <p> 答案是:<span class="yellowbg">每个用户。</span><br>
+ <div>
+ <h3 class="subtitle">{% trans "Who moderates this community?" %}</h3>
+ <p>{% trans "The short answer is: <strong>you</strong>." %}
+ {% trans "This website is moderated by the users." %}
+ {% trans "The reputation system allows users earn the authorization to perform a variety of moderation tasks." %}
+ </p>
+ </div>
- 社区没有严格意义上的管理员身份。
- 通过积分运作,<span class="yellowbg">每个用户都有权限创建标签,进行对所有问题、回答的投票、编辑、关闭等操作。</span>
- </p><br>
-
- <h3 class="subtitle">什么是社区积分?</h3>
- <p> 对于正常使用社区进行提问、回答而言,积分不是必须的。<br>
- 我们一再声明,CNProg由你来运行和维护。如果你想帮助我们来运作CNProg,你需要一定的积分等级。<span class="yellowbg">积分是一种用来粗略衡量社区对你有多信任的数据。</span>积分不是有谁来支付或直接给予你的,而是你通过获得其他用户的支持和信任“赚得”的。
- <br><br>
- 举例来说,如果你提了一个非常有帮助的问题或者做了很有用的回答,你将会被其他用户投赞成票。相反,你提了不受欢迎的问题,或者误导用户的回答,你将可能被其他用户投反对票。每个赞成票会帮你产生<strong>10</strong>个社区积分,每个反对票会相应扣除你<strong>2</strong>个积分。每天通过别人投赞成票,你最多只能产生<strong>200</strong>个积分,这是上限。当你累计到一定积分,你可以在社区做更多的事情:<br>
- <table style="font-family:arial;" cellspacing="3" cellpadding="3">
- <tr>
- <th width="40px" style="text-align:right"></th>
- <th width="300px"></th>
- </tr>
- <tr>
- <td style="text-align:right;padding-right:5px"><strong>15</strong></td>
- <td>投赞成票</td>
- </tr>
- <tr>
- <td style="text-align:right;padding-right:5px"><strong>15</strong></td>
- <td>标记垃圾帖</td>
- </tr>
- <tr>
- <td style="text-align:right;padding-right:5px"><strong>50</strong></td>
- <td>添加评论</td>
- </tr>
- <tr>
- <td style="text-align:right;padding-right:5px"><strong>100</strong></td>
- <td>投反对票</td>
- </tr><tr>
- <td style="text-align:right;padding-right:5px"><strong>250</strong></td>
- <td>打开关闭自己的问题</td>
- </tr>
- <tr>
- <td style="text-align:right;padding-right:5px"><strong>500</strong></td>
- <td>给任何问题整理标签</td>
- </tr>
- <tr>
- <td style="text-align:right;padding-right:5px"><strong>750</strong></td>
- <td>编辑wiki类问题</td>
- </tr>
- <tr>
- <td style="text-align:right;padding-right:5px"><strong>2000</strong></td>
- <td>编辑任何问题或答案</td>
- </tr>
- <tr>
- <td style="text-align:right;padding-right:5px"><strong>3000</strong></td>
- <td>打开关闭任何人的问题</td>
- </tr>
- <tr>
- <td style="text-align:right;padding-right:5px"><strong>5000</strong></td>
- <td>删除任何一个评论</td>
- </tr>
- <tr>
- <td style="text-align:right;padding-right:5px"><strong>10000</strong></td>
- <td>删除任何一个问题或答案,及其他管理功能</td>
- </tr>
+ <div>
+ <h3 class="subtitle">{% trans "How does reputation system work?" %}</h3>
+ <p>{% trans "Rep system summary" %}</p>
+ <p>
+ For example, if you ask an interesting question or give a helpful answer, your input will be upvoted.
+ On the other hand if the answer is misleading - it will be downvoted.
+ Each vote in favor will generate <strong>10</strong> points, each vote against will subtract <strong>2</strong> points.
+ There is a limit of <strong>200</strong> points that can be accumulated per question or answer.
- </table>
-
- </p><br>
-
- <h3 class="subtitle">我需要注册一个新用户吗?</h3>
- <p> 不需要。社区提供了OpenID的登录支持,你要用Google、Yahoo等任何支持OpenID登录的帐号就可以使用系统。<strong><a href="/account/signin">马上登录</a> »</strong>
- </p><br>
-
- <h3 class="subtitle">为什么其他人可以修改我的问题/回答?</h3>
- <p> CNProg 是为了帮助程序员解决更多问题,更加方便的解决问题。所以问题和答案都是如Wiki一样可编辑的,我们希望社区能帮助用户沉淀、积累更多有用的知识和经验。如果您不喜欢这种方式,我们尊重你的选择。
- </p><br>
+ The table below explains reputation point requirements for each type of moderation task.
+ </p>
+
+ <table style="font-family:arial;" cellspacing="3" cellpadding="3">
+ <tr>
+ <th width="40px" style="text-align:right"></th>
+ <th width="300px"></th>
+ </tr>
+ <!--
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>15</strong></td>
+ <td>{% trans "upvote" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>15</strong></td>
+ <td>{% trans "use tags" %}</td>
+ </tr>
+ -->
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>50</strong></td>
+ <td>{% trans "add comments" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>100</strong></td>
+ <td>{% trans "downvote" %}</td>
+ </tr><tr>
+ <td style="text-align:right;padding-right:5px"><strong>250</strong></td>
+ <td>{% trans "open and close own questions" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>500</strong></td>
+ <td>{% trans "retag questions" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>750</strong></td>
+ <td>{% trans "edit community wiki questions" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>2000</strong></td>
+ <td>{% trans "edit any answer" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>3000</strong></td>
+ <td>{% trans "open any closed question" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>5000</strong></td>
+ <td>{% trans "delete any comment" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>10000</strong></td>
+ <td>{% trans "delete any questions and answers and perform other moderation tasks" %}</td>
+ </tr>
+
+ </table>
+ </div>
+ {% ifequal settings.EMAIL_VALIDATION 'on' %}
+ <div>
+ <a id='validate'></a><h3 class="subtitle">{% trans "how to validate email title" %}</h3>
+ <!--special case here message must contain paragraphs-->
+ {% trans "how to validate email info" %}
+ </div>
+ {% endifequal %}
+ <div>
+ <a id='gravatar'></a><h3 class="subtitle">{% trans "what is gravatar" %}</h3>
+ <p>{% trans "gravatar faq info" %}</p>
+ </div>
+ <div>
+ <h3 class="subtitle">{% trans "To register, do I need to create new password?" %}</h3>
+ <p>{% trans "No, you don't have to. You can login through any service that supports OpenID, e.g. Google, Yahoo, AOL, etc." %}
+ <strong><a href="/account/signin">{% trans "Login now!" %}</a> »</strong>
+ </p>
+ </div>
- <h3 class="subtitle">还有其他问题?</h3>
- <p> 如果您对社区还有其他疑问,请一起来完善我们的<a href="/tags/faq" class="big">CNProg FAQ</a>。
- </p>
- <br><br>
+ <div>
+ <h3 class="subtitle">{% trans "Why other people can edit my questions/answers?" %}</h3>
+ <p> {% trans "Goal of this site is..." %} {% trans "So questions and answers can be edited like wiki pages by experienced users of this site and this improves the overall quality of the knowledge base content." %}
+ {% trans "If this approach is not for you, we respect your choice." %}
+ </p>
+ </div>
+ <div>
+ <h3 class="subtitle">{% trans "Still have questions?" %}</h3>
+ <p>{% trans "Please ask your question, help make our community better!" %}
+ <!--
+ <a href="/tags/faq" class="big">{% trans "site title" %} {% trans "questions" %}</a>{% trans "." %}
+ -->
+ </p>
+ </div>
</div>
+<script type="text/javascript">
+ //highlihts section if url has matching #anchor_name
+ $(document).ready(function (){
+ var hash = window.location.hash;
+ if (hash.length > 1){
+ $(hash).parent().addClass('highlight');
+ window.location.hash = hash;
+ }
+ })
+</script>
{% endblock %}
-
-
-
+<!-- end template faq.html -->
diff --git a/templates/footer.html b/templates/footer.html
index 46649b9f..637ca0d8 100644
--- a/templates/footer.html
+++ b/templates/footer.html
@@ -1,28 +1,33 @@
-{% load extra_tags %}
-{% load i18n %}
- <div id="ground">
- <div class="footerLinks" >
- <a href="/about">{% trans "About us" %}</a><span class="link-separator"> |</span>
- <a href="/faq">{% trans "faq" %}</a><span class="link-separator"> |</span>
- <a href="http://blog.cnprog.com">Blog</a><span class="link-separator"> |</span>
- <a href="mailto:team@cnprog.com">{% trans "Contact" %}</a><span class="link-separator"> |</span>
- <a href="/privacy">{% trans "Privacy" %}</a><span class="link-separator"> |</span>
- <a href="http://cnprog.uservoice.com" target="_blank">{% trans "Feedback" %}</a>
- </div>
- <p style="margin-top:10px;">
- <a href="http://code.google.com/p/cnprog/" target="_blank">
- <img src="/content/images/djangomade124x25_grey.gif" border="0" alt="Made with Django." title="Made with Django." >
- </a>
- </p>
- <p style="margin-top:-30px; margin-right:15px;" class="fright"><img src="/content/images/cc-wiki.png" title="Creative Commons: Attribution - Share Alike" alt="cc-wiki" width="50" height="68" /></p>
- </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("UA-248512-5");
- pageTracker._trackPageview();
- } catch(err) {}
- </script>
+<!-- template footer.html -->
+{% load extra_tags %}
+{% load i18n %}
+<!-- 页面底部开始: -->
+ <div id="ground">
+ <div class="footerLinks" >
+ <a href="/about">{% trans "about" %}</a><span class="link-separator"> |</span>
+ <a href="/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="/privacy">{% trans "privacy policy" %}</a><span class="link-separator"> |</span>
+ <a href="{{ feedback_url }}" target="_blank">{% trans "give feedback" %}</a>
+ </div>
+ <p style="margin-top:10px;">
+ <a href="http://code.google.com/p/cnprog/" target="_blank">
+ <img src="/content/images/djangomade124x25_grey.gif" border="0" alt="Made with Django." title="Made with Django." >
+ </a>
+ <!--<div style="font-size:90%;color:#333">{% trans "current revision" %}: R-0120-20090406</div>-->
+ </p>
+ <p id="licenseLogo"><img src="/content/images/cc-wiki.png" title="Creative Commons: Attribution - Share Alike" alt="cc-wiki" width="50" height="68" /></p>
+ </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) {}
+ </script>
+<!-- end template footer.html -->
diff --git a/templates/header.html b/templates/header.html
index fb07a4c0..db08a39a 100644
--- a/templates/header.html
+++ b/templates/header.html
@@ -1,70 +1,70 @@
-<!-- template header.html -->
-{% load extra_tags %}
-{% load i18n %}
- <div id="top">
- <div id="header">
- {% if request.user.is_authenticated %}
- <a href="/users/{{ request.user.id }}/{{ request.user.username }}/">{{ request.user.username }}</a> {% get_score_badge request.user %}
- <a href="{% url user_signout %}">{% trans "logout" %}</a>
- {% else %}
- <a href="{% url user_signin %}">{% trans "login" %}</a>
- {% endif %}
- <a href="/about">{% trans "about" %}</a>
- <a href="/faq">{% trans "faq" %}</a>
- </div>
- </div>
- <div id="roof">
- <div id="navBar">
- <table width="100%" border="0" cellspacing="0" cellpadding="0">
- <tr>
- <td width="23%">
- <div id="logo">
- <a href="/">
- <img src="/content/images/logo.png" title="{% trans "back to home page" %}" />
- </a>
- </div>
- </td>
- <td width="77%" valign="bottom">
- <div class="nav">
- <a id="nav_questions" href="/questions/" >{% trans "questions" %}</a>
- <a id="nav_tags" href="/tags/">{% trans "tags" %}</a>
- <a id="nav_users" href="/users/">{% trans "users" %}</a>
- <!--<a id="nav_books" href="/books/">{% trans "books" %}</a>-->
- <a id="nav_badges" href="/badges/">{% trans "badges" %}</a>
- <a id="nav_unanswered" href="/questions/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="/users/{{ request.user.id }}/{{ request.user.username }}/">{% trans "my profile" %}</a>
- {% endif %}
- {% endcomment %}
- <div class="focus">
- <a id="nav_ask" href="/questions/ask/" class="special">{% trans "ask a question" %}</a>
- </div>
- </div>
-
- </td>
- </tr>
- </table>
- </div>
- <div id="searchBar">
- <table width="100%" height="60" border="0" cellpadding="0" cellspacing="0" class="content">
- <tr>
- <td align="center" valign="middle">
- <form action="/search/" method="GET">
- <div>
- <input type="text" class="searchInput" value="{{ keywords }}" name="q" id="keywords" >
- <input type="submit" name="Submit" value="{% trans "search" %}" class="searchBtn" >
- </div>
- <div class="options">
- <input id="type-question" type="radio" class="" value="question" name="t" checked >{% trans "questions" %}
- <input id="type-tag" type="radio" class="" value="tag" name="t" >{% trans "tags" %}
- <input id="type-user" type="radio" class="" value="user" name="t" >{% trans "users" %}
- </div>
- </form>
- </td>
- </tr>
- </table>
- </div>
- </div>
-<!-- end template header.html -->
+<!-- template header.html -->
+{% load extra_tags %}
+{% load i18n %}
+ <div id="roof">
+ <div id="navBar">
+ <div id="top">
+ <!--<div id="header">-->
+ {% if request.user.is_authenticated %}
+ <a href="/users/{{ request.user.id }}/{{ request.user.username }}/">{{ request.user.username }}</a> {% get_score_badge request.user %}
+ <a href="/logout/">{% trans "logout" %}</a>
+ {% else %}
+ <a href="/account/signin">{% trans "login" %}</a>
+ {% endif %}
+ <a href="/about">{% trans "about" %}</a>
+ <a href="/faq">{% trans "faq" %}</a>
+ <!--</div>-->
+ </div>
+ <table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td width="23%">
+ <div id="logo">
+ <a href="/">
+ <img src="/content/images/logo.png" title="{% trans "back to home page" %}" />
+ </a>
+ </div>
+ </td>
+ <td width="77%" valign="bottom">
+ <div class="nav">
+ <a id="nav_questions" href="/questions/" >{% trans "questions" %}</a>
+ <a id="nav_tags" href="/tags/">{% trans "tags" %}</a>
+ <a id="nav_users" href="/users/">{% trans "users" %}</a>
+ <!--<a id="nav_books" href="/books/">{% trans "books" %}</a>-->
+ <a id="nav_badges" href="/badges/">{% trans "badges" %}</a>
+ <a id="nav_unanswered" href="/questions/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="/users/{{ request.user.id }}/{{ request.user.username }}/">{% trans "my profile" %}</a>
+ {% endif %}
+ {% endcomment %}
+ <div class="focus">
+ <a id="nav_ask" href="/questions/ask/" class="special">{% trans "ask a question" %}</a>
+ </div>
+ </div>
+
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="searchBar">
+ <table width="100%" height="60" border="0" cellpadding="0" cellspacing="0" class="content">
+ <tr>
+ <td align="center" valign="middle">
+ <form action="/search/" method="GET">
+ <div>
+ <input type="text" class="searchInput" value="{{ keywords }}" name="q" id="keywords" >
+ <input type="submit" name="Submit" value="{% trans "search" %}" class="searchBtn" >
+ </div>
+ <div class="options">
+ <input id="type-question" type="radio" class="" value="question" name="t" checked >{% trans "questions" %}
+ <input id="type-tag" type="radio" class="" value="tag" name="t" >{% trans "tags" %}
+ <input id="type-user" type="radio" class="" value="user" name="t" >{% trans "users" %}
+ </div>
+ </form>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+<!-- end template header.html -->
diff --git a/templates/index.html b/templates/index.html
index db2bb12e..a1ab06a4 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -1,11 +1,12 @@
{% extends "base.html" %}
+<!-- index.html -->
{% load i18n %}
{% load extra_tags %}
{% load humanize %}
{% load extra_filters %}
{% block title %}{% spaceless %}{% trans "Home" %}{% endspaceless %}{% endblock %}
-{% block meta %}<meta name="keywords" content="{{ APP_KEYWORDS }}" />
- <meta name="description" content="{{ APP_DESCRIPTION }}" />{% endblock %}
+{% block meta %}<meta name="keywords" content="{{ settings.APP_KEYWORDS }}" />
+ <meta name="description" content="{{ settings.APP_DESCRIPTION }}" />{% endblock %}
{% block forejs %}
<script type="text/javascript">
$().ready(function(){
@@ -78,7 +79,7 @@
<div class="boxA">
<h3>{% trans "welcome to website" %}</h3>
<div class="body">
- {{ APP_INTRO|safe }}
+ {{ settings.APP_INTRO|safe }}
<div class="more"><a href="/about">{% trans "about" %} »</a></div>
<div class="more"><a href="/faq">{% trans "faq" %} »</a></div>
</div>
@@ -117,6 +118,7 @@
{% endblock %}
{% block tail %}
<div style="padding:5px 0 5px 5px;">
-<span class="evenMore">{% trans "Still looking for more? See" %} <a href="/questions/">{% trans "complete list of quesionts" %}</a>, {% trans "or" %} <a href="/tags/">{% trans "popular tags" %}</a>{% trans "." %} {% trans "Please help us answer" %} <a href="/questions/unanswered">{% trans "unanswered questions" %}</a>{% trans "." %}</span>
+<span class="evenMore">{% trans "Still looking for more? See" %} <a href="/questions/">{% trans "complete list of questions" %}</a> {% trans "or" %} <a href="/tags/">{% trans "popular tags" %}</a>{% trans "." %} {% trans "Please help us answer" %} <a href="/questions/unanswered">{% trans "list of unanswered questions" %}</a>{% trans "." %}</span>
</div>
{% endblock %}
+<!-- index.html -->
diff --git a/templates/logout.html b/templates/logout.html
index 6270924a..e05a4328 100644
--- a/templates/logout.html
+++ b/templates/logout.html
@@ -1,5 +1,5 @@
-<!-- template logout.html -->
{% extends "base_content.html" %}
+<!-- template logout.html -->
{% load extra_tags %}
{% load humanize %}
{% load i18n %}
@@ -22,4 +22,4 @@
<br><br>
</div>
{% endblock %}
-<!-- ent template logout.html -->
+<!-- end logout.html -->
diff --git a/templates/pagesize.html b/templates/pagesize.html
index e5e17a24..5037f1f6 100644
--- a/templates/pagesize.html
+++ b/templates/pagesize.html
@@ -1,8 +1,9 @@
+<!-- template pagesize.html -->
{% spaceless %}
{% load i18n %}
{% if is_paginated %}
<div class="paginator">
- <span class="text">{% trans "Size per page:" %}</span>
+ <span class="text">{% trans "posts per page" %}</span>
{% ifequal pagesize 10 %}
<span class="curr">10</span>
{% else %}
@@ -23,3 +24,4 @@
</div>
{% endif %}
{% endspaceless %}
+<!-- end template pagesize.html -->
diff --git a/templates/paginator.html b/templates/paginator.html
index ae8329cb..2fba5425 100644
--- a/templates/paginator.html
+++ b/templates/paginator.html
@@ -1,9 +1,10 @@
+<!-- paginator.html -->
{% spaceless %}
{% load i18n %}
{% if is_paginated %}
<div class="paginator">
-{% if has_previous %}<span class="prev"><a href="{{base_url}}page={{ previous }}{{ extend_url }}" title="{% trans 'Previous'%}">&laquo; <% trans "Previous" %></a></span>{% endif %}
-
+{% if has_previous %}<span class="prev"><a href="{{base_url}}page={{ previous }}{{ extend_url }}" title="{% trans "previous" %}">
+&laquo; {% trans "previous" %}</a></span>{% endif %}
{% if not in_leading_range %}
{% for num in pages_outside_trailing_range %}
<span class="page"><a href="{{base_url}}page={{ num }}{{ extend_url }}" >{{ num }}</a></span>
@@ -15,22 +16,23 @@
{% ifequal num page %}
{% ifequal pages 1 %}
{% else %}
- <span class="curr" title='<% trans "Current page" %>'>{{ num }}</span>
+ <span class="curr" title="{% trans "current page" %}">{{ num }}</span>
{% endifequal %}
{% else %}
- <span class="page"><a href="{{base_url}}page={{ num }}{{ extend_url }}" title="No.{{ num }}">{{ num }}</a></span>
+ <span class="page"><a href="{{base_url}}page={{ num }}{{ extend_url }}" title="{% trans "page number " %}{{ num }}{% trans "number - make blank in english" %}">{{ num }}</a></span>
{% endifequal %}
{% endfor %}
{% if not in_trailing_range %}
...
{% for num in pages_outside_leading_range reversed %}
- <span class="page"><a href="{{base_url}}page={{ num }}{{ extend_url }}" title="No.{{ num }}">{{ num }}</a></span>
+ <span class="page"><a href="{{base_url}}page={{ num }}{{ extend_url }}" title="{% trans "page number " %}{{ num }}{% trans "number - make blank in english" %}">{{ num }}</a></span>
{% endfor %}
{% endif %}
-{% if has_next %}<span class="next"><a href="{{base_url}}page={{ next }}{{ extend_url }}" title="下一页">下一页 &raquo;</a></span>{% endif %}
+{% if has_next %}<span class="next"><a href="{{base_url}}page={{ next }}{{ extend_url }}" title="{% trans "next page" %}">{% trans "next page" %} &raquo;</a></span>{% endif %}
</div>
{% endif %}
{% endspaceless %}
+<!-- end paginator.html -->
diff --git a/templates/privacy.html b/templates/privacy.html
index 335aba92..d5d7dd43 100644
--- a/templates/privacy.html
+++ b/templates/privacy.html
@@ -1,5 +1,5 @@
-<!-- privacy.html -->
{% extends "base_content.html" %}
+<!-- privacy.html -->
{% load extra_tags %}
{% load i18n %}
{% load humanize %}
diff --git a/templates/question.html b/templates/question.html
index 5ba08b63..eaff9da3 100644
--- a/templates/question.html
+++ b/templates/question.html
@@ -1,5 +1,8 @@
+{% extends "base.html" %}
<!-- question.html -->
-{% extends "base.html" %}{% load extra_tags %}{% load extra_filters %}{% load humanize %}
+{% load extra_tags %}
+{% load extra_filters %}
+{% load humanize %}
{% load i18n %}
{% block title %}{% spaceless %}{{ question.get_question_title }}{% endspaceless %}{% endblock %}
{% block forejs %}
@@ -250,10 +253,10 @@
</div>
{% endif %}
- {% ifnotequal question.answer_count 0 %}
+ {% ifnotequal answers.length 0 %}
<div class="tabBar">
<a name="sort-top"></a>
- <div class="headQuestions">{{ question.answer_count }}{% trans "Answers" %}:</div>
+ <div class="headQuestions">{{ answers|length }}{% trans "Answers" %}:</div>
<div class="tabsA">
<a id="oldest" href="?sort=oldest#sort-top" title="{% trans "oldest answers will be shown first" %}">{% trans "oldest answers" %}</a>
<a id="latest" href="?sort=latest#sort-top" title="{% trans "newest answers will be shown first" %}">{% trans "newest answers" %}</a>
@@ -431,10 +434,12 @@
</div>
{% if not question.closed %}
- {% if request.user.is_authenticated %}
<div style="padding:10px 0 0 0;">
<div class="headNormal">{% trans "Your answer" %}:</div>
</div>
+ {% if not request.user.is_authenticated %}
+ <div class="message">{% trans "you can answer anonymously and then login" %}</div>
+ {% endif %}
<div id="description" class="" >
<div id="wmd-button-bar" class="wmd-panel"></div>
@@ -458,9 +463,11 @@
</div>
<br>
<input type="submit" value="{% trans "Answer the question" %}" class="submit"><span class="form-error"></span>
- {% else %}
- <input id="btLogin" type="button" class="submit" style="width:200px" value="{% trans "Login to answer" %}">
- {% endif %}
+ {% if request.user.is_authenticated %}
+ {{ answer.email_notify }} <label for="question-subscribe-updates">{% trans "Notify me daily if there are any new answers." %}</label>
+ {% else %}
+ <input type="checkbox" disabled><label>{% trans "once you sign in you will be able to subscribe for any updates here" %}</label>
+ {% endif %}
{% endif %}
<br><br>
</form>
diff --git a/templates/question_edit.html b/templates/question_edit.html
index 6fe8bb41..9ad60a88 100644
--- a/templates/question_edit.html
+++ b/templates/question_edit.html
@@ -1,5 +1,5 @@
-<!-- question_edit.html -->
{% extends "base.html" %}
+<!-- question_edit.html -->
{% load i18n %}
{% block title %}{% spaceless %}{% trans "Edit question" %}{% endspaceless %}{% endblock %}
{% block forejs %}
diff --git a/templates/question_retag.html b/templates/question_retag.html
index aebf93a5..c7062e30 100644
--- a/templates/question_retag.html
+++ b/templates/question_retag.html
@@ -1,5 +1,6 @@
{% extends "base.html" %}
-{% block title %}{% spaceless %}修改问题标签{% endspaceless %}{% endblock %}
+<!-- question_retag.html -->
+{% block title %}{% spaceless %}{% trans "Change tags" %}{% endspaceless %}{% endblock %}
{% block forejs %}
<script type='text/javascript' src='/content/js/com.cnprog.editor.js'></script>
<script type='text/javascript' src='/content/js/com.cnprog.post.js'></script>
@@ -34,8 +35,8 @@
},
messages: {
tags: {
- required: " 标签不能为空。",
- maxlength: " 最多5个标签,每个标签长度小于20个字符。"
+ required: "{% trans "tags are required" %}",
+ maxlength: "{% trans "up to 5 tags, less than 20 characters each" %}
}
}
@@ -48,7 +49,7 @@
{% block content %}
<div id="main-bar" class="headNormal">
- 修改标签 [<a href="{{ question.get_absolute_url }}">返回</a>]
+ {% trans "Change tags" %} [<a href="{{ question.get_absolute_url }}">{% trans "back" %}</a>]
</div>
<div id="main-body" class="ask-body">
<div id="askform">
@@ -71,8 +72,8 @@
<br>
<div class="error" ></div>
- <input type="submit" value="现在修改" class="submit" />
- <input type="button" value="取消" class="submit" onclick="history.back(-1);" />
+ <input type="submit" value="{% trans "Save edit" %}" class="submit" />
+ <input type="button" value="{% trans "Cancel" %}" class="submit" onclick="history.back(-1);" />
<br>
<br>
</form>
@@ -82,20 +83,16 @@
{% block sidebar %}
<div class="boxC">
- <p class="subtitle">为什么我只能修改问题标签?</p>
+ <p class="subtitle">{% trans "Why use and modify tags?" %}</p>
<ul class="list-item">
-
<li>
- CNProg用标签来分类系统的信息
-
+ {% trans "tags help us keep Questions organized" %}
</li>
<li>
修改完整问题需要用户的积分达到一定条件(比如:积分 >= 3000分,自己发布的问题除外),而用户积分达到比较低的时候,就可以修改问题的标签(比如:积分 >= 500, 这里指所有问题的标签)。
-
</li>
<li>
- 修改标签的用户将授予特殊的社区奖牌
-
+ {% trans "tag editors receive special awards from the community" %}
</li>
</ul>
<a href="{% url faq %}" style="float:right;position:relative">faq »</a>
@@ -106,4 +103,4 @@
{% block endjs %}
{% endblock %}
-
+<!-- end question_retag.html -->
diff --git a/templates/questions.html b/templates/questions.html
index bfc1177c..5dac0156 100644
--- a/templates/questions.html
+++ b/templates/questions.html
@@ -1,9 +1,10 @@
{% extends "base.html" %}
-{% load i18n %}
+<!-- questions.html -->
{% load extra_tags %}
+{% load i18n %}
{% load humanize %}
{% load extra_filters %}
-{% block title %}{% spaceless %}{% trans "Question list" %}{% endspaceless %}{% endblock %}
+{% block title %}{% spaceless %}{% trans "Questions" %}{% endspaceless %}{% endblock %}
{% block forejs %}
<script type="text/javascript">
$().ready(function(){
@@ -19,12 +20,12 @@
{% endblock %}
{% block content %}
<div class="tabBar">
- <div class="headQuestions">{% if searchtag %}{% trans "Tagged questions" %}{% else %}{% if searchtitle %}{% trans "Query result" %}{% else %}{% trans "All questions" %}{% endif %}{% endif %}</div>
+ <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="tabsA">
- <a id="latest" href="?sort=latest" class="off" title="{% trans 'New questions' %}">{% trans "Newest" %}</a>
- <a id="active" href="?sort=active" class="off" title="{% trans 'Newest updated questions' %}">{% trans "Active" %}</a>
- <a id="hottest" href="?sort=hottest" class="off" title="{% trans 'Questions with most answers' %}">{% trans "Hottest" %}</a>
- <a id="mostvoted" href="?sort=mostvoted" class="off" title="{% trans 'Questions with most votes' %}">{% trans "Best" %}</a>
+ <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">
@@ -41,9 +42,9 @@
<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 "Visits" %}</span></td>
+ <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>
@@ -54,7 +55,7 @@
{% ifequal tab_id 'active'%}
{% if question.wiki %}
- <span class="from wiki">{% trans "Community wiki" %}</span>
+ <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">
@@ -66,7 +67,7 @@
{% endif %}
{% else %}
{% if question.wiki %}
- <span class="from wiki">{% trans "Community wiki" %}</span>
+ <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">
@@ -80,7 +81,7 @@
<div class="tags">
{% for tag in question.tagname_list %}
- <a href="{% url forum.views.tag tag|urlencode %}" title="{% trans 'Browse questions with tag of '%}" rel="tag">{{ tag }}</a>
+ <a href="{% url forum.views.tag tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
{% endfor %}
</div>
</div>
@@ -104,28 +105,55 @@
{% block sidebar %}
<div class="boxC">
<p>
- 您正在浏览所有<br><div class="questions-count">{{ questions_count|intcomma }}</div>
- <p>个
- {% if searchtag %}
- 标记为
- <span class="tag">
- {{ searchtag }}
- </span>
- {% endif %}
- {% if searchtitle %}
- 标题含有
- <strong class="darkred">
- {{ searchtitle }}
- </strong>
- {% endif %}
- 的问题。</p>
+ {% 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 %}
+ {% 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 %}
+
+ {% ifequal tab_id "latest" %}
+ <p>{% trans "latest questions info" %}</p>
+ {% endifequal %}
+
+ {% ifequal tab_id "active" %}
+ <p>{% trans "Questions are sorted by the <strong>time of last update</strong>." %}
+ {% trans "Most recently answered ones are shown first." %}</p>
+ {% endifequal %}
+
+ {% ifequal tab_id "hottest" %}
+ <p>{% trans "Questions sorted by <strong>number of responses</strong>." %}
+ {% trans "Most answered questions are shown first." %}</p>
+ {% endifequal %}
+
+ {% ifequal tab_id "mostvoted" %}
+ <p>{% trans "Questions are sorted by the <strong>number of votes</strong>." %}
+ {% trans "Most voted questions are shown first." %}</p>
+ {% endifequal %}
+
+
</p>
</div>
<div class="boxC">
<h3 class="subtitle">{% trans "Related tags" %}</h3>
<div class="tags">
{% for tag in tags %}
- <a rel="tag" href="{% url forum.views.tag tag.name|urlencode %}">{{ tag.name }}</a>
+ <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 %}
@@ -134,4 +162,4 @@
</div>
{% endblock %}
-
+<!-- end questions.html -->
diff --git a/templates/reopen.html b/templates/reopen.html
index 7ab59421..37fb69c1 100644
--- a/templates/reopen.html
+++ b/templates/reopen.html
@@ -1,5 +1,5 @@
-<!-- reopen.html -->
{% extends "base_content.html" %}
+<!-- reopen.html -->
{% load extra_tags %}
{% load i18n %}
{% load humanize %}
@@ -34,7 +34,4 @@
</form>
</div>
{% endblock %}
-
-
-
<!-- end reopen.html -->
diff --git a/templates/revisions_answer.html b/templates/revisions_answer.html
index 5ab12fbf..9c2e53eb 100644
--- a/templates/revisions_answer.html
+++ b/templates/revisions_answer.html
@@ -1,5 +1,5 @@
-<!-- revisions_answer.html -->
{% extends "base_content.html" %}
+<!-- revisions_answer.html -->
{% load i18n %}
{% load extra_tags %}
{% load extra_filters %}
diff --git a/templates/revisions_question.html b/templates/revisions_question.html
index 77a421bb..7ada3e74 100644
--- a/templates/revisions_question.html
+++ b/templates/revisions_question.html
@@ -1,5 +1,5 @@
-<!-- revisions_question.html -->
{% extends "base_content.html" %}
+<!-- revisions_question.html -->
<!--somehow very similar to revisions_answer.html-->
{% load extra_tags %}
{% load i18n %}
@@ -99,5 +99,4 @@
{% block endjs %}
{% endblock %}
-
<!-- end revisions_question.html -->
diff --git a/templates/tags.html b/templates/tags.html
index 6131e527..6f9b3aed 100644
--- a/templates/tags.html
+++ b/templates/tags.html
@@ -1,4 +1,5 @@
{% extends "base_content.html" %}
+<!-- tags.html -->
{% load i18n %}
{% load extra_tags %}
{% load humanize %}
diff --git a/templates/unanswered.html b/templates/unanswered.html
index 926f2ffd..13b3a3b2 100644
--- a/templates/unanswered.html
+++ b/templates/unanswered.html
@@ -1,5 +1,5 @@
-<!-- unanswered.html -->
{% extends "base.html" %}
+<!-- unanswered.html -->
{% load extra_tags %}
{% load i18n %}
{% load humanize %}
diff --git a/templates/upfiles/1245715031297631.png b/templates/upfiles/1245715031297631.png
new file mode 100755
index 00000000..89a6aed4
--- /dev/null
+++ b/templates/upfiles/1245715031297631.png
Binary files differ
diff --git a/templates/upfiles/12457157052552259.png b/templates/upfiles/12457157052552259.png
new file mode 100755
index 00000000..89a6aed4
--- /dev/null
+++ b/templates/upfiles/12457157052552259.png
Binary files differ
diff --git a/templates/user.html b/templates/user.html
index 53a30dc0..efca80e6 100644
--- a/templates/user.html
+++ b/templates/user.html
@@ -1,35 +1,33 @@
+{% extends "base_content.html" %}
<!-- user.html -->
-{% extends "base_content.html" %}
-{% load extra_tags %}
-{% load humanize %}
-{% block title %}{% spaceless %}{{ page_title }}{% endspaceless %}{% endblock %}
-{% block forestyle%}
- <style type="text/css">
- .history-table td { padding: 5px; }
- .user-stats-table { margin-left:50px; }
- </style>
-{% endblock %}
-{% block forejs %}
- <script type="text/javascript">
- $().ready(function(){
- {% ifequal view_user request.user%}
- $("#nav_profile").attr('className',"on");
- {% else %}
- $("#nav_users").attr('className',"on");
-
- {% endifequal %}
- });
- </script>
- {% block userjs %}
- {% endblock %}
-{% endblock %}
-
-{% block content %}
- <div id="mainbar-full">
- {% include "user_info.html" %}
- {% include "user_tabs.html" %}
- {% block usercontent %}
- {% endblock %}
- {% include "user_footer.html" %}
- </div>
+{% load extra_tags %}
+{% load humanize %}
+{% block title %}{% spaceless %}{{ page_title }}{% endspaceless %}{% endblock %}
+{% block forestyle%}
+ <style type="text/css">
+ .history-table td { padding: 5px; }
+ .user-stats-table { margin-left:50px; }
+ </style>
+{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ {% ifequal view_user request.user%}
+ $("#nav_profile").attr('className',"on");
+ {% else %}
+ $("#nav_users").attr('className',"on");
+ {% endifequal %}
+ });
+ </script>
+ {% block userjs %}
+ {% endblock %}
+{% endblock %}
+{% block content %}
+ <div id="mainbar-full">
+ {% include "user_info.html" %}
+ {% include "user_tabs.html" %}
+ {% block usercontent %}
+ {% endblock %}
+ {% include "user_footer.html" %}
+ </div>
{% endblock %}<!-- end user.html -->
diff --git a/templates/user_edit.html b/templates/user_edit.html
index 0f927374..b49cea31 100644
--- a/templates/user_edit.html
+++ b/templates/user_edit.html
@@ -1,5 +1,5 @@
-<!-- user_edit.html -->
{% extends "base_content.html" %}
+<!-- user_edit.html -->
{% load extra_tags %}
{% load humanize %}
{% load i18n %}
diff --git a/templates/user_favorites.html b/templates/user_favorites.html
index d47670bd..185423c6 100644
--- a/templates/user_favorites.html
+++ b/templates/user_favorites.html
@@ -1,5 +1,5 @@
-<!-- user_favorites.html -->
{% extends "user.html" %}
+<!-- user_favorites.html -->
{% load extra_tags %}
{% load humanize %}
diff --git a/templates/user_info.html b/templates/user_info.html
index 8e6dca84..8d35015c 100644
--- a/templates/user_info.html
+++ b/templates/user_info.html
@@ -70,12 +70,14 @@
<td>{% get_age view_user.date_of_birth %} {% trans "age unit" %}</td>
</tr>
{% endif %}
+ <!--
{% if votes_today_left %}
<tr>
<td>{% trans "todays unused votes" %}</td>
<td><strong class="darkred">{{ votes_today_left }}</strong> {% trans "votes left" %}</td>
</tr>
{% endif %}
+ -->
</table>
</td>
<td width="380">
diff --git a/templates/user_preferences.html b/templates/user_preferences.html
index 3a760a25..eb7c4761 100644
--- a/templates/user_preferences.html
+++ b/templates/user_preferences.html
@@ -1,20 +1,23 @@
{% extends "user.html" %}
+<!-- user_preferences.html -->
+{% load i18n %}
{% load extra_tags %}
{% load humanize %}
{% block usercontent %}
<div style="padding:5px;">
<fieldset>
- <legend><b>同步Twitter消息</b></legend>
+ <legend><b>{% trans "Connect with Twitter" %}</b></legend>
<form>
- <label for="name">账号:</label>
+ <label for="name">{% trans "Twitter account name:" %}</label>
<input id="name" /><br>
- <label for="password">密码:</label>
+ <label for="password">{% trans "Twitter password:" %}</label>
<input id="password" type="password"/><br>
- <input id="cbMessage" type="checkbox" />发布我的提问到我的Twitter<br>
- <input id="cbReply" type="checkbox" />发布我的回答到我的Twitter<br>
- <input type="submit" value="保存" />
+ <input id="cbMessage" type="checkbox" />{% trans "Send my Questions to Twitter" %}<br>
+ <input id="cbReply" type="checkbox" />{% trans "Send my Answers to Twitter" %}<br>
+ <input type="submit" value="{% trans "Save" %}" />
</form>
</fieldset>
</div>
{% endblock %}
+<!-- end user_preferences.html -->
diff --git a/templates/user_recent.html b/templates/user_recent.html
index 70c074ad..7f6c9c8c 100644
--- a/templates/user_recent.html
+++ b/templates/user_recent.html
@@ -1,5 +1,5 @@
-<!-- user_recent.html -->
{% extends "user.html" %}
+<!-- user_recent.html -->
{% load extra_tags %}
{% load humanize %}
diff --git a/templates/user_reputation.html b/templates/user_reputation.html
index 7a1c7366..bb7200de 100644
--- a/templates/user_reputation.html
+++ b/templates/user_reputation.html
@@ -1,5 +1,5 @@
-<!-- user_reputation.html -->
{% extends "user.html" %}
+<!-- user_reputation.html -->
{% load extra_tags %}
{% load humanize %}
{% block userjs %}
diff --git a/templates/user_responses.html b/templates/user_responses.html
index 45aab21b..d739395b 100644
--- a/templates/user_responses.html
+++ b/templates/user_responses.html
@@ -1,5 +1,5 @@
-<!-- user_responses.html -->
{% extends "user.html" %}
+<!-- user_responses.html -->
{% load extra_tags %}
{% load humanize %}
diff --git a/templates/user_stats.html b/templates/user_stats.html
index a5be1a77..432a5b34 100644
--- a/templates/user_stats.html
+++ b/templates/user_stats.html
@@ -1,5 +1,5 @@
-<!-- user_stats.html -->
{% extends "user.html" %}
+<!-- user_stats.html -->
{% load i18n %}
{% load extra_tags %}
{% load humanize %}
diff --git a/templates/user_tabs.html b/templates/user_tabs.html
index 03a9d111..bcc12b8f 100644
--- a/templates/user_tabs.html
+++ b/templates/user_tabs.html
@@ -22,13 +22,11 @@
<a id="favorites" {% ifequal tab_name "favorites" %}class="on"{% endifequal %}
title="{% trans "questions that user selected as his/her favorite"
href="/users/{{view_user.id}}/{{view_user.username}}?sort=favorites">{% trans "favorites" %}</a>
- <!--
{% if request.user|can_view_user_preferences:view_user %}
<a id="preferences" {% ifequal tab_name "preferences" %}class="on"{% endifequal %}
title="{% trans "user preference settings" %}"
href="/users/{{view_user.id}}/{{view_user.username}}?sort=preferences">{% trans "settings" %}</a>
{% endif %}
- -->
</div>
</div>
<!-- end user_tabs.html -->
diff --git a/templates/user_votes.html b/templates/user_votes.html
index ec967704..80fa27ee 100644
--- a/templates/user_votes.html
+++ b/templates/user_votes.html
@@ -1,5 +1,5 @@
-<!-- user_votes.html -->
{% extends "user.html" %}
+<!-- user_votes.html -->
{% load extra_tags %}
{% load humanize %}
{% load i18n %}
diff --git a/templates/users.html b/templates/users.html
index c3c9f790..966596fc 100644
--- a/templates/users.html
+++ b/templates/users.html
@@ -1,4 +1,5 @@
{% extends "base_content.html" %}
+<!-- users.html -->
{% load extra_tags %}
{% load humanize %}
{% load i18n %}
diff --git a/urls.py b/urls.py
index a219fd44..eb28cf69 100644
--- a/urls.py
+++ b/urls.py
@@ -1,64 +1,67 @@
-import os.path
-from django.conf.urls.defaults import *
-from django.contrib import admin
-from forum.views import index
-from forum import views as app
-from forum.feed import RssLastestQuestionsFeed
-
-admin.autodiscover()
-feeds = {
- 'rss': RssLastestQuestionsFeed
-}
-
-APP_PATH = os.path.dirname(__file__)
-urlpatterns = patterns('',
- (r'^$', index),
- (r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/content/images/favicon.ico'}),
- (r'^favicon\.gif$', 'django.views.generic.simple.redirect_to', {'url': '/content/images/favicon.gif'}),
- (r'^content/(?P<path>.*)$', 'django.views.static.serve',
- {'document_root': os.path.join(APP_PATH, 'templates/content').replace('\\','/')}
- ),
- (r'^upfiles/(?P<path>.*)$', 'django.views.static.serve',
- {'document_root': os.path.join(APP_PATH, 'templates/upfiles').replace('\\','/')}
- ),
- (r'^account/', include('django_authopenid.urls')),
- (r'^signin/$', 'django_authopenid.views.signin'),
- url(r'^about/$', app.about, name='about'),
- url(r'^faq/$', app.faq, name='faq'),
- url(r'^privacy/$', app.privacy, name='privacy'),
- url(r'^logout/$', app.logout, name='logout'),
- url(r'^answers/(?P<id>\d+)/comments/$', app.answer_comments, name='answer_comments'),
- url(r'^answers/(?P<id>\d+)/edit/$', app.edit_answer, name='edit_answer'),
- url(r'^answers/(?P<id>\d+)/revisions/$', app.answer_revisions, name='answer_revisions'),
- url(r'^questions/$', app.questions, name='questions'),
- url(r'^questions/ask/$', app.ask, name='ask'),
- url(r'^questions/unanswered/$', app.unanswered, name='unanswered'),
- url(r'^questions/(?P<id>\d+)/edit/$', app.edit_question, name='edit_question'),
- url(r'^questions/(?P<id>\d+)/close/$', app.close, name='close'),
- url(r'^questions/(?P<id>\d+)/reopen/$', app.reopen, name='reopen'),
- url(r'^questions/(?P<id>\d+)/answer/$', app.answer, name='answer'),
- url(r'^questions/(?P<id>\d+)/vote/$', app.vote, name='vote'),
- url(r'^questions/(?P<id>\d+)/revisions/$', app.question_revisions, name='question_revisions'),
- url(r'^questions/(?P<id>\d+)/comments/$', app.question_comments, name='question_comments'),
- url(r'^questions/(?P<question_id>\d+)/comments/(?P<comment_id>\d+)/delete/$', app.delete_question_comment, name='delete_question_comment'),
- url(r'^answers/(?P<answer_id>\d+)/comments/(?P<comment_id>\d+)/delete/$', app.delete_answer_comment, name='delete_answer_comment'),
- #place general question item in the end of other operations
- url(r'^questions/(?P<id>\d+)//*', app.question, name='question'),
- (r'^tags/$', app.tags),
- (r'^tags/(?P<tag>[^/]+)/$', app.tag),
- (r'^users/$',app.users),
- url(r'^users/(?P<id>\d+)/edit/$', app.edit_user, name='edit_user'),
- url(r'^users/(?P<id>\d+)//*', app.user, name='user'),
- url(r'^badges/$',app.badges, name='badges'),
- url(r'^badges/(?P<id>\d+)//*', app.badge, name='badge'),
- url(r'^messages/markread/$',app.read_message, name='read_message'),
- # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
- (r'^nimda/(.*)', admin.site.root),
- (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}),
- (r'^upload/$', app.upload),
- url(r'^books/$', app.books, name='books'),
- url(r'^books/ask/(?P<short_name>[^/]+)/$', app.ask_book, name='ask_book'),
- url(r'^books/(?P<short_name>[^/]+)/$', app.book, name='book'),
- url(r'^search/$', app.search, name='search'),
- (r'^i18n/', include('django.conf.urls.i18n')),
-)
+import os.path
+from django.conf.urls.defaults import *
+from django.contrib import admin
+from forum.views import index
+from forum import views as app
+from forum.feed import RssLastestQuestionsFeed
+
+admin.autodiscover()
+feeds = {
+ 'rss': RssLastestQuestionsFeed
+}
+
+APP_PATH = os.path.dirname(__file__)
+urlpatterns = patterns('',
+ (r'^$', index),
+ (r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/content/images/favicon.ico'}),
+ (r'^favicon\.gif$', 'django.views.generic.simple.redirect_to', {'url': '/content/images/favicon.gif'}),
+ (r'^content/(?P<path>.*)$', 'django.views.static.serve',
+ {'document_root': os.path.join(APP_PATH, 'templates/content').replace('\\','/')}
+ ),
+ (r'^upfiles/(?P<path>.*)$', 'django.views.static.serve',
+ {'document_root': os.path.join(APP_PATH, 'templates/upfiles').replace('\\','/')}
+ ),
+ (r'^account/', include('django_authopenid.urls')),
+ (r'^signin/$', 'django_authopenid.views.signin'),
+ url(r'^email/change/$', 'django_authopenid.views.changeemail', name='user_changeemail'),
+ url(r'^email/sendkey/$', 'django_authopenid.views.send_email_key'),
+ url(r'^email/verify/(?P<id>\d+)/(?P<key>[\dabcdef]{32})/$', 'django_authopenid.views.verifyemail', name='user_verifyemail'),
+ url(r'^about/$', app.about, name='about'),
+ url(r'^faq/$', app.faq, name='faq'),
+ url(r'^privacy/$', app.privacy, name='privacy'),
+ url(r'^logout/$', app.logout, name='logout'),
+ url(r'^answers/(?P<id>\d+)/comments/$', app.answer_comments, name='answer_comments'),
+ url(r'^answers/(?P<id>\d+)/edit/$', app.edit_answer, name='edit_answer'),
+ url(r'^answers/(?P<id>\d+)/revisions/$', app.answer_revisions, name='answer_revisions'),
+ url(r'^questions/$', app.questions, name='questions'),
+ url(r'^questions/ask/$', app.ask, name='ask'),
+ url(r'^questions/unanswered/$', app.unanswered, name='unanswered'),
+ url(r'^questions/(?P<id>\d+)/edit/$', app.edit_question, name='edit_question'),
+ url(r'^questions/(?P<id>\d+)/close/$', app.close, name='close'),
+ url(r'^questions/(?P<id>\d+)/reopen/$', app.reopen, name='reopen'),
+ url(r'^questions/(?P<id>\d+)/answer/$', app.answer, name='answer'),
+ url(r'^questions/(?P<id>\d+)/vote/$', app.vote, name='vote'),
+ url(r'^questions/(?P<id>\d+)/revisions/$', app.question_revisions, name='question_revisions'),
+ url(r'^questions/(?P<id>\d+)/comments/$', app.question_comments, name='question_comments'),
+ url(r'^questions/(?P<question_id>\d+)/comments/(?P<comment_id>\d+)/delete/$', app.delete_question_comment, name='delete_question_comment'),
+ url(r'^answers/(?P<answer_id>\d+)/comments/(?P<comment_id>\d+)/delete/$', app.delete_answer_comment, name='delete_answer_comment'),
+ #place general question item in the end of other operations
+ url(r'^questions/(?P<id>\d+)//*', app.question, name='question'),
+ (r'^tags/$', app.tags),
+ (r'^tags/(?P<tag>[^/]+)/$', app.tag),
+ (r'^users/$',app.users),
+ url(r'^users/(?P<id>\d+)/edit/$', app.edit_user, name='edit_user'),
+ url(r'^users/(?P<id>\d+)//*', app.user, name='user'),
+ url(r'^badges/$',app.badges, name='badges'),
+ url(r'^badges/(?P<id>\d+)//*', app.badge, name='badge'),
+ url(r'^messages/markread/$',app.read_message, name='read_message'),
+ # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+ (r'^nimda/(.*)', admin.site.root),
+ (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}),
+ (r'^upload/$', app.upload),
+ url(r'^books/$', app.books, name='books'),
+ url(r'^books/ask/(?P<short_name>[^/]+)/$', app.ask_book, name='ask_book'),
+ url(r'^books/(?P<short_name>[^/]+)/$', app.book, name='book'),
+ url(r'^search/$', app.search, name='search'),
+ (r'^i18n/', include('django.conf.urls.i18n')),
+)
diff --git a/utils/cache.py b/utils/cache.py
index bc1cb1af..410c0662 100644
--- a/utils/cache.py
+++ b/utils/cache.py
@@ -1,92 +1,92 @@
-"""Utilities for working with Django Models."""
-import itertools
-
-from django.contrib.contenttypes.models import ContentType
-
-from lanai.utils.lists import flatten
-
-def fetch_model_dict(model, ids, fields=None):
- """
- Fetches a dict of model details for model instances with the given
- ids, keyed by their id.
-
- If a fields list is given, a dict of details will be retrieved for
- each model, otherwise complete model instances will be retrieved.
-
- Any fields list given shouldn't contain the primary key attribute for
- the model, as this can be determined from its Options.
- """
- if fields is None:
- return model._default_manager.in_bulk(ids)
- else:
- id_attr = model._meta.pk.attname
- return dict((obj[id_attr], obj) for obj
- in model._default_manager.filter(id__in=ids).values(
- *itertools.chain((id_attr,), fields)))
-
-def populate_foreign_key_caches(model, objects_to_populate, fields=None):
- """
- Populates caches for the given related Model in instances of objects
- which have a ForeignKey relationship to it, specified as a list of
- (object list, related attribute name list) two-tuples.
-
- If a list of field names is given, only the given fields will be
- looked up and related object caches will be populated with a dict of
- the specified fields. Otherwise, complete model instances will be
- retrieved.
- """
- # Get all related object ids for the appropriate fields
- related_object_ids = []
- for objects, attrs in objects_to_populate:
- related_object_ids.append(tuple(tuple(getattr(obj, '%s_id' % attr)
- for attr in attrs)
- for obj in objects))
- unique_ids = tuple(set(pk for pk in flatten(related_object_ids) if pk))
- related_objects = fetch_model_dict(model, unique_ids, fields)
-
- # Fill related object caches
- for (objects, attrs), related_ids in itertools.izip(objects_to_populate,
- related_object_ids):
- for obj, related_ids_for_obj in itertools.izip(objects,
- related_ids):
- for attr, related_object in itertools.izip(attrs, (related_objects.get(pk, None)
- for pk in related_ids_for_obj)):
- setattr(obj, '_%s_cache' % attr, related_object)
-
-def populate_content_object_caches(generic_related_objects, model_fields=None):
- """
- Retrieves ``ContentType`` and content objects for the given list of
- items which use a generic relation, grouping the retrieval of content
- objects by model to reduce the number of queries executed.
-
- This results in ``number_of_content_types + 1`` queries rather than
- the ``number_of_generic_reL_objects * 2`` queries you'd get by
- iterating over the list and accessing each item's object attribute.
-
- If a dict mapping model classes to field names is given, only the
- given fields will be looked up for each model specified and the
- object cache will be populated with a dict of the specified fields.
- Otherwise, complete model instances will be retrieved.
- """
- if model_fields is None:
- model_fields = {}
-
- # Group content object ids by their content type ids
- ids_by_content_type = {}
- for obj in generic_related_objects:
- ids_by_content_type.setdefault(obj.content_type_id,
- []).append(obj.object_id)
-
- # Retrieve content types and content objects in bulk
- content_types = ContentType.objects.in_bulk(ids_by_content_type.keys())
- for content_type_id, ids in ids_by_content_type.iteritems():
- model = content_types[content_type_id].model_class()
- objects[content_type_id] = fetch_model_dict(
- model, tuple(set(ids)), model_fields.get(model, None))
-
- # Set content types and content objects in the appropriate cache
- # attributes, so accessing the 'content_type' and 'object' attributes
- # on each object won't result in further database hits.
- for obj in generic_related_objects:
- obj._object_cache = objects[obj.content_type_id][obj.object_id]
- obj._content_type_cache = content_types[obj.content_type_id]
+"""Utilities for working with Django Models."""
+import itertools
+
+from django.contrib.contenttypes.models import ContentType
+
+from lanai.utils.lists import flatten
+
+def fetch_model_dict(model, ids, fields=None):
+ """
+ Fetches a dict of model details for model instances with the given
+ ids, keyed by their id.
+
+ If a fields list is given, a dict of details will be retrieved for
+ each model, otherwise complete model instances will be retrieved.
+
+ Any fields list given shouldn't contain the primary key attribute for
+ the model, as this can be determined from its Options.
+ """
+ if fields is None:
+ return model._default_manager.in_bulk(ids)
+ else:
+ id_attr = model._meta.pk.attname
+ return dict((obj[id_attr], obj) for obj
+ in model._default_manager.filter(id__in=ids).values(
+ *itertools.chain((id_attr,), fields)))
+
+def populate_foreign_key_caches(model, objects_to_populate, fields=None):
+ """
+ Populates caches for the given related Model in instances of objects
+ which have a ForeignKey relationship to it, specified as a list of
+ (object list, related attribute name list) two-tuples.
+
+ If a list of field names is given, only the given fields will be
+ looked up and related object caches will be populated with a dict of
+ the specified fields. Otherwise, complete model instances will be
+ retrieved.
+ """
+ # Get all related object ids for the appropriate fields
+ related_object_ids = []
+ for objects, attrs in objects_to_populate:
+ related_object_ids.append(tuple(tuple(getattr(obj, '%s_id' % attr)
+ for attr in attrs)
+ for obj in objects))
+ unique_ids = tuple(set(pk for pk in flatten(related_object_ids) if pk))
+ related_objects = fetch_model_dict(model, unique_ids, fields)
+
+ # Fill related object caches
+ for (objects, attrs), related_ids in itertools.izip(objects_to_populate,
+ related_object_ids):
+ for obj, related_ids_for_obj in itertools.izip(objects,
+ related_ids):
+ for attr, related_object in itertools.izip(attrs, (related_objects.get(pk, None)
+ for pk in related_ids_for_obj)):
+ setattr(obj, '_%s_cache' % attr, related_object)
+
+def populate_content_object_caches(generic_related_objects, model_fields=None):
+ """
+ Retrieves ``ContentType`` and content objects for the given list of
+ items which use a generic relation, grouping the retrieval of content
+ objects by model to reduce the number of queries executed.
+
+ This results in ``number_of_content_types + 1`` queries rather than
+ the ``number_of_generic_reL_objects * 2`` queries you'd get by
+ iterating over the list and accessing each item's object attribute.
+
+ If a dict mapping model classes to field names is given, only the
+ given fields will be looked up for each model specified and the
+ object cache will be populated with a dict of the specified fields.
+ Otherwise, complete model instances will be retrieved.
+ """
+ if model_fields is None:
+ model_fields = {}
+
+ # Group content object ids by their content type ids
+ ids_by_content_type = {}
+ for obj in generic_related_objects:
+ ids_by_content_type.setdefault(obj.content_type_id,
+ []).append(obj.object_id)
+
+ # Retrieve content types and content objects in bulk
+ content_types = ContentType.objects.in_bulk(ids_by_content_type.keys())
+ for content_type_id, ids in ids_by_content_type.iteritems():
+ model = content_types[content_type_id].model_class()
+ objects[content_type_id] = fetch_model_dict(
+ model, tuple(set(ids)), model_fields.get(model, None))
+
+ # Set content types and content objects in the appropriate cache
+ # attributes, so accessing the 'content_type' and 'object' attributes
+ # on each object won't result in further database hits.
+ for obj in generic_related_objects:
+ obj._object_cache = objects[obj.content_type_id][obj.object_id]
+ obj._content_type_cache = content_types[obj.content_type_id]
diff --git a/utils/html.py b/utils/html.py
index 602e1a76..25a74a4a 100644
--- a/utils/html.py
+++ b/utils/html.py
@@ -1,51 +1,51 @@
-"""Utilities for working with HTML."""
-import html5lib
-from html5lib import sanitizer, serializer, tokenizer, treebuilders, treewalkers
-
-class HTMLSanitizerMixin(sanitizer.HTMLSanitizerMixin):
- acceptable_elements = ('a', 'abbr', 'acronym', 'address', 'b', 'big',
- 'blockquote', 'br', 'caption', 'center', 'cite', 'code', 'col',
- 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'font',
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd',
- 'li', 'ol', 'p', 'pre', 'q', 's', 'samp', 'small', 'span', 'strike',
- 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead',
- 'tr', 'tt', 'u', 'ul', 'var')
-
- acceptable_attributes = ('abbr', 'align', 'alt', 'axis', 'border',
- 'cellpadding', 'cellspacing', 'char', 'charoff', 'charset', 'cite',
- 'cols', 'colspan', 'datetime', 'dir', 'frame', 'headers', 'height',
- 'href', 'hreflang', 'hspace', 'lang', 'longdesc', 'name', 'nohref',
- 'noshade', 'nowrap', 'rel', 'rev', 'rows', 'rowspan', 'rules', 'scope',
- 'span', 'src', 'start', 'summary', 'title', 'type', 'valign', 'vspace',
- 'width')
-
- allowed_elements = acceptable_elements
- allowed_attributes = acceptable_attributes
- allowed_css_properties = ()
- allowed_css_keywords = ()
- allowed_svg_properties = ()
-
-class HTMLSanitizer(tokenizer.HTMLTokenizer, HTMLSanitizerMixin):
- def __init__(self, stream, encoding=None, parseMeta=True, useChardet=True,
- lowercaseElementName=True, lowercaseAttrName=True):
- tokenizer.HTMLTokenizer.__init__(self, stream, encoding, parseMeta,
- useChardet, lowercaseElementName,
- lowercaseAttrName)
-
- def __iter__(self):
- for token in tokenizer.HTMLTokenizer.__iter__(self):
- token = self.sanitize_token(token)
- if token:
- yield token
-
-def sanitize_html(html):
- """Sanitizes an HTML fragment."""
- p = html5lib.HTMLParser(tokenizer=HTMLSanitizer,
- tree=treebuilders.getTreeBuilder("dom"))
- dom_tree = p.parseFragment(html)
- walker = treewalkers.getTreeWalker("dom")
- stream = walker(dom_tree)
- s = serializer.HTMLSerializer(omit_optional_tags=False,
- quote_attr_values=True)
- output_generator = s.serialize(stream)
- return u''.join(output_generator)
+"""Utilities for working with HTML."""
+import html5lib
+from html5lib import sanitizer, serializer, tokenizer, treebuilders, treewalkers
+
+class HTMLSanitizerMixin(sanitizer.HTMLSanitizerMixin):
+ acceptable_elements = ('a', 'abbr', 'acronym', 'address', 'b', 'big',
+ 'blockquote', 'br', 'caption', 'center', 'cite', 'code', 'col',
+ 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'font',
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd',
+ 'li', 'ol', 'p', 'pre', 'q', 's', 'samp', 'small', 'span', 'strike',
+ 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead',
+ 'tr', 'tt', 'u', 'ul', 'var')
+
+ acceptable_attributes = ('abbr', 'align', 'alt', 'axis', 'border',
+ 'cellpadding', 'cellspacing', 'char', 'charoff', 'charset', 'cite',
+ 'cols', 'colspan', 'datetime', 'dir', 'frame', 'headers', 'height',
+ 'href', 'hreflang', 'hspace', 'lang', 'longdesc', 'name', 'nohref',
+ 'noshade', 'nowrap', 'rel', 'rev', 'rows', 'rowspan', 'rules', 'scope',
+ 'span', 'src', 'start', 'summary', 'title', 'type', 'valign', 'vspace',
+ 'width')
+
+ allowed_elements = acceptable_elements
+ allowed_attributes = acceptable_attributes
+ allowed_css_properties = ()
+ allowed_css_keywords = ()
+ allowed_svg_properties = ()
+
+class HTMLSanitizer(tokenizer.HTMLTokenizer, HTMLSanitizerMixin):
+ def __init__(self, stream, encoding=None, parseMeta=True, useChardet=True,
+ lowercaseElementName=True, lowercaseAttrName=True):
+ tokenizer.HTMLTokenizer.__init__(self, stream, encoding, parseMeta,
+ useChardet, lowercaseElementName,
+ lowercaseAttrName)
+
+ def __iter__(self):
+ for token in tokenizer.HTMLTokenizer.__iter__(self):
+ token = self.sanitize_token(token)
+ if token:
+ yield token
+
+def sanitize_html(html):
+ """Sanitizes an HTML fragment."""
+ p = html5lib.HTMLParser(tokenizer=HTMLSanitizer,
+ tree=treebuilders.getTreeBuilder("dom"))
+ dom_tree = p.parseFragment(html)
+ walker = treewalkers.getTreeWalker("dom")
+ stream = walker(dom_tree)
+ s = serializer.HTMLSerializer(omit_optional_tags=False,
+ quote_attr_values=True)
+ output_generator = s.serialize(stream)
+ return u''.join(output_generator)
diff --git a/utils/lists.py b/utils/lists.py
index 426d9cd3..bbcfae98 100644
--- a/utils/lists.py
+++ b/utils/lists.py
@@ -1,86 +1,86 @@
-"""Utilities for working with lists and sequences."""
-
-def flatten(x):
- """
- Returns a single, flat list which contains all elements retrieved
- from the sequence and all recursively contained sub-sequences
- (iterables).
-
- Examples:
- >>> [1, 2, [3, 4], (5, 6)]
- [1, 2, [3, 4], (5, 6)]
-
- From http://kogs-www.informatik.uni-hamburg.de/~meine/python_tricks
- """
- result = []
- for el in x:
- if hasattr(el, '__iter__') and not isinstance(el, basestring):
- result.extend(flatten(el))
- else:
- result.append(el)
- return result
-
-def batch_size(items, size):
- """
- Retrieves items in batches of the given size.
-
- >>> l = range(1, 11)
- >>> batch_size(l, 3)
- [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
- >>> batch_size(l, 5)
- [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
- """
- return [items[i:i+size] for i in xrange(0, len(items), size)]
-
-def batches(items, number):
- """
- Retrieves items in the given number of batches.
-
- >>> l = range(1, 11)
- >>> batches(l, 1)
- [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]
- >>> batches(l, 2)
- [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
- >>> batches(l, 3)
- [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
- >>> batches(l, 4)
- [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
- >>> batches(l, 5)
- [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
-
- Initial batches will contain as many items as possible in cases where
- there are not enough items to be distributed evenly.
-
- >>> batches(l, 6)
- [[1, 2], [3, 4], [5, 6], [7, 8], [9], [10]]
- >>> batches(l, 7)
- [[1, 2], [3, 4], [5, 6], [7], [8], [9], [10]]
- >>> batches(l, 8)
- [[1, 2], [3, 4], [5], [6], [7], [8], [9], [10]]
- >>> batches(l, 9)
- [[1, 2], [3], [4], [5], [6], [7], [8], [9], [10]]
- >>> batches(l, 10)
- [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]]
-
- If there are more batches than items, empty batches will be appended
- to the batch list.
-
- >>> batches(l, 11)
- [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], []]
- >>> batches(l, 12)
- [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [], []]
- """
- div, mod= divmod(len(items), number)
- if div > 1:
- if mod:
- div += 1
- return batch_size(items, div)
- else:
- if not div:
- return [[item] for item in items] + [[]] * (number - mod)
- elif div == 1 and not mod:
- return [[item] for item in items]
- else:
- # mod now tells you how many lists of 2 you can fit in
- return ([items[i*2:(i*2)+2] for i in xrange(0, mod)] +
- [[item] for item in items[mod*2:]])
+"""Utilities for working with lists and sequences."""
+
+def flatten(x):
+ """
+ Returns a single, flat list which contains all elements retrieved
+ from the sequence and all recursively contained sub-sequences
+ (iterables).
+
+ Examples:
+ >>> [1, 2, [3, 4], (5, 6)]
+ [1, 2, [3, 4], (5, 6)]
+
+ From http://kogs-www.informatik.uni-hamburg.de/~meine/python_tricks
+ """
+ result = []
+ for el in x:
+ if hasattr(el, '__iter__') and not isinstance(el, basestring):
+ result.extend(flatten(el))
+ else:
+ result.append(el)
+ return result
+
+def batch_size(items, size):
+ """
+ Retrieves items in batches of the given size.
+
+ >>> l = range(1, 11)
+ >>> batch_size(l, 3)
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
+ >>> batch_size(l, 5)
+ [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
+ """
+ return [items[i:i+size] for i in xrange(0, len(items), size)]
+
+def batches(items, number):
+ """
+ Retrieves items in the given number of batches.
+
+ >>> l = range(1, 11)
+ >>> batches(l, 1)
+ [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]
+ >>> batches(l, 2)
+ [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
+ >>> batches(l, 3)
+ [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
+ >>> batches(l, 4)
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
+ >>> batches(l, 5)
+ [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
+
+ Initial batches will contain as many items as possible in cases where
+ there are not enough items to be distributed evenly.
+
+ >>> batches(l, 6)
+ [[1, 2], [3, 4], [5, 6], [7, 8], [9], [10]]
+ >>> batches(l, 7)
+ [[1, 2], [3, 4], [5, 6], [7], [8], [9], [10]]
+ >>> batches(l, 8)
+ [[1, 2], [3, 4], [5], [6], [7], [8], [9], [10]]
+ >>> batches(l, 9)
+ [[1, 2], [3], [4], [5], [6], [7], [8], [9], [10]]
+ >>> batches(l, 10)
+ [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]]
+
+ If there are more batches than items, empty batches will be appended
+ to the batch list.
+
+ >>> batches(l, 11)
+ [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], []]
+ >>> batches(l, 12)
+ [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [], []]
+ """
+ div, mod= divmod(len(items), number)
+ if div > 1:
+ if mod:
+ div += 1
+ return batch_size(items, div)
+ else:
+ if not div:
+ return [[item] for item in items] + [[]] * (number - mod)
+ elif div == 1 and not mod:
+ return [[item] for item in items]
+ else:
+ # mod now tells you how many lists of 2 you can fit in
+ return ([items[i*2:(i*2)+2] for i in xrange(0, mod)] +
+ [[item] for item in items[mod*2:]])